手动实现call、apply、bind, 逐步分析

174 阅读2分钟

写在前面

在面试中常被问到的一个问题:applycall有什么区别,能手动实现他们吗?

它们都是用来改变函数中 this 的指向的,区别只在于传参,apply传入一个数组,而call传入多个参数。

至于bind也是用来改变 this指向的,但是它不会立即执行,同时,它还会将所有传入bind()方法中的实参(第一个参数之后的参数)与this一起绑定。

1. 实现call

Funciton.prototype.myCall = function(context){
    // 先判断调用call的对象(this)是不是函数,如果不是,则抛出类型错误
    if(typeof this !== 'function'){
        throw new TypeError('not a function')    
    }
    
    // 当第一个参数,也就是新的上下文对象传入的 !!context === false 时,将上下文置为 window
    context = context || window
    
    /**
    * 定义参数,已知第一个参数是新的上下文对象,那么就需要将第一个参数剥离出去,  
    * 由于arguments是类数组,不能直接用数组原型上的slice方法操作,所以先要转换成数组。还可选:
    * var args = Array.prototype.slice.call(arguments, 1)
    * var args = Array.from(arguments).slice(1)
    * var args = [].shift.call(arguments)
    */
    var args = [...arguments].slice(1)
    
    /** 
    * 这一步很多人都忽略掉了,而在这里直接context.fn = this, 那么如果 context 上本来就有一个属性叫做 fn 呢?  
    * 明显会被覆盖掉,导致原来的属性丢失,这个时候可以用ES6中的Symbol解决,它一定是唯一的。
    */
    var fn = Symbol()
    
    // 将call的调用者 赋值给 新的上下文对象并(传入参数)执行,将执行结果赋值给result
    context[fn] = this
    var result = context[fn](...args)
    
    // 这个时候新的对象被新添了一个叫做Symbol() 的属性,要删掉它
    delete context[fn]
    
    // 返回执行结果
    return result
}

2. 实现apply

applycall的主要区别在于传参方式不同,所以实现方式大致相同, 不再逐行分析

Function.prototype.myApply = function(context){
    if(typeof this !== 'function'){
        throw new TypeError('not a function')    
    }
    context = context || window
    var fn = Symbol()
    context[fn] = this
    
    // 先定义result
    var result
    
    // 判断是否传入了另外的参数,若有,则传入执行;若无,则直接执行, 将执行结果赋值给result
    if(arguments[1]){
        result = context[fn](...arguments[1])
    }else{
        result = conetxt[fn]()
    }
    delete context[fn]
    return result
}

3.实现bind

bind是一个典型的 函数柯里化。查看函数柯里化相关,可以查看 www.zhangxinxu.com/wordpress/2…

Function.prototype.myBind = function(context){
    if(typeof this !== 'function'){
        throw new TypeError('not a function')    
    }
    context = context || window
    var args = Array.from(arguments).slice(1)
    
    // 这里要先将当前 this 赋值给 that,因为 function 中 this 的作用域决定于被谁调用,若直接在return的函数中调用,则this指向谁不确定
    var that = this
    
    // 返回call函数
    return function(F){
        // 判断是否被当做构造函数实例化
        if(this instanceof F){
            return that.apply(this, [...args, ...arguments])
        }
        return that.apply(context,[...args, ...arguments])
    }
}

以上就是这三个函数的实现以及个人理解了,欢迎指正。