原生JS实现bind()函数

一、bind()函数的两个特性:

1、bind和curring,函数科里化

function add(a, b, c) {
    var i = a+b+c;
    console.log(i);
    return i;
}

var func = add.bind(undefined, 100);//给add()传了第一个参数a
func(1, 2);//103,继续传入b和c

var func2 = func.bind(undefined, 200);//给func2传入第一个参数,也就是b,此前func已有参数a=100
func2(10);//310,继续传入c,100+200+10

  可以利用此种特性方便代码重用,如下,可以不同的页面中只需要配置某几项,前面几项固定的配置可以选择用bind函数先绑定好,讲一个复杂的函数拆分成简单的子函数。

2、bind和new

function foo() {
    this.b = 100;
    console.log(this.a);
    return this.a;
}

var func =  foo.bind({a:1});
func();//1
new func();//undefined   {b:100},可以看到此时上面的bind并不起作用

  函数中的return除非返回的是个对象,否则通过new返回的是个this,指向一个空对象,空对象原型指向foo.prototype,空对象的b属性是100。也就是说通过new的方式创建一个对象,bind()函数在this层面上并不起作用,但是需要注意在参数层面上仍起作用,如下:

function foo(c) {
    this.b = 100;
    console.log(this.a);
    console.log(c);
    return this.a;
}

var func =  foo.bind({a:1},20);
new func();//undefined 20,通过new创建对象func,bind绑定的c依旧起作用

二、bind实现

  了解完以上两个特性,再来看看bind()的实现:

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), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis || window,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };

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

    return fBound;
  };
}

 

  第3行:传入oThis就是foo.bind({a:1})中传入的对象{a:1};

  第4行:判断调用此bind方法的对象是不是一个函数function,若不是则报错;

  第10行:

  (1)因为函数自带的arguments属性并不是一个数组,只是一个类数组,不具有slice这些方法,所以用call方法给slice()指定this为arguments,让arguments也可以实现slice()方法。
  (2)后面传入参数1,是slice(start, end)中的一个参数start,表示从arguments的小标为1,即第二个参数开始切割。 这里是将bind函数的参数数组取出来,第一个参数不要(就是不要oThis)也就是要被绑定方法的那个对象

  (3)arguments参数只有在函数调用执行的时候才存在,也就是当var func = foo.bind({a:1});的时候,调用了bind,此时aArgs是一个空数组。如果是var func = foo.bind({a:1}, 2),那么aArgs = [2];

  第12行,13行,20行,21行:创建了一个空对象FNOP,并将这个空对象的原型指向foo的原型;

  然后又将func/fBound的原型指向一个新的FNOP实例,这个步骤完成了给func/fBound拷贝一个FNOP的prototype即this/foo的prototype。

  其实这几句就相当于fBound.prototype = Object.create(this.prototype);

  第14行:

  (1)给 fBound/func return一个fToBind/foo对象;

  (2)这里的this指的是调用func()时的执行环境;直接调用func()的时候,this指向的是全局对象,那么结果是oThis/{a:1},这样就可以让这个fToBind的this指向这个传进来的对象oThis;

  (3)bind()同时也会传参数:aArgs.concat(Array.prototype.slice.call(arguments))

  【注意】这里的arguments是调用此函数时的arguments,也就是func()的执行环境,和上面的arguments(bind的执行环境)不一样,在此例中,此时的arguments是空数组,因为并没有给func()传参数。这段contact的意思就是把bind()中传的参数和func()中传的参数连起来,来实现上面提到的bind的科里性。

  (4)如果通过new func()来调用,this会指向一个空对象,这个空对象的原型会指向构造器的prototype的属性,也就是func/fBound的prototype属性。此时this instanceof fNOP 为true,那么返回的是this就是当前正常的this;相当于忽略掉bind的this的影响,实现了上述的bind特性二:bind和new。

  那么想想为什么要给func/fBound拷贝一个FNOP的prototype即this/foo的prototype?没有实现这个会怎样?

  我们知道bind()函数其实是实现了this的指定和参数的传递; 实际中的new func()其实相当于创建了func()一个新实例,使用的构造函数是func,它只为新对象定义了【默认的】属性和方法。也就是Object.create(this.prototype)的作用,如果不把foo的prototype拷贝个func,那么这里的new func()就没法得到foo默认的属性。 如图我把那两行注释掉后,za没办法获取到this.b的值

 

posted @ 2018-07-27 22:52  古兰精  阅读(20527)  评论(1编辑  收藏  举报