javascript关于bind方法的实现

bindECMA5中才出现的方法,IE8等低版本浏览器并不支持,所以在MDN中有了这样的一段代码:

if (!Function.prototype.bind) { //如果低版本浏览器不支持bind
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") { //如果this不是一个函数,抛出错误
      // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1),  //arguments为bind()括号中的参数
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis || window,
                               aArgs.concat(Array.prototype.slice.call(arguments))); //argument 为bind返回方法运行时候的参数
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

在理解的时候,加了一些注释。但是其他的一段代码不知道为什么(或者修补什么情况)存在:

fNOP = function () {} //只是为了单纯复制this的原型链?
this instanceof fNOP && oThis ? this : oThis || window //经测试,this instanceof fNO一直返回false,所以等价于 oThis || window,那为什么存在?
fNOP.prototype = this.prototype; //?
fBound.prototype = new fNOP();//?

按照我的理解,觉得以下代码也可以满足需要:

if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
        if (typeof this !== "function") {
          throw new TypeError("bind function error");
        }
        var aArgs = Array.prototype.slice.call(arguments,1), 
            fToBind = this, 
            fBound = function () {
              return fToBind.apply(oThis || window,aArgs.concat(Array.prototype.slice.call(arguments)));
            };
        return fBound;
      };
}

下面测试代码,好使:

    var obj = {
        name : 'name1',
        say : function (arg1, arg2) {
            console.log(this.name, arg1, arg2)
        }
    }
    var foo = {
        name : 'name2'
    }
    obj.say.bind(foo, '必定显示1', '必定显示2')('没有位置不显示了1', '没有位置不显示了2') // name2 必定显示1 必定显示2

能给个比较清楚的解释吗?

阅读 6.3k
4 个回答

我把部分代码的位置调整了一下,更方便看明白具体工作原理:

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    //下面是核心代码

    var aArgs = Array.prototype.slice.call(arguments, 1),     //准备要绑定的参数

        //现在是在Function.prototype.bind这个函数里
        //因此这一句的this指的是OriginalFunction.bind(...)的OriginalFunction,也就是调用bind方法的那个对象
        fToBind = this, 

        //建立一个空函数,指定它的prototype与OriginalFunction相同
        fNOP = function () {},
        fNOP.prototype = this.prototype;

        //fBound是bind函数的结果
    var fBound;

        //注意,这样写是错误的,因为此时fBound还是undefined,但是为了按顺序说明我把对prototype的赋值提前了
        fBound.prototype = new fNOP();
        //fBound的原型被指定为new fNOP(),不妨记作fnopObj
        //这样的话,如果我们把fBound也就是绑定后的函数当作构造函数使用的话:
            /*
                var bndObj = new fBound();
             */
        //bndObj.__proto__就是fnopObj,而fnopObj.__proto__是OriginalFunction.prototype
        //再来看直接调用原函数:
            /*
                var oriObj = new OriginalFunction();
             */
        //oriObj.__proto__是OriginalFunction.prototype
        //也就是说如果我们绑定了一个构造函数A得到新的构造函数B,则使用B构造的对象仍然会是A的实例,只不过原型链被new fNOP插了一脚
        //反正new fNOP()本身是{}没有什么属性,插这一脚不影响新对象的使用

        fBound = function () {
          //注意,现在进入了fBound这个函数内,所以this就是fBound的调用者
          //当我们直接调用绑定好的函数时:
              /*
                  var f = function (x, y) { return x + y; }
                  var f7y = f.bind(7); //此时产生的f7y即为bind函数返回的fBound
                  var r = f7y(16); //直接调用f7y,所以this就是window,因此this就不是fNOP的实例,apply传进去的第一个参数就会是oThis || window
               */
          //而如果我们使用了new来调用它,例如:
              /*
                  var f = function (x, y) { this.x = x; this.y = y }
                  var f7y = f.bind(7);
                  var r = new f7y(16);
                  //注意到f7y是使用new来调用的,因此this就是新new出来的对象了
                  //而f7y也就是fBound的prototype属性是new fNOP(),所以this是fNOP的实例
                  //这样就完成了构造函数的功能
               */
          //如果调用时指定oThis为null,那么apply的第一个参数就是window
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis || window,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    return fBound;
  };
}

下面是我加工过的一个版本,更加接近原生的Function.prototype.bind的特性:

Function.prototype.nbind = function (oThis) {
                if (typeof this !== "function") {
                    throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
                }

                var aArgs = Array.prototype.slice.call(arguments, 1),

                    fToBind = this,

                    fBound = function () {
                        var thisToBind;
                        if (this instanceof fToBind) {
                            thisToBind = this;
                        } else if (oThis) {
                            thisToBind = oThis;
                        } else {
                            thisToBind = window;
                        }
                        return fToBind.apply(thisToBind, aArgs.concat(Array.prototype.slice.call(arguments)));
                    };
                    fBound.prototype = fToBind.prototype;
                return fBound;
            };

            //普通函数
            var add = function (a, b) {
                return a + b;
            };
            var nadd = add.nbind(null, 1);
            console.log(nadd(7));

            //配合setTimeout/setInterval
            var counter = {
                interval: 0,
                count: 0,
                start: function () {
                    this.count = 0;
                    this.interval = setInterval(this.say.nbind(this), 1000);
                },
                end: function  () {
                    clearInterval(this.interval);
                },
                say: function () {
                    console.log(this.count);
                    this.count++;
                }
            };
            counter.start();
            setTimeout(function () {
                counter.end();
            }, 10000);

            //构造函数
            var Fruit = function (color, name, price) {
                this.color = color;
                this.name = name;
                this.price = price;
            };
            //第一次绑定,使用null
            var RedFruit = Fruit.nbind(null, 'red');
            var rf = new RedFruit('apple', 5)
            console.log(rf);
            //第二次绑定,使用了一个空的对象o
            var o = {};
            var RedApple = RedFruit.nbind(o, 'apple');
            var ra = new RedApple(7)
            console.log(ra);
            console.log(o); //o没变

简单的说你要保证这个函数作为构造函数也是可用的,请看官方的例子

jsfunction Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function() { 
  return this.x + ',' + this.y; 
};

var p = new Point(1, 2);
p.toString(); // '1,2'


var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);
// not supported in the polyfill below,
// works fine with native bind:
var YAxisPoint = Point.bind(null, 0/*x*/);

var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5'

axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new Point(17, 42) instanceof YAxisPoint; // true

正准备写篇文章介绍这段代码, 其实最主要的就是你疑惑的地方~

首先你需要看到bind函数的官方描述有这么一段话:

A bound function may also be constructed using the new operator: doing
so acts as though the target function had instead been constructed.
The provided this value is ignored, while prepended arguments are
provided to the emulated function.

恩, 你需要考虑bind之后把函数当做constructor用时, this指向问题~

具体说起来好像还有上下文, 我写好文章再贴过来~~

LZ不理解代码的部分我觉得是为了保持被返回的新函数继承原来函数的原型链上的属性和方法。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