手动实现 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
相关推荐
小小小小宇1 小时前
虚拟列表兼容老DOM操作
前端
悦悦子a啊1 小时前
Python之--基本知识
开发语言·前端·python
安全系统学习2 小时前
系统安全之大模型案例分析
前端·安全·web安全·网络安全·xss
涛哥码咖2 小时前
chrome安装AXURE插件后无效
前端·chrome·axure
OEC小胖胖3 小时前
告别 undefined is not a function:TypeScript 前端开发优势与实践指南
前端·javascript·typescript·web
行云&流水3 小时前
Vue3 Lifecycle Hooks
前端·javascript·vue.js
Sally璐璐3 小时前
零基础学HTML和CSS:网页设计入门
前端·css
老虎06273 小时前
JavaWeb(苍穹外卖)--学习笔记04(前端:HTML,CSS,JavaScript)
前端·javascript·css·笔记·学习·html
三水气象台3 小时前
用户中心Vue3网页开发(1.0版)
javascript·css·vue.js·typescript·前端框架·html·anti-design-vue
灿灿121383 小时前
CSS 文字浮雕效果:巧用 text-shadow 实现 3D 立体文字
前端·css