手动实现 JavaScript 的 call、apply 和 bind 方法

1. 实现 call 方法

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

实现思路:

  1. 将函数设置为对象的属性
  2. 执行该函数
  3. 删除该属性(避免污染对象)

实现代码:

javascript 复制代码
Function.prototype.myCall = function(context, ...args) {
  // 如果context为null或undefined,则指向全局对象(浏览器中是window)
  context = context || window;
  
  // 创建一个唯一的属性名,避免覆盖原有属性
  const fnSymbol = Symbol('fn');
  
  // 将当前函数(this)赋值给context的fnSymbol属性
  context[fnSymbol] = this;
  
  // 执行函数
  const result = context[fnSymbol](...args);
  
  // 删除临时属性
  delete context[fnSymbol];
  
  return result;
};

// 测试用例
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: 'John' };
greet.myCall(person, 'Hello', '!'); // 输出: "Hello, John!"

2. 实现 apply 方法

apply() 方法与 call() 类似,只是参数以数组(或类数组对象)的形式传递。

实现代码:

javascript 复制代码
Function.prototype.myApply = function(context, argsArray) {
  // 如果context为null或undefined,则指向全局对象
  context = context || window;
  
  // 创建一个唯一的属性名
  const fnSymbol = Symbol('fn');
  
  // 将当前函数赋值给context的fnSymbol属性
  context[fnSymbol] = this;
  
  // 执行函数,处理argsArray为null或undefined的情况
  const result = argsArray ? context[fnSymbol](...argsArray) : context[fnSymbol]();
  
  // 删除临时属性
  delete context[fnSymbol];
  
  return result;
};

// 测试用例
function introduce(language, years) {
  console.log(`I'm ${this.name}, I code in ${language} for ${years} years.`);
}

const dev = { name: 'Alice' };
introduce.myApply(dev, ['JavaScript', 5]); // 输出: "I'm Alice, I code in JavaScript for 5 years."

3. 实现 bind 方法

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,其余参数将作为新函数的参数供调用时使用。

实现思路:

  1. 返回一个新函数
  2. 新函数执行时,使用指定的 this
  3. 支持柯里化(参数分次传递)

实现代码:

javascript 复制代码
Function.prototype.myBind = function(context, ...bindArgs) {
  const originalFunc = this;
  
  return function(...callArgs) {
    // 判断是否作为构造函数调用(使用new操作符)
    if (new.target) {
      return new originalFunc(...bindArgs, ...callArgs);
    }
    
    // 普通函数调用
    const fnSymbol = Symbol('fn');
    context = context || window;
    context[fnSymbol] = originalFunc;
    
    const result = context[fnSymbol](...bindArgs, ...callArgs);
    delete context[fnSymbol];
    
    return result;
  };
};

// 测试用例
function printInfo(role, department) {
  console.log(`${this.name} is ${role} in ${department}`);
}

const employee = { name: 'Bob' };
const boundFunc = printInfo.myBind(employee, 'developer');
boundFunc('IT'); // 输出: "Bob is developer in IT"

// 测试作为构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

const BoundPerson = Person.myBind(null, 'DefaultName');
const p = new BoundPerson(30);
console.log(p); // 输出: Person { name: 'DefaultName', age: 30 }

4. 完整实现与边界情况处理

完整实现(处理更多边界情况):

javascript 复制代码
Function.prototype.myFullBind = function(context, ...bindArgs) {
  if (typeof this !== 'function') {
    throw new TypeError('Bind must be called on a function');
  }
  
  const originalFunc = this;
  const boundFunc = function(...callArgs) {
    // 判断是否作为构造函数调用
    const isConstructorCall = new.target;
    
    return originalFunc.apply(
      isConstructorCall ? this : (context || globalThis),
      bindArgs.concat(callArgs)
    );
  };
  
  // 维护原型关系
  if (originalFunc.prototype) {
    boundFunc.prototype = Object.create(originalFunc.prototype);
  }
  
  return boundFunc;
};

边界情况说明:

  1. 构造函数调用 :当使用 new 操作符调用绑定函数时,this 应该指向新创建的对象,而不是绑定的 context
  2. 原型链维护:确保绑定后的函数能正确继承原函数的原型链
  3. 严格模式 :在严格模式下,contextnullundefined 时,this 不会自动转为全局对象
  4. 参数合并:正确处理绑定时参数和调用时参数的合并

5. 三种方法的对比总结

方法 调用方式 参数形式 立即执行 返回新函数
call func.call(ctx, arg1, arg2) 参数列表
apply func.apply(ctx, [arg1, arg2]) 数组形式参数
bind func.bind(ctx, arg1, arg2) 参数列表

6. 实际应用场景

call/apply 使用场景:

  1. 借用方法
javascript 复制代码
// 类数组对象使用数组方法
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
Array.prototype.push.myCall(arrayLike, 'c');
console.log(arrayLike); // { 0: 'a', 1: 'b', 2: 'c', length: 3 }
  1. 确定函数上下文
javascript 复制代码
function logThis() {
  console.log(this);
}
logThis.myCall({ id: 42 }); // 输出: { id: 42 }

bind 使用场景:

  1. 事件处理函数
javascript 复制代码
class Button {
  constructor() {
    this.text = 'Click me';
    this.handleClick = this.handleClick.myBind(this);
  }
  
  handleClick() {
    console.log(this.text);
  }
}

const btn = new Button();
document.querySelector('button').addEventListener('click', btn.handleClick);
  1. 参数预设(柯里化)
javascript 复制代码
function multiply(a, b) {
  return a * b;
}
const double = multiply.myBind(null, 2);
console.log(double(5)); // 10
相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax