改变上下文的 API:call, apply, bind

📚 基础区别示例

javascript 复制代码
// ====================
// 1. 基础用法对比
// ====================
const person = {
  name: 'Alice',
  age: 25
};

function introduce(greeting, punctuation) {
  console.log(`${greeting}, 我是 ${this.name}, 今年 ${this.age} 岁${punctuation}`);
}

// call: 立即执行,参数逐个传递
introduce.call(person, '你好', '!');
// 输出: 你好, 我是 Alice, 今年 25 岁!

// apply: 立即执行,参数以数组形式传递
introduce.apply(person, ['大家好', '。']);
// 输出: 大家好, 我是 Alice, 今年 25 岁。

// bind: 返回新函数,不立即执行,参数逐个传递
const boundIntroduce = introduce.bind(person, 'Hi', '~');
boundIntroduce(); // 需要手动调用
// 输出: Hi, 我是 Alice, 今年 25 岁~

🔥 深度使用场景

1. 找出数组中的最大/最小值

javascript 复制代码
// apply 的经典应用场景
const numbers = [5, 6, 2, 3, 7, 1, 9];

// Math.max 不接受数组,但可以接受多个参数
const max = Math.max.apply(null, numbers);
console.log('最大值:', max); // 9

const min = Math.min.apply(null, numbers);
console.log('最小值:', min); // 1

// ES6 更优雅的写法(但理解 apply 仍然重要)
const maxES6 = Math.max(...numbers);

2. 数组合并与追加

javascript 复制代码
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];

// 使用 apply 将 array2 的元素追加到 array1
Array.prototype.push.apply(array1, array2);
console.log(array1); // [1, 2, 3, 4, 5, 6]

// 或者
const array3 = [7, 8];
const array4 = [9, 10];
[].push.apply(array3, array4);
console.log(array3); // [7, 8, 9, 10]

3. 类数组转换为真数组

javascript 复制代码
function testArguments() {
  console.log(arguments); // 类数组对象
  
  // 使用 call 将类数组转换为真数组
  const argsArray = Array.prototype.slice.call(arguments);
  console.log(Array.isArray(argsArray)); // true
  
  // 或者使用 apply
  const argsArray2 = [].slice.apply(arguments);
  console.log(argsArray2);
}

testArguments('a', 'b', 'c');

// 实际应用:NodeList 转数组
// const divs = document.querySelectorAll('div');
// const divsArray = Array.prototype.slice.call(divs);

4. bind 的柯里化(预设参数)

javascript 复制代码
// bind 可以预设参数,实现函数柯里化
function multiply(a, b, c) {
  return a * b * c;
}

// 预设第一个参数为 2
const double = multiply.bind(null, 2);
console.log(double(3, 4)); // 2 * 3 * 4 = 24

// 预设前两个参数
const multiplyBy6 = multiply.bind(null, 2, 3);
console.log(multiplyBy6(5)); // 2 * 3 * 5 = 30

// 实际应用:创建特定功能的函数
function greet(greeting, name, age) {
  return `${greeting}, ${name}! 你 ${age} 岁了。`;
}

const sayHello = greet.bind(null, 'Hello');
console.log(sayHello('Bob', 30)); // Hello, Bob! 你 30 岁了。

const sayHelloToBob = greet.bind(null, 'Hello', 'Bob');
console.log(sayHelloToBob(30)); // Hello, Bob! 你 30 岁了。

5. this 绑定在事件处理中的应用

javascript 复制代码
class Button {
  constructor(text) {
    this.text = text;
    this.clickCount = 0;
  }
  
  // 问题:直接传递方法会丢失 this
  handleClickWrong() {
    this.clickCount++;
    console.log(`${this.text} 被点击了 ${this.clickCount} 次`);
  }
  
  // 解决方案1:使用 bind
  setupButton1(element) {
    element.addEventListener('click', this.handleClickWrong.bind(this));
  }
  
  // 解决方案2:箭头函数(现代推荐方式)
  handleClickRight = () => {
    this.clickCount++;
    console.log(`${this.text} 被点击了 ${this.clickCount} 次`);
  }
}

// 使用示例
const btn = new Button('提交按钮');
// btn.setupButton1(document.querySelector('#myButton'));

6. 借用其他对象的方法

javascript 复制代码
// 借用 Array 的方法
const arrayLikeObject = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

// 借用 Array.prototype.join
const joined = Array.prototype.join.call(arrayLikeObject, '-');
console.log(joined); // "a-b-c"

// 借用 Array.prototype.map
const mapped = Array.prototype.map.call(arrayLikeObject, item => item.toUpperCase());
console.log(mapped); // ["A", "B", "C"]

// 实际应用:检测数据类型
function getType(obj) {
  return Object.prototype.toString.call(obj).slice(8, -1);
}

