1. 防抖函数
  • 原理:在事件被触发n秒后再执行回调, 如果再这n秒内又被触发,则重新计时。

  • 适用场景:

    • 按钮提交场景: 防止多次提交按钮,只执行最后提交的一次
    • 服务端验证场景: 表单验证需要服务端配合,只执行一段连续的输入事件的最后一次, 还有搜索联想词功能等类似
  • 完整代码

    1
    function debounce(func, wait, immediate) {
    2
        let time;
    3
        let debounced = function() {
    4
            let context = this
    5
            if (time) {
    6
                clearTimeout(this);
    7
            }
    8
            if (immediate) {
    9
                let callNow = !time;
    10
                if (callNow) func.apply(context, arguments)
    11
                time = setTimeout (
    12
                	() => {time = null)
    13
                        , wait)
    14
            } else {
    15
                    time = setTimeout {
    16
                    	() => {func.apply(context, arguments)}, wait)
    17
        	}
    18
        }
    19
        
    20
        debouced.cancel = function() {
    21
            clearTimeout(time)
    22
            time = null
    23
        }
    24
        
    25
        return debounced
    26
    }

    参考:https://github.com/mqyqingfeng/Blog/issues/22#issuecomment-357874221

  • 生产环境使用lodash.debounce

    https://segmentfault.com/a/1190000015312430

2.节流函数
  • 原理:规定再一个单位时间内, 只触发一次函数,如果这个单位时间内触发多次函数, 只有一次生效

  • 场景:

    • 拖拽:固定时间内只执行一次, 防止超高频次触发位置变动
    • 缩放:监控浏览器resize
    • 动画:避免短时间多次触发动画引起性能问题
  • 实现:

    1
    // 节流函数
    2
    const throttle = (fn, delay = 500) => {
    3
      let flag = true;
    4
      return (...args) => {
    5
        if (!flag) return;
    6
        flag = false;
    7
        setTimeout(() => {
    8
          fn.apply(this, args);
    9
          flag = true;
    10
        }, delay);
    11
      };
    12
    };
3. 深克隆(deepclone)

精简版:

1
const newObj = JSON.parse(JSON.stringify(oldObj)))

局限:

  • 无法实现对函数, RegExp等特殊对象的克隆
  • 会抛弃对象的constructor, 所有的构造函数会指向Object
  • 对象有循环引用, 会报错

完整版:

