bind 的使用
在实现bind方法之前,首先了解完整的bind 方法是怎么工作的。
bind : 创建一个新的函数,在bind被调用时,这个新函数的this被指定bind的第一个参数而其余的参数将作为新函数的参数
var food = {
value: 1,
name: "banana",
}
function bar () {
console.log('this@', this);
console.log('vale,name:@', this.value, this.name);
}
// 用bind创建一个新的函数
var bindFood = bar.bind(food)
bindFood()
//ouput:
//this@ {value: 1, name: 'banana'}
//vale,name:@ 1 banana
实现步骤分析
版本1
1.获取调用bind 的上下文this
2.考虑到调用bind的函数可能有返回值,加上return
Function.prototype.myBind = function(context) {
// 1.获取调用函数
var self = this
// 2.调用myBind函数可能有返回值
return function() {
// 3.将调用新函数的this,指向对应的参数
return self.apply(context)
}
}
测试 : mybind test
var food = {
value: 1,
name: 'banana',
};
function bar (name, age) {
console.log('this.value@', this.value);
console.log('name@', name);
console.log('age@', age);
}
var bindFood = bar.myBind(food, '大明')
bindFood('23')
//ouput:
//this.value@ 1
//name@ undefined
//age@ undefined
通过对myBind测试得出,传进去的参数没得到过滤(处理),所有无值返回
版本2
为了解决版本1的问题,提出解决方法:
1.对传入参数 arguments 进行过滤
2.生成新函数被调用时,并传入参数时,也要进行过滤
Function.prototype.myBind2 = function(context) {
// 1.获取调用myBind2的函数
var self = this
// 2.过滤调用myBind2时传入的参数
var args = Array.prototype.slice.call(arguments, 1)
// 3.调用myBind2的函数可能有返回值
return function() {
// 4.第二次过滤,对生成新函数被调用时,并传入的参数将进行过滤
var bindArgs = Array.prototype.slice.call(arguments)
// 5.合并第一,第二次过滤的arguments ,并通过apply改变this指向(让生成的新函数也可以调用传入参数的属性)
return self.apply(context, args.concat(bindArgs))
}
}
测试:mtBind2 test
var food2 = {
value: 1,
name: 'banana',
};
function bar2 (name, age) {
console.log('this.value@', this.value);
console.log('name@', name);
console.log('age@', age);
}
var bindFood2 = bar2.myBind2(food2, '大明')
bindFood2('23') // 1 ,大明,23
//output:
//this.value@ 1
//name@ 大明
//age@ 23
虽然版本2的问题已得到解决,但当把bindFood2当成原函数, 通过 new 构造一个函数时,this的指向会出现失效问题。
例子:
bar2.prototype.color = 'RED'
var bindFood2 = bar2.bind(food2, '大明')
const obj = new bindFood2('18') // undefined , 大明 ,18
console.log('new构造的新函数@', obj.color); //undefined
通过例子不难发现, bar2.prototype.color 赋值后,new 构造并读取 color 属性时为undefined,说明使用new构造的新没有被返回,为了解决这个问题看下个版本。
版本3
解决版本2问题的方式:
判断是否有对myBind2生成的新函数使用 new ,有需要将实例赋值给返回函数,没有侧返回原实例。
Function.prototype.myBind3 = function() {
// 1.获取调用myBind3的函数
var self = this
// 2.过滤第一次,arguments参数
var args = Array.prototype.slice.call(arguments, 1)
// 3.返回函数对this过滤
var fBound = function() {
// 4.第二次过滤,arguments参数(新函数调用时转递的参数)
var bindArgs = Array.prototype.slice.call(arguments)
// 5.对myBind3生成的新函数,使用 new 构造函数时,this已经指向调用myBind3的实例;
// 所有:使用new构造一个myBind3生成的函数时,要将调用myBind3的实例this赋值给返回函数
// 是否使用new构造 ? 是将this给它 : 不是侧将原上下文context给它(当作为普通函数时,this 指向 window)
return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs))
}
// 6.修改返回函数的原型 prototype ,将获取到的this赋值给新返回函数的原型,使实例可以继承原函数中的属性
fBound.prototype = this.prototype
// 7.返回最终函数
return fBound
}
测试:mtBind3 test
var food3 = {
value: 3,
name: 'banana',
};
function bar3 (name, age) {
console.log('this.value@', this.value);
console.log('name@', name);
console.log('age@', age);
}
bar3.prototype.color = 'GREEN'
var bindFood3 = bar3.myBind3(food3, '果果')
const obj3 = new bindFood3('23')
console.log('new构造的新函数@', obj3.color);
//ouput:
//this.value@ undefined
//name@ 果果
//age@ 23
//new构造的新函数@ GREEN
通过3版本的优化,解决了版本二使用new 无效的问题。 但是通过测试结果来看,this.value 返回的是 undefined 这是什么原因呢?为了解决这个问题看下个版本
版本4
经过分析发现: 直接将 fBound.prototype = this.prototype 在修改 fBound.prototype 时可能会修改到绑定函数的 prototype
解决方式:通过第三个函数将,实例转接到新的函数
Function.prototype.myBind4 = function(context) {
// 1.获取调用myBind4的实例
var self = this
// 2.过滤调用myBind4时传进来的参数
var args = Array.prototype.slice.call(arguments, 1)
// 4.定义转接this的第三者函数
var fNOP = function() { }
// 3.定义返回的新函数
var fBound = function() {
// 3.1 过滤调用新函数时传进来的参数
var bindArgs = Array.prototype.slice.call(arguments)
// 3.2 判断是否对新函数使用 new 构造
return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs))
}
// 5.将实例原型赋值给第三者函数原型
fNOP.prototype = this.prototype
// 6.通关new将第三者得到的原型,赋值给新函数
fBound.prototype = new fNOP()
return fBound
}
测试:mtBind4 test
var food4 = {
value: 4,
name: 'banana',
};
function bar4 (name, age) {
console.log('this.value@', this.value);
console.log('name@', name);
console.log('age@', age);
}
bar4.prototype.color = 'yellow'
// var bindFood4 = bar4.bind(food4, '东东')
// bindFood4('23')
//ouput:
//this.value@ 4
//name@ 东东
//age@ 23
var bindFood4 = bar4.myBind4(food4, '东东')
bindFood5('23')
//ouput:
//this.value@ 4
//name@ 东东
//age@ 23
//var bindFood4 = bar4.myBind4(food4, '东东')
// const obj4 = new bindFood5('23')
// console.log('new构造的新函数@', obj4.color);
//ouput:
//this.value@ undefined
//name@ 东东
//age@ 23
//new构造的新函数@ yellow
经过测试,原生的bind 和myBind4 对比以实现相似功能效果。
最终简洁版
Function.prototype.myBindFN = function(context) {
var self = this
var args = Array.prototype.slice.call(arguments, 1)
var fNOP = function() { }
var fBound = function() {
var bindArgs = Array.prototype.slice.call(arguments)
return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs))
}
fNOP.prototype = this.prototype
fBound.prototype = new fNOP()
return fBound
}
总结
本文主要通过对bind使用功能分析的方式,一步步实现手写bind时可能遇到的问题。