手写 call 与 原生 Function.prototype.call 的区别

512 阅读1分钟

根据 Function.prototype.call() - JavaScript | MDN 的定义

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

前言

此篇文章仅讨论 更改普通函数内部 this 的引用,为了简化问题 不区分 严格模式/null/undefined

默认目标类型就是 object,不讨论其他类型

更改函数this的几种方法

函数直接赋值到目标对象

Function.prototype.call2 = function (context, ...args) {
  let uniqueKey = Symbol("全局唯一引用");
  context[uniqueKey] = this;
  let result = context[uniqueKey](...args);
  delete context[uniqueKey];
  return result;
};

缺点:如果对象被 冻结 或者 是 Proxy,函数会赋值失败,而且破坏了原来对象的结构

function test(){
  return this.a+this.b
}
ob1 = {a:1,b:2}

调用例子

// 例子1
Object.seal(ob1)
test.call2(ob1)
// 例子2
Object.freeze(ob1)
test.call2(ob1)
// 例子3
ob1 = new Proxy(ob1, {
  set(target, key, value) {}
});
test.call2(ob1)

都会得到如下错误:

VM86:4 Uncaught TypeError: context[uniqueKey] is not a function
    at Function.call2 (<anonymous>:4:34)
    at <anonymous>:1:6

借助原型链更改this

Function.prototype.call3 = function (context, ...args) {
  const obj = Object.create(context);
  let uniqueKey = Symbol("全局唯一引用");
  obj[uniqueKey] = this;
  return context[uniqueKey](...args);
};

此方法可以解决对象被冻结/属性拦截的问题 ,但是调用对象已经不是原来的对象

如果有对原型的操作,会发生未期望行为

总结

Function.prototype.call 是 native code

纯 js 无法实现 完整的 Function.prototype.call