上一篇对call和apply的模拟实现做了一个梳理,可参见:模拟实现call、apply,下面将具体研究一下bind啦啦啦
1. bind和call/apply的差别
bind方法会创建一个新函数,返回值是一个绑定了上下文的函数
call和apply是将函数直接执行
描述:
bind()函数会创建一个绑定函数(bound function,BF),它包装了原函数对象,调用该绑定函数即执行原函数
返回值:是一个原函数拷贝,并拥有指定的this值和初始参数
当一个绑定函数是用来作为构造函数即使用new操作符去构造一个新实例时,原来bind绑定的this将被忽略,this将指向当前新创建的实例。但是提供的参数列表仍然会插入到构造函数调用时的参数列表之前。
2. bind的模拟实现
bind的几个特性:
- 指定this
- 返回值为一个函数
- 可以传参数
- 函数柯里化
第一步:基本实现以上四个特性
Function.prototype.bind2 = function (context) { var self = this; // 这句实际上是把调用bind2的函数赋给self,console.log(self)得到的是一个函数 var args = Array.prototype.slice.call(arguments, 1); // 获取传入的参数,将其变为数组存入args中 return function () { var funArgs = Array.prototype.slice.call(arguments); // 这里的arguments是这个return函数中传入的参数 return self.apply(context, args.concat(funArgs)) // 将this指向context,将self的参数和return的function的参数拼接起来 } }
说明:
- var self = this;不是常理中我们想象的将this的值保存在self,这里是将整个函数赋给self,其实也等同于将this指向调用者
- 因为arguments是类数组对象,所以这里还是使用Array.prototype.slice.call的方式将arguments存储在args数组中
- funArgs得到的是返回的function中带的参数,实际上就是实现柯里化的一种参数获取方式!
第二步:实现bind特性
当绑定函数使用new操作符创建对象时,会使原this的指向失效,this会指向新的对象实例!
var value = 2; var foo = { value: 1 }; function bar(name, age) { this.habit = 'shopping'; console.log(this.value); console.log(name); console.log(age); }
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind(foo, 'Jack'); var obj = new bindFoo(20); // undefined // Jack // 20
首先将foo这个对象绑到bar这个函数上,传入参数“Jack”,然后将绑定函数bindFoo()作为构造函数生成新的对象obj,此时bindFoo的this是指向obj的,这里可以看到this.value并没有输出1,而是输出了undefined。因为obj没有value值!
因此这里将通过修改返回函数的原型实现:
Function.prototype.bind2 = function (context) { var self = this; var args = Array.prototype.slice.call(arguments, 1); var fNOP = function () { }; // 1 创建一个空对象 var fBound = function () { var funArgs = Array.prototype.slice.call(arguments); return self.apply( this instanceof fNOP ? this : context, // this instanceof fNOP为true时,说明返回的fBound被当做构造函数调用;为false时,this指向context args.concat(funArgs) ); } fNOP.prototype = this.prototype; // 2 空对象的原型 指向 绑定函数的原型 fBound.prototype = new fNOP(); // 3 然后将空对象实例赋给fBound.prototype return fBound; }
以上已基本实现bind的功能
- 说明:注释中的123三步是利用空对象作为中介的方式来维护原型关系,实际上完成的功能是:修改返回函数的prototype为绑定函数的prototype —— 完成这三步后,new出来的实例就可以继承绑定函数的原型中的值!拿上题为例,即obj可以获取到bar.prototype的friend值。
第三步:
当调用bind调用的不是函数时需要抛出异常!因此加上如下代码就得到bind模拟的最终的完整版:
Function.prototype.bind2 = function (context) { // 如果bind绑定的不是函数,则抛错 if (typeof this !== "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable") } var self = this; var args = Array.prototype.slice.call(arguments, 1); var fNOP = function () { }; var fBound = function () { var funArgs = Array.prototype.slice.call(arguments); return self.apply( this instanceof fNOP ? this : context, args.concat(funArgs) ); } fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }