JS -- 详解apply()

152 阅读3分钟

什么是apply()?

apply 是 JavaScript 中函数对象的一个方法,它和 call 方法类似,用于在特定的作用域(上下文)中调用函数。apply 方法允许你指定函数运行时的 this 值,并且可以传入一个数组作为函数的参数。

语法如下:

functionName.apply(thisArg, [argsArray])

其中:

  • functionName 是要调用的函数名。
  • thisArg 是函数执行时的上下文对象,在函数内部通过 this 关键字访问。如果将 thisArg 设置为 nullundefined,则在严格模式下 this 值会被设置为全局对象,非严格模式下则会被设置为 window 对象。
  • argsArray 是一个数组,包含了作为参数传递给函数的参数列表。

apply 方法通常用于以下几种情况:

  1. 将数组作为参数传递给函数。
  2. 在不知道参数个数的情况下,将一个数组展开成参数列表传递给函数。

下面是一个示例,演示了如何使用 apply 方法将一个数组作为参数传递给函数:

function sum(x, y) {
    return x + y;
}

const numbers = [3, 4];

const result = sum.apply(null, numbers);
console.log(result); // 输出 7,等同于 sum(3, 4)

在这个示例中,apply 方法将数组 numbers 中的元素作为参数传递给 sum 函数,相当于调用了 sum(3, 4)

call 方法不同的是,apply 方法接受的参数是一个数组,而 call 方法接受的是一系列参数。

为什么要使用apply()?

apply() 方法通常用于以下几种情况:

1.将数组作为参数传递给函数

当一个函数接受可变数量的参数,且这些参数已经存在于一个数组中时,可以使用 apply() 方法将数组中的元素作为参数传递给函数。这种情况下,使用 apply() 比逐个参数传递更方便、更简洁。

function sum(x, y) {
    return x + y;
}

const numbers = [3, 4];

const result = sum.apply(null, numbers);
console.log(result); // 输出 7,等同于 sum(3, 4)

2.在不知道参数个数的情况下,将一个数组展开成参数列表传递给函数

有时候在编写函数时无法确定参数的数量,这时候可以将参数存储在数组中,然后使用 apply() 方法将数组展开成参数列表传递给函数。

function sum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
        total += arguments[i];
    }
    return total;
}

const numbers = [1, 2, 3, 4, 5];

const result = sum.apply(null, numbers);
console.log(result); // 输出 15,等同于 sum(1, 2, 3, 4, 5)

3.指定上下文对象并调用函数

apply() 方法可以用来指定函数执行时的上下文对象(即 this 值)。这种情况下,apply() 方法和 call() 方法类似。

const person = {
    name: 'Alice',
    greet: function() {
        console.log(`Hello, my name is ${this.name}.`);
    }
};

const anotherPerson = {
    name: 'Bob'
};

person.greet.apply(anotherPerson);
// 输出:Hello, my name is Bob.

总的来说,apply() 方法提供了一种灵活、方便的方式来处理参数数组以及指定函数执行时的上下文,使得函数的调用更加灵活和可控。

apply()和call()的区别?

apply()call() 是 JavaScript 中函数对象的两个方法,它们的作用都是在特定的作用域(上下文)中调用函数,并且可以传入参数列表。它们之间的区别主要在于传参的方式:

  1. 参数传递方式

    • apply(thisArg, [argsArray])apply() 方法接受两个参数,第一个参数是函数执行时的上下文对象,第二个参数是一个数组,包含了作为参数传递给函数的参数列表。
    • call(thisArg, arg1, arg2, ...)call() 方法接受多个参数,第一个参数是函数执行时的上下文对象,后续参数是作为参数传递给函数的具体参数。
  2. 参数个数和类型

    • apply() 方法适用于参数数量不确定的情况,可以将一个数组作为参数传递给函数。
    • call() 方法适用于已知参数数量且类型明确的情况,可以将一系列参数作为参数传递给函数。

举例说明:

function greet(name) {
    console.log(`Hello, ${name}! My name is ${this.name}.`);
}

const person = {
    name: 'Alice'
};

const anotherPerson = {
    name: 'Bob'
};

// 使用 apply() 方法
greet.apply(person, ['Bob']);
// 输出:Hello, Bob! My name is Alice.

// 使用 call() 方法
greet.call(anotherPerson, 'Alice');
// 输出:Hello, Alice! My name is Bob.

在这个例子中,apply() 方法将数组 ['Bob'] 作为参数传递给 greet 函数,而 call() 方法则将字符串 'Alice' 作为参数传递给 greet 函数。

如何⼿写apply()

apply 的实现跟 call 类似,只是⼊参不⼀样,apply为数组

下面是一个简单的手写实现 apply 方法的示例:

Function.prototype.myApply = function(context, argsArray) {
    // 如果传入的 context 是 null 或者 undefined,则默认为全局对象(非严格模式下为 window,严格模式下为 undefined)
    context = context || window;
    // 将当前函数设为传入的 context 对象的方法
    context.fn = this;
    // 调用当前函数,并传入参数数组
    const result = context.fn(...argsArray);
    // 删除添加的方法
    delete context.fn;
    // 返回调用结果
    return result;
};

// 测试
function greet(name) {
    console.log(`Hello, ${name}! My name is ${this.name}.`);
}

const person = {
    name: 'Alice'
};

// 使用手写的 myApply 方法调用 greet 函数
greet.myApply(person, ['Bob']);

最简化的写法

Function.prototype.apply2 = function(context, args) {
    context = context || window;

    let fnSymbol = Symbol()
    context[fnSymbol] = this
    let fn = context[fnSymbol](...args)
    delete context[fnSymbol]
    return fn
}

这个方法的实现步骤如下:

  • 首先,判断传入的 context 是否为 undefinednull,如果是,则将 context 设置为全局对象(非严格模式下为 window,严格模式下为 undefined)。
  • 然后,创建一个唯一的 Symbol 作为临时属性的键,以避免和对象原有的属性名冲突。
  • 将当前函数设置为 context 对象的临时属性。
  • 调用函数,并传入参数。
  • 最后,删除添加的临时属性,以避免对 context 对象造成污染,并返回调用结果。

这个方法的功能与原生的 apply 方法是相似的,可以用来改变函数执行时的上下文,并传入参数列表。