经典面试题:手动实现call,apply,bind

304 阅读2分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

call,apply,bind三兄弟的作用类似都是可以改变函数执行的上下文,即改变函数运行时的this指向

区别与使用

传参方式

Function.call(obj,[param1[,param2[,…[,paramN]]]])
Function.apply(obj[,argArray])
Function.bind(thisArg[, arg1[, arg2[, ...]]])
  • 三个方法的第一个参数都是 this 指向的对象,差别在于后面的参数
  • call 的参数是直接传进去,用逗号分割
  • apply 后面的参数放在一个数组中作为第二个参数
  • bind 传参和 call 一样,不直接执行函数而是返回一个函数

使用案例

var obj = {
    name: 'Shane',
    age: 30,
    intro: function(city) {
        console.log('I am ' + this.name + ' ' + this.age + ' years old' + ' from ' + city)
    }
}
obj.intro('Shanghai') // I am Shane 30 years old from Shanghai
var ot = {
    name: 'Peter',
    age: 20
}
obj.intro.call(ot, 'Beijing') // I am Peter 20 years old from Beijing
obj.intro.apply(ot, ['Beijing']) // I am Peter 20 years old from Beijing
obj.intro.bind(ot, 'Beijing')() // I am Peter 20 years old from Beijing

手动实现

分析一波

  • call,apply,bind 方法第一个参数都函数执行的上下文,如果不传默认指向全局window,后面的参数传参的形式不同
  • 调用结果call,apply一样直接执行函数并返回结果,bind则返回一个函数
  • 由于函数都可以使用这三种方法,所有函数都是 Function 的实例,因此可以在 Function 的原型上直接进行修改或者扩充,即 Function.prototype
Function.prototype.myCall = function() {} // 手动实现call
  • Function.prototype 定义的方法中 this 指向实例,也就是说当声明一个函数时,Function.prototype 原型上的方法中的 this 都指向这个声明的函数
Function.prototype.myCall = function() {
    // 当fn调用myCall时,这里的this指向fn
}
var fn = function(name) {
    this.name = name
}
  • 我们知道,执行一个对象中的函数时,函数中的 this 指向这个对象。因此在手动实现时,在函数中传入第一个参数 ctx 就是上下文的对象,在对象里新增一个属性指向 this ,这样就将函数的作用域指向了 ctx 然后执行这个新增的属性将剩余参数传入,最后返回。
  • 看到这里可能有点懵圈,接下来看一下代码实现

call,apply

call apply类似只是传参方式不同

Function.prototype.myCall = function(ctx, ...args) {
    ctx = ctx || window // 执行上下文
    const symbol = Symbol() //使用symbol避免上下文中有重复属性名
    ctx[symbol] = this // 新增一个属性指向调用的函数
    const result = ctx[symbol](...args) // 储存调用结果
    delete ctx[symbol] // 执行完成删除属性
    return result // 返回执行的结果
}
Function.prototype.myApply = function(ctx, args) {
    ctx = ctx || window
    const symbol = Symbol()
    ctx[symbol] = this
    const result = ctx.fn(...args)
    delete ctx[symbol]
    return result
}

bind

因为返回的是一个函数,因此需要考虑如果是new一个实例的情况,需要继承原函数原型链上的方法

Function.prototype.myBind = function(ctx, ...args) {
    ctx = ctx || window
    ctx.fn = this
    return function newFn(...returnArgs) {
        // 是否实例化操作
        if(this instanceof newFn) {
            return new ctx.fn(...args, ...returnArgs)
        }
        return ctx.fn(...args, ...returnArgs)
    }
}