这三个函数是用于函数方法是用于改变this的绑定。this的指向一直是我们经常关注的问题,让我们先看看它的四个绑定原则。
this的四个绑定原则
1.默认绑定
在独立函数调用时,为全局,此时函数中的this指向Window,但当使用严格模式时,全局属性被保护,所以无法绑定,此时显示的是undefind,并抛出错误。
2.隐式绑定
当函数被调用是存在上下文时,此时函数的this会被绑定在上下文。
let obj={
show(){
console.log(this)
}
}
obj.show()
这种绑定方式存在隐式丢失问题
3.显式绑定
函数可以调用apply,call,bind函数实现对this的精确的绑定。
4.new绑定
在使用构造函数时,此时this被绑定在新构造的对象上。
上述的四条规则优先级从上到下依次增加。
apply,call,bind函数之间的差别
1.call方法:call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
2.apply方法实际的作用与call方法类似,而两者的区别是:call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
3.bind方法:bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的this被指定为 bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
手动实现call方法
Function.prototype.mycall=function(goal,...args){
let contex=goal||window
let fn=Symbol('fn')
contex.fn=this
let result=eval('contex.fn(...args)')
delete goal.fn
return result
}
我们在Function的prototype上创建我们的mycall方法,实现在函数对象可以直接调用我们的mycall函数。
函数接收两种参数,第一种即第一个参数,即需函数需要绑定的上下文.
第二种参数为函数实际需要的参数,这里参数数量不能固定,我们用...args进行接收,当然我们也能用ES5中的arguments在函数内部进行调用.
当然函数也存在不传入参数的情况,但对于我们的mycall方法来说context参数实际上是必须的,我们给context设置默认值window以确保我们的方法可以成功完成。
下一步,我们将我们的目标上下文上创建新的fn,将次方法的this赋值为我们新创建的context.fn,这里可能有些同学还是不能很快的反应出来这里的this指向哪里,这里实际上是符合this绑定四项原则的隐式绑定的规则,为调用这个方法的对象。
这里fn用了用了symbol数据类型,是为了让fn实现唯一性,避免了与context原型中其他的键产生冲突。
最后将我们创建的fn进行删除,避免对空间的浪费,最后返回需要的result便实现了我们自己的call方法了。
手动实现apply方法
之前我们提到了,call方法和apply方法的主要求别就在于call函数在第二种参数的传递中,可以传递多个参数,而apply方法只能传递一个数据类型的数据,所以apply方法的实现方式实际与call方法的实现方式相似。
Function.prototype.myapply=function(goal,args){
let context=goal||window
context.fn=this
let result=eval('context.fn(...args)')
delete context.fn
return result
}
手动实现bind方法
我们先来分析一下bind方法的输入和输出,MDN中对bind的介绍是这样的。
bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
总的来说bind有这样三个功能点:
1.改变原函数的 this 指向,即绑定上下文,返回原函数的拷贝。
2.当 绑定函数 被调用时,bind的额外参数将置于实参之前传递给被绑定的方法。
3.注意,一个 绑定函数 也能使用 new 操作符创建对象,这种行为就像把原函数当成构造器,thisArg 参数无效。也就是 new 操作符修改 this 指向的优先级更高。
这样已经明确,输入与call方法相似,分为两类参数,一类为绑定的上下文,一类为传递的实际参数。而返回值与之前的apply,call都不相同,返回值为一个函数,我们可以先实现一个类似的结构。且函数可能进行两次传参。
Function.prototype.myBind = function (context, ...args1) {
var Fn=function(){}
return Fn
}
我们来完成内部的逻辑:
Function.prototype.myBind=function(context,...args1){
var _self=this
var Fn=function(...args2){
return _self.apply(context,args1.concat(args2)
}
return Fn
}
这样实现了bind方法的一部分功能,测试代码如下:
function foo(name) {
this.name = name;
}
var obj = {}
//上下文 功能 done
var bar = foo.myBind(obj)
bar('jack')
console.log(obj.name) //'jack'
// 参数 功能 done
var tar = foo.myBind(obj, 'rose');
tar()
console.log(obj.name) //'rose'
// new 功能 error
var alice = new bar('alice')
console.log(obj.name) //alice obj name should be 'jack'
console.log(alice.name) //undefined, alice name should be 'alice'
可以看到在绑定构造函数时,仍然出现了问题,问题简单来看,就是在执行var alice = new bar('alice')时,this仍然指向到obj中,这也是我们之前代码中的逻辑,但在这里出现了问题。所以我们需要在方法内部进行判断到底时普通函数调用还是构造函数调用。
Function.prototype.myBind=function(context,...args1){
var _self=this
var Fn=function(...args2){
return _self.apply(this instanceof _self?this:contect,args1.concat(args2)
}
return Fn
}
这样看似解决了构造函数调用时的执行问题,但仍然还有不足,我们拷贝的函数没有对原函数进行继承,所以此时在原函数的原型链上的函数在返回的新构造函数上不能使用,我们要对返回的新函数对原函数实现继承。继承的方法有很多,我们这里用Object.creat()的方法实现,最后添加参数属性的判断,最终完成我们的bind方法。
Function.prototype.myBind=function(context,...args1){
if(typeof this!=='function'){
throw new Error(this+'not a function')
}
var _self=this
var Fn=function(...args2){
return _self.apply(this instanceof _self?this:contect,args1.concat(args2)
}
Fn,prototype=Object.creat(_self.prototype)
return Fn
}
再来看测试结果:
function foo(name) {
this.name = name;
}
var obj = {};
var bar = foo.myBind(obj);
bar('Jack');
console.log(obj.name); // Jack
var alice = new bar('Alice');
console.log(obj.name); // Jack
console.log(alice.name); // Alice