console.log(getType([])); // "Array"
console.log(getType({})); // "Object"
console.log(getType(new Date())); // "Date"
console.log(getType(null)); // "Null"
console.log(getType(undefined)); // "Undefined"
console.log(getType(/regex/)); // "RegExp"

7. 继承中的应用

javascript 复制代码
// 构造函数继承
function Animal(name, age) {
  this.name = name;
  this.age = age;
}

Animal.prototype.sayName = function() {
  console.log(`我是 ${this.name}`);
};

function Dog(name, age, breed) {
  // 调用父类构造函数,继承属性
  Animal.call(this, name, age);
  this.breed = breed;
}

// 继承原型方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
  console.log(`${this.name} 汪汪叫!`);
};

const dog = new Dog('旺财', 3, '金毛');
console.log(dog.name); // 旺财
console.log(dog.age); // 3
console.log(dog.breed); // 金毛
dog.sayName(); // 我是 旺财
dog.bark(); // 旺财 汪汪叫!

8. 多次 bind 的行为

javascript 复制代码
function showName() {
  console.log(this.name);
}

const obj1 = { name: 'Object 1' };
const obj2 = { name: 'Object 2' };
const obj3 = { name: 'Object 3' };

// 多次 bind,只有第一次生效
const boundFn = showName.bind(obj1).bind(obj2).bind(obj3);
boundFn(); // 输出: Object 1

// 原因:bind 返回的是一个新函数,this 在第一次 bind 时就已经固定了

9. 实现防抖和节流

javascript 复制代码
// 防抖函数中使用 apply
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      // 使用 apply 保持 this 和参数
      fn.apply(this, args);
    }, delay);
  };
}

// 使用示例
const input = {
  value: '',
  handleInput: function(event) {
    console.log('处理输入:', this.value, event);
  }
};

const debouncedHandler = debounce(input.handleInput, 500);
// debouncedHandler.call(input, { type: 'input' });

// 节流函数
function throttle(fn, delay) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= delay) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
}

10. 手写实现 call、apply、bind

javascript 复制代码
// 手写 call
Function.prototype.myCall = function(context, ...args) {
  // context 为 null 或 undefined 时,指向 window
  context = context || window;
  // 给 context 添加一个唯一属性,值为当前函数
  const fnSymbol = Symbol('fn');
  context[fnSymbol] = this;
  // 执行函数
  const result = context[fnSymbol](...args);
  // 删除添加的属性
  delete context[fnSymbol];
  return result;
};

// 手写 apply
Function.prototype.myApply = function(context, argsArray) {
  context = context || window;
  const fnSymbol = Symbol('fn');
  context[fnSymbol] = this;
  const result = argsArray ? context[fnSymbol](...argsArray) : context[fnSymbol]();
  delete context[fnSymbol];
  return result;
};

// 手写 bind
Function.prototype.myBind = function(context, ...bindArgs) {
  const fn = this;
  return function(...callArgs) {
    // 合并 bind 时的参数和调用时的参数
    return fn.apply(context, [...bindArgs, ...callArgs]);
  };
};

// 测试手写方法
function testFunc(a, b, c) {
  console.log('this.name:', this.name);
  console.log('参数:', a, b, c);
  return a + b + c;
}

const testObj = { name: 'Test Object' };

console.log('=== myCall 测试 ===');
testFunc.myCall(testObj, 1, 2, 3);

console.log('=== myApply 测试 ===');
testFunc.myApply(testObj, [4, 5, 6]);

console.log('=== myBind 测试 ===');
const boundTest = testFunc.myBind(testObj, 7);
boundTest(8, 9);

📋 总结对比表

特性 call apply bind
执行时机 立即执行 立即执行 返回函数,需手动调用
参数传递 逗号分隔 fn.call(obj, a, b) 数组 fn.apply(obj, [a, b]) 逗号分隔 fn.bind(obj, a, b)
返回值 函数执行结果 函数执行结果 新函数
典型应用 借用方法、继承 数组操作、参数不定 事件绑定、柯里化
相关推荐
三门3 小时前
vue官网新读之后收获记录
前端
Keepreal4963 小时前
使用Canvas绘制转盘
javascript·vue.js·canvas
mapbar_front3 小时前
我们需要前端架构师这个职位吗?
前端
ScriptBIN4 小时前
Javaweb--Vue
前端·vue.js
KenXu4 小时前
React Conf 2025 - 核心更新
前端
前端Hardy4 小时前
Vue 高效开发技巧合集:10 个实用技巧让代码简洁 50%+,面试直接加分!
前端·javascript·vue.js
ᖰ・◡・ᖳ4 小时前
JavaScript:神奇的ES6之旅
前端·javascript·学习·es6
app出海创收老李4 小时前
海外独立创收日记(5)-上个月收入回顾与本月计划
前端·后端·程序员
前端Hardy4 小时前
HTML&CSS:一眼心动的 SVG 时钟
前端·javascript·css