bind的模拟实现
2019-06-15 本文已影响0人
小泡_08f5
bind() 方法会创建一个新函数。 当这个函数被调用时, bind()的第一个参数将作为它运行时的this, 之后的一序列参数将会在传递的实参前传入作为它的参数
由此得出bind函数的两个特点:
- 返回一个函数
- 可以传入参数
举个例子:
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
// 返回了一个函数
var bindFoo = bar.bind(foo);
bindFoo(); // 1
以上返回了一个函数,同时也指定了this的指向,可以用call和apply来实现
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
Function.prototype.bind2 = function(context){
// console.log(this);
var self = this;
return function(){
return self.call(context);
}
}
bar.bind2(foo)();
这里之所以 return self.call(context); 是考虑到绑定函数可能是有返回值的,
var foo = {
value: 1
};
function bar() {
return this.value;
}
var bindFoo = bar.bind(foo);
console.log(bindFoo()); // 1
传参的模拟实现
var foo = {
value: 1
};
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind(foo, 'daisy');
bindFoo('18');
// 1
// daisy
// 18
说明在bind的时候可以传参,在执行bind返回函数的时候,还可以传参。
思路:第一次绑定返回的是一个函数,接收的参数用arguments 正常接收,可以用Array.prototype.slice.call(arguments,1) 接收从第二个参数截取。也可以用for循环遍历arguments获取
第二次运行bind返回的函数传参, 因为返回的是一个函数,也可以利用arguments来接收全部参数,Array.prototype.slice.call(arguments,0),然后把第一次传入的参数concat () 连接起来一并传入。
来试试
var foo = {
value: 1
};
function bar(name,age) {
console.log(this.value);
console.log(name);
console.log(age);
}
Function.prototype.bind2 = function(context){
// console.log(arguments);
var self = this;
var args = Array.prototype.slice.call(arguments,1);
return function(){
var args1 = Array.prototype.slice.call(arguments,0);
console.log(args.concat(args1));
var args2 = args.concat(args1);
return self.apply(context,args2);
}
}
var bindFoo = bar.bind2(foo,'Ailse');
bindFoo('18');
image.png
构造函数效果的模拟实现
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的this值被忽略,同时调用时的参数被提供给模拟函数
bind的另外一个特点
也就是说当bind返回的函数作为构造函数的时候,bind时指定的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, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
尽管在全局和foo中都声明了value值, 最后依然返回了undefined, 说明绑定的this失效了,此时的this已经指向了obj
// 第四步 如果一个构造函数使用new操作符创建对象
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';
Function.prototype.bind2 = function(context){
var self = this;
var args = Array.prototype.slice.call(arguments,1);
var fBound = function(){ // 因为要return回去,我们这里用函数表达式的形式定义函数。
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
// 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
console.log(this instanceof fBound);
var args1 = Array.prototype.slice.call(arguments,0);
var args2 = args.concat(args1);
if(this instanceof fBound){ // 说明obj是'构造函数'fBound new出来的实例, 用instanceof可判断一个对象是否是构造函数的实例
return self.apply(this,args2); // 将绑定函数的this指向该实例对象obj,可以让实例获得来自绑定函数的值
}else{
return self.apply(context,args2)
}
}
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
fBound.prototype = this.prototype; // 因为fBound是新创建的函数,new出来的实例的原型指向的是fBound的原型对象,让'构造函数'fBound的原型指向最初bar函数的原型,则fBound new出来的实例继承了bar
return fBound;
}
var bindFoo = bar.bind2(foo,'Ailse');
var obj = new bindFoo('18');
console.log(obj.habit); // shopping
console.log(obj.friend); // Ailse //18
// 第五步 ,
// 在第四步中,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转:
// 如果是修改空函数的prototype, 其实也会修改绑定函数的 prototype 吧, 那为什么还是要用一个空函数中转???
// 空函数不习惯被操作?
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';
Function.prototype.bind2 = function(context){
var self = this;
var args = Array.prototype.slice.call(arguments,1);
var FNOP = function(){};
var fBound = function(){
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
// 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
console.log(this instanceof fBound);
var args1 = Array.prototype.slice.call(arguments,0);
var args2 = args.concat(args1);
if(this instanceof fBound){ // 说明obj是'构造函数'fBound new出来的实例, 用instanceof可判断一个对象是否是构造函数的实例
return self.apply(this,args2); // 将绑定函数的this指向该实例对象obj,可以让实例获得来自绑定函数的值
}else{
return self.apply(context,args2)
}
}
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
// fBound.prototype = self.prototype; // 因为fBound是新创建的函数,new出来的实例的原型指向的是fBound的原型对象,让'构造函数'fBound的原型指向最初bar函数的原型,则fBound new出来的实例继承了bar
// fBound.prototype.friend = '123'
FNOP.prototype = self.prototype;
fBound.prototype = new FNOP();
// FNOP.prototype.friend = '6666'
return fBound;
}
var bindFoo = bar.bind2(foo,'Ailse');
var obj = new bindFoo('18');
console.log(obj.habit); // shopping
console.log(obj.friend); // Ailse //18