实现 call、apply、bind

手写 JavaScript 的 call、apply 和 bind 方法

问题

从零实现 callapplybind 方法。

解答

实现 call

Function.prototype.myCall = function(context, ...args) {
  // 如果 context 为 null 或 undefined,指向全局对象
  context = context ?? globalThis;
  
  // 将原始值转换为对象
  context = Object(context);
  
  // 用 Symbol 避免属性名冲突
  const fn = Symbol('fn');
  
  // 将函数作为 context 的方法
  context[fn] = this;
  
  // 调用函数并获取结果
  const result = context[fn](...args);
  
  // 删除临时属性
  delete context[fn];
  
  return result;
};

实现 apply

Function.prototype.myApply = function(context, args = []) {
  context = context ?? globalThis;
  context = Object(context);
  
  const fn = Symbol('fn');
  context[fn] = this;
  
  // 与 call 的区别:参数是数组
  const result = context[fn](...args);
  
  delete context[fn];
  
  return result;
};

实现 bind

Function.prototype.myBind = function(context, ...args) {
  const self = this;
  
  const boundFn = function(...newArgs) {
    // 判断是否作为构造函数调用
    // 如果是 new 调用,this 指向实例,忽略绑定的 context
    return self.apply(
      this instanceof boundFn ? this : context,
      [...args, ...newArgs]
    );
  };
  
  // 维护原型链,让 new 出来的实例能访问原函数原型上的属性
  if (self.prototype) {
    boundFn.prototype = Object.create(self.prototype);
  }
  
  return boundFn;
};

测试代码

const obj = { name: 'Alice' };

function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
  return this.name;
}

// 测试 call
greet.myCall(obj, 'Hello', '!'); // Hello, Alice!

// 测试 apply
greet.myApply(obj, ['Hi', '?']); // Hi, Alice?

// 测试 bind
const boundGreet = greet.myBind(obj, 'Hey');
boundGreet('~'); // Hey, Alice~

// 测试 bind + new
function Person(name) {
  this.name = name;
}
const BoundPerson = Person.myBind({ name: 'ignored' });
const p = new BoundPerson('Bob');
console.log(p.name); // Bob(new 时忽略绑定的 context)

关键点

  • 使用 Symbol 作为临时属性名,避免覆盖 context 上的已有属性
  • context ?? globalThis 处理 null/undefined,Object(context) 处理原始值
  • callapply 的区别仅在于参数传递方式:逐个 vs 数组
  • bind 返回新函数,需要合并预设参数和调用时参数
  • bind 要处理 new 调用的情况:通过 instanceof 判断,是则忽略绑定的 context