1
const clone = parent => {
2
    // 判断类型
3
    const isType = (obj, type) => {
4
      if (typeof Obj !== "object") return false;
5
      const typeString = Object.prototype.toString.call(Obj);
6
      let flag;
7
      switch (type) {
8
        case "Array":
9
          flag = typeString == "[object Array]";
10
          break;
11
        case "Date":
12
          flag = typeString == "[object Date]";
13
          break;
14
        case "RegExp":
15
          flag = typeString == "[object RegExp]";
16
          break;
17
        default:
18
          flag = false;
19
      }
20
      return flag;
21
    };
22
23
    // 处理正则
24
    const getRegExp = re => {
25
      var flags = "";
26
      if (re.global) flags += "g";
27
      if (re.ignoreCase) flags += "i";
28
      if (re.multiline) flags += "m";
29
      return flags;
30
    };
31
32
    // 维护两个存储循环引用的数组
33
    const parents = [];
34
    const children = [];
35
36
    const _clone = parent => {
37
      if (parent === null) return null;
38
      if (typeof parent !== "object") return parent;
39
40
      let child, proto;
41
42
      if (isType(parent, "Array")) {
43
        child = [];
44
      } else if (isType(parent, "RegEXp") {
45
        child = new RegExp(parent.source, getRegExp(parent));
46
        if (parent.lastIndex) child.lastIndex = parent.lastIndex;
47
      } else if (isType(parent, "Date")) {
48
        child = new Date(parent.getTime());
49
      } else {
50
        // 处理对象原型
51
        proto = Object.getPrototypeOf(parent);
52
        // 利用Object.crete切断原型链
53
        child = Object.create(proto);
54
      }
55
56
      // 处理循环引用
57
      const index = parents.indexOf(parent);
58
59
      if (index != -1) {
60
        return children[index];
61
      }
62
      parents.push(parent);
63
      children.push(child);
64
65
      for (let i in parent) {
66
        child[i] = _clone(parent[i]);
67
      }
68
      return child;
69
    };
70
    return _clone(parent)
71
  }
4. 实现Event(event bus)

event bud 是node中各个模块的基础, 也是前端组件通信的依赖手段之一,也涉及了订阅-发布涉及模式,是非常重要的基础

  • 简单实现

    1
    class EventEmeitter {
    2
      constructor() {
    3
        this._events = this._events || new Map();
    4
        this._maxListeners = this._maxListeners || 10;
    5
      }
    6
    }
    7
      
    8
    EventEmeitter.prototype.emit = function(type, ...args) {
    9
      let handler;
    10
      
    11
      // 从存储事件键值对的this._events 中获取对应事件回调函数
    12
      handler = this._events.get(type);
    13
      if (args.length > 0) {
    14
        handler.apply(this, args);
    15
      } else {
    16
        handler.call(this);
    17
      }
    18
      return true;
    19
    };
    20
      
    21
    // 监听名为type的事件
    22
    EventEmeitter.prototype.addListener = function(type, fn) {
    23
      if (!this._events.get(type)) {
    24
        this._events.set(type, fn)
    25
      }
    26
    };
  • 完整实现:

    1
    class EventEmeitter {
    2
      constructor() {
    3
        this._events = this._events || new Map();
    4
        this._maxListeners = this._maxListeners || 10;
    5
      }
    6
    }
    7
      
    8
    EventEmeitter.prototype.emit = function (type, ...args) {
    9
      let handler;
    10
      
    11
      // 从存储事件键值对的this._events中获取对应事件回调函数
    12
      handler = this._events.get(type);
    13
      if (args.length > 0) {
    14
        handler.apply(this, args);
    15
      } else {
    16
        handler.call(this);
    17
      }
    18
      return true;
    19
    };
    20
      
    21
    // 监听名为type的事件
    22
    EventEmeitter.prototype.addListener = function (type, fn) {
    23
      // 将type事件以及对应的fn函数放入this._events中存储
    24
      if (!this._events.get(type)) {
    25
        this._events.set(type, fn);
    26
      }
    27
    };
    28
      
    29
    // 触发名为type的事件
    30
    EventEmeitter.prototype.emit = function (type, ...args) {
    31
      let handler;
    32
      handler = this._events.get(type);
    33
      if (Array.isArray(handler)) {
    34
        // 如果是一个数组说明有多个监听者,需要依次触发里面的函数
    35
        for (let i = 0; i < handler.length; i++) {
    36
          if (args.length > 0) {
    37
            handler[i].apply(this, args);
    38
          } else {
    39
            handler[i].call(this);
    40
          }
    41
        }
    42
      } else {
    43
        // 单个函数直接触发
    44
        if (args.length > 0) {
    45
          handler.apply(this, args);
    46
        } else {
    47
          handler.call(this);
    48
        }
    49
      }
    50
      return true;
    51
    };
    52
      
    53
    // 监听名为type的事件
    54
    EventEmeitter.prototype.addListener = function (type, fn) {
    55
      const handler = this._events.get(type);
    56
      if (!handler) {
    57
        this._events.set(type, fn);
    58
      } else if (handler && typeof handler === "function") {
    59
        this._events.set(type, [handler, fn]);
    60
      } else {
    61
        handler.push(fn);
    62
      }
    63
    };
    64
      
    65
    EventEmeitter.prototype.removeListener = function (type, fn) {
    66
      const handler = this._events.get(type);
    67
      
    68
      //如果是函数,说明只被监听了一次
    69
      if (handler && typeof handler === "function") {
    70
        this._events.delete(type, fn);
    71
      } else {
    72
        let position;
    73
      
    74
        // 如果handler是数组, 说明被监听多次要找到对应的函数
    75
        for (let i = 0; i < handler.length; i++) {
    76
          if (handler[i] === fn) {
    77
            position = i;
    78
          } else {
    79
            position = -1;
    80
          }
    81
        }
    82
      
    83
        // 如果找到匹配的函数, 就从数组中清楚
    84
        if (position !== -1) {
    85
          // 找到数组对应的位置, 直接清除此回调
    86
          handler.splice(position, 1);
    87
          // 如果清除后只有一个函数, 那么取消数组, 以函数形式保存
    88
          if (handler.length === 1) {
    89
            this._events.set(type, handler[0]);
    90
          } else {
    91
            return this;
    92
          }
    93
        }
    94
      }
    95
    }
5. 实现instanceOf
1
// 模拟 instanceof
2
function instance_of(L, R) {
3
  //L 表示左表达式,R 表示右表达式
4
  var O = R.prototype; // 取 R 的显示原型
5
  L = L.__proto__; // 取 L 的隐式原型
6
  while (true) {
7
    if (L === null) return false;
8
    if (O === L)
9
      // 这里重点:当 O 严格等于 L 时,返回 true
10
      return true;
11
    L = L.__proto__;
12
  }
13
}
6.模拟new

new操作符做了这些事

  • 创建了一个全新的对象

  • 会被执行[[ProtoTtpe]](也就是__proto__)链接

  • 使this指向新创建的对象

  • 通过new创建的每个对象最终被[[ProtoType]]链接到这个函数的prototype对象上

  • 如果函数没有返回对象类型Object(包含Function, Array, Date, RegExg, Error), 那么new表达式中的函数雕塑将返回该对象引用

    1
    function objectFactory() {
    2
      const obj = new Object();
    3
      const Contstructor = [].shift.call(arguments);
    4
    5
      obj.__proto__ = Contstructor.prototype;
    6
      const ret = Contstructor.apply(obj, arguments);
    7
    8
      return typeof ret === "object" ? ret : obj;
    9
    }
6.实现一个call

call所作操作:

  • 将函数设为对象的属性

  • 执行&删除这个函数

  • 执行this到函数并传入给定参数执行函数

  • 如果不传入参数, 默认指向为window

    1
    Function.prototype.myCall = function (context) {
    2
      // 没有考虑context非obejct的情况
    3
      context.fn = this;
    4
      let args = [];
    5
      for (let i = 1, len = arguments.length; i < len; i++) {
    6
        args.push(arguments[i]);
    7
      }
    8
      context.fn(...args);
    9
      let result = context.fn(...args);
    10
      delete context.fn;
    11
      return result;
    12
    }