最近的面试笔试都遇上了手动实现bind方法,而我只能实现简单的功能,并不了解细节,没有深入理解掌握原理,所以今天就整理了相关知识,彻底弄懂这些方法的原理和实现。
call、apply和bind方法是用来修改函数或方法的this指向的,下面在实现这些方法时,再顺便说一下他们的区别吧
实现call方法
函数调用call方法时,传入的第一个参数表示this的指向,如果不传参或传入undefined/null,则this指向window对象(严格模式this不能指向window,this赋值undefined),之后的参数则作为修改this指向的函数的实参。函数执行上下文中,一个函数的this指向,一般是调用该函数的对象,默认指向window,模拟call方法就是利用这个原理实现
Function.prototype.mockcall=function(obj,...args){
我看其他博客作者都写这几行代码,我觉得没必要使用,因为是添加在Function的原型上,
所以没有这几行代码,也会报一样的错误,如果有其他想法欢迎探讨
if(typeof this!=='function'){
throw new TypeError(`${this}.mockcall is not a function`)
}
//利用Symbol使方法名唯一,不与对象上的已有的属性产生冲突
//obj可能不为对象,通过Object()转化成对象
var fn=Symbol(),obj=Object(obj||window),result
//this表示调用call的方法,将方法添加到obj对象上
obj[fn]=this
//通过obj对象调用该方法,方法内的this则指向obj
result=obj[fn](...args)
//还原原对象
delete obj[fn]
return result
}
问题:如果要指向的对象是冻结对象,无法添加方法,会报错
实现apply
与call方法原理一致,区别在于只有前两个参数有效,且第二个参数必须为数组形式,作为修改this指向的方法的实参
Function.prototype.mockapply=function(obj,arr){
if(arr&&!Array.isArray(arr)){
throw new TypeError('CreateListFromArrayLike called on non-object')
}
var fn=Symbol(),obj=Object(obj||window),result
//如果未传参,默认为空数组
arr=arr||[]
obj[fn]=this
result=obj[fn](...arr)
delete obj[fn]
return result
}
实现bind
bind的特点:
1、不会立即执行,返回一个修改了this指向的函数
2、使用new操作符,this指向新对象且新对象的原型均指向原函数与返回函数的原型
3、使用bind后,再使用call或者apply,this指向不改变
4、调用bind后,仍可以给返回的函数传递参数
Function.prototype.mockbind=function(obj,...args){
var fn = this,
newFn=function(){},
bindF = function(...args1){
//实现特点4
fn.call(this instanceof bindF?this:obj,...args,args1)
}
/*实现特点2:因为新对象一定是bindF的实例,若要继承fn,fn的原型必须在this的隐式
原型链上,而不使用bindF.prototype=fn.prototype是为了不内存共享,避免操作bindF
原型时影响fn的原型,所以设置bindF继承fn
*/
//错误:bindF.prototype=new fn()
//更改:利用空函数,空函数与fn原型指向同一对象,继承空函数
//等同于继承fn,且不会执行fn
newFn.prototype = fn.prototype
bindF.prototype = new newFn()
//实现特点1
return bindF
}
问题:调用mockbind后调用call或apply(例如:fun.bind(obj1).call(obj2) ),this指向不会再改变,因为mockbind返回的函数只有用到new操作符时重新修改this。所以特点3是实现了,但是现在的问题是,如果call或者apply传一个bindF的实例对象,那么mockbind的this仍会修改
更新:纠个错,在实现bindF继承fn上我犯了个错,我原来是直接bindF.prototype=new fn(),虽然这样可以完成继承且不会影响原函数的原型,但是这有个问题,就是在继承时,new操作符会执行原函数,显然不是我们想要的,而且容易报错(比如:如果这样写,在事件的回调函数使用mockbind时,就会执行回调函数了,但是获取不到事件对象,因为事件并未触发)
参考: