模拟实现call、apply和bind

164 阅读3分钟

最近的面试笔试都遇上了手动实现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时,就会执行回调函数了,但是获取不到事件对象,因为事件并未触发)

参考:

1、blog.csdn.net/qq_22654379…

2、mp.weixin.qq.com/s?src=11&ti…