在 JavaScript 中,apply
是函数对象的一个方法,用于调用函数并显式指定 this
的值,同时以数组形式传递参数。它与 call
方法类似,区别在于 apply
接受参数数组,而 call
接受参数列表。让我们从入门到精通,逐步通过示例讲解 apply
的用法。
基础概念
- 语法:
1 |
function.apply(thisArg, [argsArray]) |
thisArg
:函数执行时this
的绑定对象。argsArray
:一个数组或类数组对象,函数的参数会从中依次取出。- 返回值:函数的执行结果。
- 作用:改变函数的
this
指向,并传入参数执行函数。
从入门到精通的示例
1. 入门:基本用法
- 场景:简单调用函数,指定
this
。 - 示例:
1 2 3 4 5 6 |
function sayHello(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } const person = { name: "Alice" }; sayHello.apply(person, ["Hello", "!"]); // 输出: "Hello, Alice!" |
- 解释:
this
被绑定到person
对象。- 参数
["Hello", "!"]
被解构为greeting = "Hello"
和punctuation = "!"
。
2. 初级:与数组参数结合
- 场景:已有参数数组,直接传递给函数。
- 示例:
1 2 3 4 5 6 7 |
function sum(a, b, c) { return a + b + c; } const numbers = [1, 2, 3]; const result = sum.apply(null, numbers); // 输出: 6 console.log(result); |
- 解释:
this
不重要时,可以传null
或undefined
。numbers
数组被展开为sum(1, 2, 3)
。
3. 中级:借用方法
- 场景:用一个对象的函数操作另一个对象。
- 示例:
1 2 3 4 |
const arrayLike = { 0: "apple", 1: "banana", length: 2 }; const realArray = Array.prototype.slice.apply(arrayLike); console.log(realArray); // 输出: ["apple", "banana"] |
- 解释:
- 类数组对象
arrayLike
没有slice
方法。 - 通过
apply
借用Array.prototype.slice
,将this
绑定到arrayLike
,将其转为真正的数组。 - 补充:这在 ES6 前常用于类数组(如
arguments
)转数组,现在可以用Array.from
或扩展运算符...
。
4. 中高级:结合 arguments
对象
- 场景:在函数内部动态处理不定参数。
- 示例:
1 2 3 4 5 |
function logAll() { console.log(Array.prototype.join.apply(arguments, [", "])); } logAll("a", "b", "c"); // 输出: "a, b, c" |
- 解释:
arguments
是一个类数组,包含所有传入参数。- 用
apply
借用join
方法,将arguments
转为字符串,中间用", "
分隔。
5. 高级:实现函数柯里化
- 场景:利用
apply
实现参数的逐步传递。 - 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function multiply(a, b, c) { return a * b * c; } function curry(fn) { return function (...args) { if (args.length >= fn.length) { return fn.apply(null, args); } return function (...moreArgs) { return curry(fn).apply(null, args.concat(moreArgs)); }; }; } const curriedMultiply = curry(multiply); console.log(curriedMultiply(2)(3)(4)); // 输出: 24 console.log(curriedMultiply(2, 3)(4)); // 输出: 24 |
- 解释:
curry
函数将multiply
转为柯里化形式。apply
用于动态地将累计的参数应用到目标函数上。
6. 精通:结合 Proxy
拦截函数调用
- 场景:在
Proxy
中用apply
增强函数行为。 - 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function greet(name) { return `Hello, ${name}!`; } const proxiedGreet = new Proxy(greet, { apply(target, thisArg, args) { console.log("函数被调用,参数:", args); return target.apply(thisArg, args).toUpperCase(); } }); console.log(proxiedGreet("Alice")); // 输出: // "函数被调用,参数: ["Alice"]" // "HELLO, ALICE!" |
- 解释:
Proxy
的apply
陷阱拦截函数调用。- 在陷阱中用
target.apply
执行原始函数,并对结果加工(转为大写)。
7. 精通:性能优化与函数组合
- 场景:用
apply
实现函数管道(pipeline)。 - 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function addOne(x) { return x + 1; } function double(x) { return x * 2; } function square(x) { return x * x; } function pipe(...fns) { return function (initial) { return fns.reduce((result, fn) => fn.apply(null, [result]), initial); }; } const compute = pipe(addOne, double, square); console.log(compute(3)); // 输出: 16 // 计算过程: 3 → 4 (addOne) → 8 (double) → 64 (square) |
- 解释:
pipe
将多个函数组合成一个,从左到右依次执行。apply
用于动态调用每个函数,并传递上一步的结果。
注意事项
- 与
call
的区别:
call(thisArg, arg1, arg2, ...)
:参数逐个传递。apply(thisArg, [arg1, arg2, ...])
:参数以数组形式传递。- 示例:
javascript function test(a, b) { console.log(this, a, b); } test.call({ id: 1 }, 2, 3); // { id: 1 }, 2, 3 test.apply({ id: 1 }, [2, 3]); // { id: 1 }, 2, 3
- 替代方案:
- ES6 后,扩展运算符
...
可以部分替代apply
:javascript const numbers = [1, 2, 3]; console.log(Math.max(...numbers)); // 3 // 等价于 Math.max.apply(null, numbers)
- 性能:
- 在现代 JavaScript 中,
apply
的性能稍逊于call
或...
,但差异微乎其微,除非在极端循环中。
总结
- 入门:理解
apply
的基本用法,改变this
和传递数组参数。 - 进阶:利用
apply
借用方法、处理动态参数。 - 精通:结合现代特性(如
Proxy
)或实现复杂逻辑(如柯里化、管道)。