手写bind()函数,理解MDN上的标准Polyfill

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010552788/article/details/50850453

若还不了解bind、apply、call的用法先看上一篇博文:JS中的call、apply、bind方法
若还不了解Object.create()请先看[ JavaScript创建对象的三种方法]
(http://blog.csdn.net/u010552788/article/details/50849191)


看了慕课上Bosn讲的Ployfill实现的bind之后,做个总结。视屏网址


在看Polyfill实现的bind()函数之前,先介绍一下bind()函数的两个特性:
①、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函数先绑定好,讲一个复杂的函数拆分成简单的子函数。
这里写图片描述
②、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()的实现:
这里写图片描述
为标记行数方便解释,再截一个在sublime里面的图:
这里写图片描述

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

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

第10行:①因为函数自带的arguments属性并不是一个数组,只是一个类数组,不具有slice这些方法,所以用call方法给slice()指定this为arguments,让arguments也可以实现slice()方法。
②后面传入参数1,是slice(start, end)中的一个参数start,表示从arguments的小标为1,即第二个参数开始切割。
③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行:①给 fBound/func return一个fToBind/foo对象;
②这里的this指的是调用func()时的执行环境;直接调用func()的时候,this指向的是全局对象,那么结果是oThis/{a:1},这样就可以让这个fToBind的this指向这个传进来的对象oThis;
③bind()同时也会传参数
aArgs.concat(Array.prototype.slice.call(arguments)),【注意】这里的arguments是调用此函数时的arguments,也就是func()的执行环境,和上面的arguments(bind的执行还款)不一样,在此例中,此时的arguments是空数组,因为并没有给func()传参数。这段contact的意思就是把bind()中传的参数和func()中传的参数连起来,来实现上面提到的bind的科里性。【关于arguments这里可以参考 arguments 是何时指向函数调用的
④如果通过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的值
这里写图片描述

关于这一点以上是我自己的理解,也可能有不对的地方。

阅读更多

没有更多推荐了,返回首页