JavaScript之bind模拟实现
一句话介绍bind:
bind() 方法会创建一个新函数,当这个新函数被调用时,bind()的第一个参数将作为他运行时的this,之后的一序列参数将会在传递的实参前传入作为他的参数
由此可以看出两个特点:
-
返回一个函数
-
可以传入参数
返回函数的模拟实现
第一个特点,我们举个例子(返回一个函数)
var obj = {
value : 1
}
function foo(name,age){
console.log(name)
console.log(age)
console.log(this.value)
}
var foo1 = foo.bind(obj,'hy')
foo1(18)// hy 18 1
模拟实现第一步
Function.prototype.bind2 = function(context){
var that = this
return function(){
return that.apply(context)
}
}
之所以 returnthat.apply(context) ,是考虑到绑定函数可能有返回值。
var obj = {
value : 1
}
function foo(){
return this.value
}
var foo1 = foo.bind(obj)
console.log(foo1()). // 1
传参的模拟实现
我们举个例子(可以传参)
var obj = {
value : 1
}
function foo(name,age){
console.log(this.value)
console.log(name)
console.log(age)
}
var Foobind = foo.bind(obj,'hy')
Foobind(18)
函数需要传name和age两个参数,竟然还可以在bind的时候,只传一个name,在执行返回的函数的时候,在传另一个参数age
模拟实现第二步
Function.prototype.bind2 = function(context){
var that = this
// 获取bind2函数从第二个参数到最后一个参数
var args = Array.prototype.slice.call(arguments,1)
return function(){
// 这个时候的 arguments 是指bind返回的函数传入的参数
var bindArgs = Array.prototype.slice.call(arguments)
return that.apply(context,args.concat(bindArgs))
}
}
构造函数效果的模拟实现
最难的部分到了,因为bind还有一个特点,就是:
-
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的this值被忽略,同时调用时的参数被提供给模拟函数
也就是说当bind返回的函数作为构造函数的时候,bind时指定的this值会失效,但传入的值依然有效
var value = 2
var obj = {
value : 1
}
function foo(name,age){
this.name = 'ycl'
console.log(this.value)// undefined
console.log(name)// hy
console.log(age)// 18
}
foo.prototype.flag = '1-'
var bindFoo = foo.bind(obj,'hy')
var bindFoo1 = new bindFoo(18)
console.log(bindFoo1.name)// ycl
console.log(bindFoo1.flag)// 1-
注意 :尽管在全局和obj 都声明了value值,但最后返回了undefined,说明绑定this失效了。
所以我们可以通过修改返回的函数的原型来实现
模拟实现第三步
Function.prototype.bind2=function(context){
var that = this
var args = Array.prototype.slice.call(arguments,1)
var fBound = function(){
var bindArgs = Array.prototype.slice.call(arguments)
// 当作为构造函数时,this 指向实例,此时结果为true,将绑定函数的this指向该实例,可以让实例获得来自绑定函数的值
// 当作为普通函数时,this 指向window,此时结果为false,将绑定函数的this指向context
return that.apply(this instanceof fBound ? this : context , args.concat(bindArgs))
}
// 修改返回函数的 prototype 为绑定函数的prototype,实例就可以继承绑定函数的原型的值 (此例子的绑定函数为foo)
fBound.prototype = this.prototype
return fBound
}
构造函数效果的优化实现
但是在这个写法中,我们直接将
fBound
.
prototype
=
this
.
prototype ,我们直接修改
fBound
.
prototype的时候,也会直接修改绑定函数的prototype。这个时候我们通过一个空函数进行中转。
模拟实现第四步
Function.prototype.bind2 = function(context){
var that = this
var args = Array.prototype.slice.call(arguments , 1)
var fBound = function(){
var bindArgs = Array.prototype.slice.call(arguments)
var fNOP = function(){}
// 当作为构造函数时,this 指向实例,此时结果为true,将绑定函数的this指向该实例,可以让实例获得来自绑定函数的值
// 当作为普通函数时,this 指向window,此时结果为false,将绑定函数的this指向context
return that.apply(this instanceof fNOP ? this : context , args.concat(bindArgs))
}
// 修改返回函数的 prototype 为绑定函数的prototype,实例就可以继承绑定函数的原型的值 (此例子的绑定函数为foo)
fNOP.prototype = this.prototype
fBound.prototype = new fNOP()
return fBound
}
如果调用bind的不是函数咋办?
if(typeof this !== 'function'){
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
最后完成版本
Function.prototype.bind2 = function(context){
if(typeof this !== 'function'){
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var that = this
var args = Array.prototype.slice.call(arguments , 1)
var fBound = function(){
var bindArgs = Array.prototype.slice.call(arguments)
var fNOP = function(){}
// 当作为构造函数时,this 指向实例,此时结果为true,将绑定函数的this指向该实例,可以让实例获得来自绑定函数的值
// 当作为普通函数时,this 指向window,此时结果为false,将绑定函数的this指向context
return that.apply(this instanceof fNOP ? this : context , args.concat(bindArgs))
}
// 修改返回函数的 prototype 为绑定函数的prototype,实例就可以继承绑定函数的原型的值 (此例子的绑定函数为foo)
fNOP.prototype = this.prototype
fBound.prototype = new fNOP()
return fBound
}