📚 基础区别示例
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) |
返回值 |
函数执行结果 |
函数执行结果 |
新函数 |
典型应用 |
借用方法、继承 |
数组操作、参数不定 |
事件绑定、柯里化 |