美文网首页
分析 MDN bind方法的Polyfill

分析 MDN bind方法的Polyfill

作者: 回调的幸福时光 | 来源:发表于2019-05-10 23:09 被阅读0次

    一、bind 方法介绍

    1. bind 方法创建一个新的绑定函数
    2. bind 方法重新绑定原函数的 this 值
    3. 在调用绑定函数时,将bind 中的给定参数作为原函数的参数
    function.bind(thisArg, arg1, arg2, ...)
    
    • thisArg
      调用绑定函数时,将原函数的 this 绑定为 thisArg
    1. 如果将绑定函数作为构造函数,通过关键字 new 调用,则忽略参数 thisArg

    二、从简单到复杂

    版本 1: apply 模拟 bind

    版本一不考虑那么多,仅仅使用 apply 来实现一个简单的 bind。调用语法和 bind 相似:

    function.myBind(context, arg1, arg2, ...)
    

    这里核心就是在 myBind 函数中如何获取原函数?
    利用 this 关键字即可达到目的,具体请看 梳理 this 关键字的指向 中的对象方法

    Function.prototype.myBind = function (context) {
      const fToBind  = this; // 此处 this 指向调用 myBind 函数的对象,其实就是原函数。
      // 新的绑定函数
      function fBound() {
        // 说明:这里返回的是原函数执行 apply 后返回的结果
        return fToBind.apply(context);
      }
      // 返回绑定函数
      return fBound;
    }
    

    调用示例:

    demo 版本一
    版本 2: 考虑参数

    这里参数有两种情况:

    • myBind 的预设参数
    • 新的绑定函数 fBound 的参数
    Function.prototype.myBind = function (context) {
      const fToBind  = this; // 此处 this 指向调用 myBind 函数的对象,其实就是原函数。
      const outerArgs   = [].slice.call(arguments, 1);
      // 返回新的绑定函数
      function fBound () {
        const innerArgs = [].slice.call(arguments);
        // 说明:这里返回的是原函数执行 apply 后返回的结果
        return fToBind.apply(context, outerArgs.concat(innerArgs));
      }
      // 返回绑定函数
      return fBound;
    }
    

    调用示例:

    在例子中利用 myBind 实现了偏函数,新的绑定函数 add 在调用时,只传入了一个参数 2, 但是结果是 1 + 2

    demo 版本二
    版本 3: 考虑构造函数的影响

    3.1 如果将绑定函数作为构造函数,通过关键字 new 调用,则忽略参数 context。

    通过 instanceof 即可判断是否通过 new 关键字调用构造函数。

    Function.prototype.myBind = function (context) {
      const fToBind  = this; // 此处 this 指向调用 myBind 函数的对象,其实就是原函数。
      const outerArgs   = [].slice.call(arguments, 1);
      // 返回新的绑定函数
      function fBound () {
        const innerArgs = [].slice.call(arguments);
        // 说明:这里返回的是原函数执行 apply 后返回的结果
        return fToBind.apply(this instanceof fBound ? this : context, outerArgs.concat(innerArgs));
      }
      // 返回绑定函数
      return fBound;
    }
    

    运行示例:
    从以下例子中可以看出,Person 是通过 myBind 生成的新的绑定函数:

    • Person 作为构造函数,通过 new 关键字调用时,this 指向实例对象,obj 被忽略了。
    • Person 作为普通函数调用时, this 指向 obj。
    demo 3.1

    3.2 原型链的影响
    上述的仍旧不够完美,Person 当作构造函数时,如果在函数 person 中执行语句 this.say(),就会报错。
    因为 myBind 函数返回的绑定函数 fBound 的 prototype 和原函数的 prototype 并不相同。

    那能不能直接简单粗暴地执行语句fBound.prototype = fToBind.prototype,将原函数的 prototype 赋值给 fBound 呢?

    很明显这样的操作把 fBound 和 原函数的 prototype 强关联起来了,如果 fBound 函数的 prototype 将会影响到原函数的 prototype。

    所以可以联想到通过 fBound.prototype = Object.create(fToBind.prototype) ,以原函数的 prototype 为模板,生成一个新的实例对象,并赋值给 fBound.prototype。

    Function.prototype.myBind = function (context) {
      const fToBind  = this; // 此处 this 指向调用 myBind 函数的对象,其实就是原函数。
      const outerArgs   = [].slice.call(arguments, 1);
      // 返回新的绑定函数
      function fBound () {
        const innerArgs = [].slice.call(arguments);
        // 说明:这里返回的是原函数执行 apply 后返回的结果
        return fToBind.apply(this instanceof fBound ? this : context, outerArgs.concat(innerArgs));
      }
    
      fBound.prototype = Object.create(fToBind.prototype);
      // 返回绑定函数
      return fBound;
    }
    

    运行示例:
    从以下例子中可以发现,eat 函数之后被添加到 fBound 的原型上,而不会影响到原函数。

    demo 3.2

    3.3 mdn 中并未使用 Object.create()
    因为 Object.create()bind 都是 ES5 规范提出的,如果不支持 bind, 那么bind 的 polyfill 里面自然不支持 Object.create()。

    Object.create 的实现:

    原型式继承:(参见 js 高级程序设计)

    function object(o) {
      function F() {};
      F.prototype = o;
      return new F();
    }
    

    应用到 myBind 中:

    Function.prototype.myBind = function (context) {
      const fToBind  = this; // 此处 this 指向调用 myBind 函数的对象,其实就是原函数。
      const fNop = function () {};
      const outerArgs   = [].slice.call(arguments, 1);
      // 返回新的绑定函数
      function fBound () {
        const innerArgs = [].slice.call(arguments);
        // 说明:这里返回的是原函数执行 apply 后返回的结果
        return fToBind.apply(this instanceof fBound ? this : context, outerArgs.concat(innerArgs));
      }
       
      // 这里为什么需要判断?
      if (fToBind.prototype) {
        fNop.prototype = fToBind.prototype;
      }
      fBound.prototype = new fNop();
      // 返回绑定函数
      return fBound;
    }
    

    疑惑:为什么需要对 fToBind.prototype 进行判断?
    因为 Function.prototype 是一个函数,而且它没有 prototype。
    即防止有人这么调用 bind 方法: Function.prototype.bind()

    3.4 instanceof 不准确

    这个使用 new.target 即可。

    参考

    MDN bind
    从一道面试题的进阶,到“我可能看了假源码”
    stackoverflow
    MDN Object.create

    更深入的分析文章推荐

    从一道面试题的进阶,到“我可能看了假源码”(2)
    面试官问:能否模拟实现JS的bind方法
    不用call和apply方法模拟实现ES5的bind方法

    相关文章

      网友评论

          本文标题:分析 MDN bind方法的Polyfill

          本文链接:https://www.haomeiwen.com/subject/fofovqtx.html