bind();
bind方法会创建一个新函数,当这个函数被调用时,它的this值是传递给bind的第一个参数。传入bind的第二个以及以后的参数再加上调用新函数时传入的参数按照顺序作为原函数的参数来调用原函数。
bind返回的新函数也能使用new操作符创建对象:这种行为就像把原函数作为构造器,提供的this会被忽略,同时调用时的参数被提供给原函数。
模拟实现
首先我们需要实现以下四点特征:
1、可以指定this
2、返回一个函数
3、可以传入参数
第一步
第一点:使用call/apply指定this
第二点:使用return 返回一个函数
综合以上两点,可以写出第一版,代码如下:
Function.prototype.bind1 = function(context){
var self = this;
return function(){
return self.apply(context);
}
}
测试一下:
//测试用例
function bar(){
console.log(this.value);
}
let foo = { value : 1 };
let bindF = bar.bind(foo);
bindF();
第二步
第三点: 使用arguments获取参数数组,并作为self.apply()的第二个参数。
第二版,代码如下:
Function.prototype.bind1 = function(context){
var self = this;
//截取第一个之后的所有参数
var args = Array.prototype.slice.call(arguments,1);
return function(){
//这里的arguments是bind返回的函数调用时传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(context,args.concat(bindArgs));
}
}
测试一下
//测试用例
function bar(name,age){
return {
value:this.value,
name:name,
age:age
}
}
let foo = { value : 1 };
let bindF = bar.bind(foo,'jack');
bindF(18);
第三步
bind有以下特征:
bind返回的新函数也能使用new操作符创建对象:这种行为就像把原函数作为构造器,提供的this值被忽略,同时调动时的参数被提供给模拟函数。
通过例子说明下:
var obj = {
value:1
};
function bar(name,age){
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age)
}
bar.prototype.mother = 'lily';
var bindFoo = bar.bind(obj,'jack');
var foo = new bindFoo();
console.log(foo.value);
上面的例子中,运行结果this.value输出undefined,这说明bind的this对象失效了,new的实现中生成一个新的对象,这个时候的this指向foo
可以通过修改返回函数的原型来实现,代码如下:
Function.prototype.bind1 = function(context){
var self = this;
var args = Array.prototype.slice.call(arguments,1);
let fBound = function(){
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fBound ? this:context ,args.concat(bindArgs)); //one
}
fBound.prototpye = this.prototype; //two
return fBound;
}
- one
1)、当作为构造函数时,this指向实例,此时 this instanceof fBound结果为true,可以让实例获得来自绑定函数的值(this.habit="shoping")。
2)、当作为普通函数时,this指向window,此时结果为false,将绑定函数的this指向context。 - two
修改返回函数的prototype为绑定函数的prototype,实例就可以继承绑定函数的原型中的属性,即上例中的foo可以获取bar原型上的属性(mother)
第四步
上面实现的fBound.prototype - this.prototype有一个缺点,直接修改fBound.prototype的时候,也会直接修改this.prototype
来个代码测试一下:
var obj = {
value:1
};
function bar(name,age){
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age)
}
bar.prototype.mother = 'lily';
var bindFoo = bar.bind(obj,'jack');
var foo = new bindFoo();
foo.__proto__.mother='kitty';
console.log(bar.prototype.mother) // 打印结果:kitty 这里被修改了
解决方案用一个空对象作为中介,将fBound.prototype赋值为空对象的实例(原型式继承)
==题外话:原型式继承的本质:重写原型对象,代之为某个构造函数的实例==
var fNop = function(){}
fNop.prototype = this.prototype;
fBound.prototype = new fNop();
第四版代码如下:
Function.prototype.bind1 = function(context){
var self = this;
var args = Array.prototype.slice.call(arguments,1);
let fNop = function(){};
let fBound = function(){
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fBound ? this:context ,args.concat(bindArgs));
}
fNop.prototpye = this.prototype;
fBound.prototype = new fNop();
return fBound;
}
到这一步,bind基本就实现的差不多了,但有一个问题,调用bind的如果不是函数,这时需要抛出异常。
所以完整版模拟bind实现代码如下:
Function.prototype.bind1 = function(context){
if(typeof this !== 'function'){
throw new Error(this must be function);
}
var self = this;
var args = Array.prototype.slice.call(arguments,1);
let fNop = function(){};
let fBound = function(){
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fBound ? this:context ,args.concat(bindArgs));
}
fNop.prototpye = this.prototype;
fBound.prototype = new fNop();
return fBound;
}