一、核心概念解析
1. 函数对象的三大法宝
这三个方法都继承自 Function.prototype,是所有函数的内置方法:
function sum(a, b) {
return a + b;
}
console.log(sum.hasOwnProperty('call')); // false(来自原型链)
console.log('call' in sum); // true
2. 核心能力对比表
| 方法 | 立即执行 | 参数形式 | 返回新函数 | 常用场景 |
|---|---|---|---|---|
| call | ✅ | 参数列表 | ❌ | 明确参数个数时使用 |
| apply | ✅ | 数组 | ❌ | 处理动态参数时使用 |
| bind | ❌ | 参数列表 | ✅ | 需要延迟执行时使用 |
二、方法深度剖析
1. call 方法
实现原理伪代码:
Function.prototype.myCall = function(context, ...args) {
context = context || window; // 处理 null 和 undefined
const fnKey = Symbol('tempFn');
context[fnKey] = this; // 绑定函数到目标对象
const result = context[fnKey](...args);
delete context[fnKey];
return result;
};
经典应用场景:
// 1. 类数组转换
function convertToArray() {
return [].slice.call(arguments);
}
// 2. 构造函数链式调用
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
2. apply 方法
性能优化案例:
// 更优的数组合并方式
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
// 传统方式(产生中间数组)
arr1.concat(arr2);
// 高性能方式
Array.prototype.push.apply(arr1, arr2);
数学计算示例:
const numbers = [5, 2, 8, 4];
Math.max.apply(null, numbers); // 8
3. bind 方法
手写实现(支持 new 操作) :
Function.prototype.myBind = function(context, ...bindArgs) {
const self = this;
function boundFn(...callArgs) {
// 判断是否通过 new 调用
const isNewCall = this instanceof boundFn;
return self.apply(
isNewCall ? this : (context || window),
bindArgs.concat(callArgs)
);
}
// 保持原型链
boundFn.prototype = Object.create(self.prototype);
return boundFn;
};
React 中的典型应用:
class Button extends React.Component {
handleClick = (e) => {
console.log(this.props.message, e);
}
render() {
return (
<button onClick={this.handleClick.bind(this)}>
Click me
</button>
);
}
}
三、综合应用案例
1. 柯里化函数实现
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
}
}
};
}
// 使用示例
const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
2. 面试题解析
题目:以下代码输入结果?
const obj = {
x: 42,
getX: function() {
return this.x;
}
};
const unboundGetX = obj.getX;
const boundGetX = unboundGetX.bind(obj);
console.log(unboundGetX()); // undefined(非严格模式为 window.x)
console.log(boundGetX()); // 42
变种题:
function foo() {
console.log(this.a);
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
(p.foo = o.foo)(); // 输出什么?
四、性能优化与最佳实践
1. 缓存绑定结果
// 错误示范(每次渲染都创建新函数)
element.addEventListener('click', this.handleClick.bind(this));
// 正确做法
constructor() {
this.handleClick = this.handleClick.bind(this);
}
2. 箭头函数替代方案
// 使用类属性+箭头函数
class MyComponent {
handleClick = () => {
console.log(this); // 自动绑定实例
}
}
五、常见误区警示
1. 严格模式下的差异
'use strict';
function test() {
console.log(this); // undefined
}
test.call(null); // 正常执行,this 为 null
2. 绑定优先级
const obj1 = { name: 'Alice' };
const obj2 = { name: 'Bob' };
function showName() {
console.log(this.name);
}
const boundFn = showName.bind(obj1);
boundFn.call(obj2); // 仍然输出 'Alice'
六、扩展知识
1. this的绑定优先级
new绑定 > 显示绑定(call,apply,bind)> 隐士绑定(对象方法)> 默认绑定
2. ES6+ 新特性替代方案
// 使用展开运算符替代 apply
Math.max(...[1, 2, 3]);
// 使用箭头函数自动绑定 this
const obj = {
value: 42,
getValue: function() {
setTimeout(() => {
console.log(this.value); // 正确获取42
}, 100);
}
};