【JavaScript】08. 手写call | apply | bind

195 阅读4分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

手写call |apply | bind

01. call

  • call(thisArg, arg1, arg2)

    • 第一个参数为需要改变的this指向
    • 第二个参数接收一个参数列表
    let myCall = function(context){
        // 1. 获取需要改变的 this 指向
        // 如果有,则为指定的对象,如果没有,则为window
        // (当然,这里并不严谨)
        context = context || window
        
        // 2. 获取目标函数
        // 这里的目标函数就是this
        // 因为 myCall 是作为一个方法被调用的,例如 [].slice.myCall()
        // 因此,在函数内部的 this 当然指向调用对象,而这个对象就是目标函数 f slice()
        // 所以我们 让目标函数成为 context 对象的一个成员方法
        // (这里也不严谨)
        context.fn = this
        
        // 3. 获取参数
        let args = [...arguments].slice(1)
        
        // 4. 让改变 this指向的对象 即context 调用这个函数
        let result = context.fn(...args)
        
        // 5. 删除这个函数
        delete context.fn
        
        // 6. 返回结果
        return result
    }
    

    对于context = context || window

    • 值为 nullundefined 的 this 值会自动指向全局对象(浏览器中为window)

    • 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象

    • 即,Object(context)

    if (context === null || context === undefined) {
        context = window 
    } else {
        context = Object(context)
    }
    
    

    对于context.fn = this

    • fn可能跟上下文对象的原属性冲突
    • 可以使用Symbol来避免这个冲突
    const fn = Symbol('myCall')
    context[fn] = this;
    
    

02. apply

  • apply(thisArg, [arg1,arg2])

    • 第一个参数为需要改变的this指向

    • 第二个参数接收一个参数数组(apply接收一个数组Array,两者同为A开头)

      实现方式同call,只是参数形式不一样

    let myApply = function(context){
        let context = context || window
        context.fn = this
        let result
        // 判断是否第二个参数——第二个参数是数组
        if(arguments[1]){
            result = context.fn(...arguments[1])
        }else {
            result = context.fn()
        }
        delete context.fn
        return result
    }
    

    注:有许多细节并没有去关注,这里只给出了一个简版的apply实现,解决方式同上


03. bind

  • bind(thisArg)

    • 第一个参数为需要改变的this
    • 该方法会直接返回一个函数
  • 简单版的

    Function.prototype.myBind = function (context) {
        // 先判断 this 是不是函数
        if (typeof this != 'function') {
            throw new TypeError('Error')
        }
        
        // 把 this 存起来
        let that = this
        
        // 把参数存起来
        let args = Array.prototype.slice.call([...arguments])
        
        // 定义一个函数,后面需要返回的
        let returnFunction = function () {
            // 返回的这个函数里面是改变 this 之后的函数
            // 上面的 context 就是改变了 this 之后的调用函数
            // 还要把参数全部传入
            return that.apply(context, [...args, ...arguments])
        }
        
        // 返回这个函数
        return returnFunction
    }
    
  • 具体一点

    bind函数返回的函数,还可以是一个构造函数

    所以这里要做一个判断,因为构造函数的this不被任何方式修改,它始终指向其创建的实例对象

    Function.prototype.myBind = function (context) {
        // 先判断 this 是不是函数
        if (typeof this != 'function') {
            throw new TypeError('Error')
        }
        
        // 把 this 存起来
        let that = this
        
        // 把参数存起来
        let args = Array.prototype.slice.call([...arguments])
        
        // 定义一个函数,后面需要返回的
        let returnFunction = function () {
            // 当作为构造函数时,this 指向实例,即不需要改变 this 指向
            // 当作为普通函数时,this 指向 context
            // 注:这里的 this 是调用它的this,不是外层myBind的 this
            if(this instanceof returnFunction){
                // 这里判断 this 是不是 returnFunction 的实例
                return that.apply(this, [...args, ...arguments])
            }else {
                return that.apply(context, [...args, ...arguments])
            }
        }
        
        // 然后修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
        returnFunction.prototype = this.prototype
        
        // 返回这个函数
        return returnFunction
    }
    
    
  • 优化后:

    Function.prototype.myBind = function (context) {
        // 先判断 this 是不是函数
        if (typeof this != 'function') {
            throw new Error("被绑定的不是函数")
        }
        
        // 把 this 存起来
        let that = this
        
        // 把参数存起来
        let args = Array.prototype.slice.call([...arguments])
        
        // 定义一个函数,后面需要返回的
        let returnFunction = function () {
            // 当作为构造函数时,this 指向实例,即不需要改变 this 指向
            // 当作为普通函数时,this 指向 context
            // 注:这里的 this 是调用它的this,不是外层myBind的 this
            return  that.apply(
                this instanceof returnFunction ? this : context,
                [...args, ...arguments]
            )
        }
        
        // 然后修改返回函数的 prototype 为绑定函数的实例的 prototype
        returnFunction.prototype =  Object.create(this.prototype)
        
        // 返回这个函数
        return returnFunction
    }
    
    

    当然,这里可能还是有些细节没有考虑到...,望谅解


本人前端小菜鸡,如有不对请谅解+