【前端手撕】call

call是改变this指向的方法,传入的参数是要指向的对象和函数需要的参数序列。

改变this指向还可以用apply和bind,区别如下:

方法 传参方式 执行时机 返回值
call 参数序列(逐个传入) 立即执行 函数执行的结果
apply 参数数组(或类数组) 立即执行 函数执行的结果
bind 参数序列(逐个传入) 返回新函数,稍后执行 绑定了 this 的新函数

代码

简易版

javascript 复制代码
Function.prototype.callSimple = function (context, ...args) {
    context = context || window // 确认上下文,如果没有上下文就默认window
    context.fn = this // 把当前函数赋值给上下文的fn属性(临时属性)
    const res = context.fn(...args) // 调用函数,传参
    delete context.fn // 删除临时属性
    return res
}

健壮版

javascript 复制代码
Function.prototype.call = function (context, ...args) {
    // 如果上下文是null或undefined,就默认window;否则用Object()转换为对象
    // Object()包对象返回原对象,包原始类型返回对象包装类型。这是因为简单数据类型不能挂载属性,对象才可以
    context = context !== null && context !== undefined ? Object(context) : window
    let tag = Symbol('call') // Symbol()创建一个唯一的符号值,避免与其他属性冲突/覆盖
    context[tag] = this // 把当前函数赋值给上下文的tag属性(临时属性)
    const res = context[tag](...args) // 调用函数,传参。这里使用方括号是因为tag是一个符号值,不能用点号
    return res
}

Tips

  1. bind传参数可以先传一部分参数,返回新函数,下次再传剩下的。这种特性叫函数柯里化(Currying)

  2. 如果bind返回的新函数被new构造调用了,this会失效。因为new的优先级高于bind(但也只有new比bind高)

javascript 复制代码
function Person(name) {
  this.name = name;
}
const BoundPerson = Person.bind({ name: '默认' }); // 试图绑定 this

const p = new BoundPerson('李四');
console.log(p.name); // 输出:李四

因为new强行创建了一个新对象作为this,bind绑定的this被覆盖了。