[譯] 如何在 JavaScript 中使用 apply(?),call(?),bind(➰)

JintNiu發表於2019-03-14

[譯] 如何在 JavaScript 中使用 apply(?),call(?),bind(➰)

原文連結:How to use the apply(?), call(?), and bind(➰) methods in JavaScript
原文作者:Ashay Mandwarya
譯者:JintNiu
推薦理由:apply,callbind 在面試和日常編碼中常會遇到,瞭解和掌握他們的用法變得尤為重要。

在本文中,我們將討論函式原型鏈中的 applycallbind 方法,它們是 JavaScript 中最重要且經常使用的概念,且與 this 關鍵字密切相關。

因此,想要掌握本文所述內容,您必須熟悉 this 關鍵字的概念和用法。可以參考這篇文章

譯者注:可參考:[譯] JavaScript 之 this 指南

要了解 apply|call|bind,我們首先需要了解 JavaScript 中的 Function,當然這前提是你可以熟料運用 this 了。

Functions

[譯] 如何在 JavaScript 中使用 apply(?),call(?),bind(➰)

Function 建構函式建立了一個新的 Function 物件。直接呼叫建構函式的話,可以動態地建立出在全域性作用域下的函式。

JavaScript 中,函式是一種物件,可以由applycallbind 方法進行呼叫。

要檢查函式是否為一個 Function 物件,我們可以使用以下程式碼進行判斷,該程式碼段返回 true

(function () { }).constructor === Function ? console.log(true) : console.log(false);
複製程式碼

全域性 Function 物件沒有自己的方法或屬性。但由於它本身就是一個函式,可以通過 Function.prototype 原型鏈繼承一些方法和屬性。 — MDN

以下是函式原型鏈中的方法:

  • Function.prototype.apply()
  • Function.prototype.bind()
  • Function.prototype.call()
  • Function.prototype.isGenerator()
  • Function.prototype.toSource()
  • Object.prototype.toSource
  • Function.prototype.toString()
  • Object.prototype.toString

這篇文章中我們只討論前三個。

Apply ?

[譯] 如何在 JavaScript 中使用 apply(?),call(?),bind(➰)

apply() 是函式原型中的一個重要方法,用於呼叫其他函式, 引數為給定 this 和一個陣列(或類似陣列的物件)。

類陣列物件可以參照 NodeList 或函式內的 arguments 物件。

這意味著我們可以呼叫任何函式,並顯式指定呼叫時 this 的指向。

語法

function.apply(this,[argumentsArray])
複製程式碼

返回值

返回 this 指向函式的呼叫結果。

描述

apply() 方法使物件 x 中的函式/物件可以被另一個物件 y 呼叫。

例子

1.

var array = ['a', 'b'];
var elements = [1, 2, 3];
array.push(elements);
console.log(array);  // ['a', 'b', [1, 2, 3]]
複製程式碼

以上程式碼中,當我們將一個陣列 push 進另一個陣列時,整個陣列被視為一個元素直接 push 進去。但如果我們想要將陣列 elemennts 中的元素分別 push 進陣列 array 中呢?當然有很多方法可以這樣做,在這裡我們使用 apply()

var array = ['a', 'b'];
var elements = [1, 2, 3];
array.push.apply(array, elements);
console.log(array); // ["a", "b", 1, 2, 3]
複製程式碼

該例中,使用 apply 連線給定陣列,引數為陣列 elementsthis 指向變數 array,實現陣列 elements 中的元素被 pushthis 指向的物件(array)中。最終返回的結果為第二個陣列中的每個元素被 pushthis 指向的陣列中。

2.

var numbers = [53, 65, 25, 37, 78];
console.log(Math.max(numbers)); //NaN
複製程式碼

JSmax 函式用於查詢給定元素的最大值。但正如我們所見,如果給定值為陣列,返回結果為 NaN。當然,JavaScript 中有很多方法可以解決,在這裡我們使用 apply

var numbers = [53, 65, 25, 37, 78];
console.log(Math.max.apply(null, numbers)); //78
複製程式碼

當我們使用 apply 呼叫 Math.max() 時,得到了期望結果。applynumbers 中所有值作為單獨的引數,然後再呼叫 max 進行處理,最終返回陣列中的最大值。

值得注意的是,我們使用 null 代替了 this。由於提供的引數是數字陣列,即使使用了 this,它也仍會指向同一個陣列,最終得到相同的結果。因此,這種情況下我們可以省略 this,改用 null 代替。也就是說,apply 函式中的 this 引數是一個可選引數。

Call ?

[譯] 如何在 JavaScript 中使用 apply(?),call(?),bind(➰)

call() 方法用於呼叫一個函式,引數為給定 this 和若干個單獨指定的引數序列。

這意味著我們可以呼叫任何函式,並顯式指定呼叫時 this 的指向。

這與 apply 非常相似,唯一的區別是 apply 以陣列或類陣列物件的形式接受引數,而 call 的引數是單獨提供的。

語法

function.call(thisArg,arg1,arg2,...)
複製程式碼

返回值

返回 this 指向且引數為給定引數的函式呼叫結果。

描述

call() 方法使物件 x 中的一個函式/物件可以被物件 y 呼叫。

例子

1.

function Product(name, price) {
    this.name = name;
    this.price = price;
}
function Pizza(name, price) {
    Product.call(this, name, price);
    this.category = 'pizza';
}
function Toy(name, price) {
    Product.call(this, name, price);
    this.category = 'toy';
}

var pizza = new Pizza('margherita', 50);
var toy = new Toy('robot', 40);
console.log(toy); // Toy {name: "robot", price: 40, category: "toy"}
console.log(pizza); // Pizza {name: "margherita", price: 50, category: "pizza"}
複製程式碼

這是建構函式鏈的一個例子。可以看到,每個函式中都呼叫了 Product 的建構函式,並使用 callProduct 物件的屬性分別與 PizzaToy 連線在一起。

當建立了 PizzaToy 物件的例項並輸出時,結果顯示其具有 name,pricecategory 三個屬性,但我們只定義了 category一個屬性。而屬性nameprice 則是由於已經在 Product 物件中定義並應用,可通過 Product 物件的鏈式建構函式獲得。將以上程式碼稍作改動即可實現繼承。

2.

function sleep(){
    var reply=[this.animal,'typically sleep between',this.sleepDuration].join(' ');
    console.log(reply); // I typically sleep between 12 and 16 hours
}
var obj={
    animal:'I',sleepDuration:'12 and 16 hours'
};
sleep.call(obj);
複製程式碼

在上面的程式碼中,我們定義了一個名為 sleep 的函式,其包含一個陣列 reply ,該陣列由 this 進行屬性定址得到的元素組成,這些元素在函式外的獨立物件中定義。

呼叫函式 sleep,其引數為 obj。可以看到 this.animalthis.sleepDuration 分別取到了 obj 的屬性,並輸出了完整的句子。

Bind➰

[譯] 如何在 JavaScript 中使用 apply(?),call(?),bind(➰)

bind() 方法被呼叫時會建立一個新函式,該函式第一個引數作為 this,並在呼叫新函式時提供一個給定的引數序列。  — MDN

譯者注:bind() 方法會建立一個新函式,稱為繫結函式,當呼叫這個繫結函式時,繫結函式會以建立它時傳入bind() 方法的第一個引數作為 this,第二個以及以後的引數,加上繫結函式執行時本身的引數,按照順序作為原函式的引數來呼叫原函式。

語法

function.bind(this,arg1,arg2,arg3,...)
複製程式碼

返回值

返回 this 指向且引數為給定引數的函式副本的呼叫結果。

描述

bind 方法與 call 方法類似,主要區別在於 bind 返回一個新函式,而 call 不返回。

根據 ECMAScript 5 規範,bind 方法返回的函式是一種特殊型別的函式物件,稱為繫結函式( BF )。BF 中包含原始函式物件,呼叫 BF 時會執行該函式。

例子

var x = 9;
var module = {
    x: 81,
    getX: function () {
        return this.x;
    }
};
console.log(module.getX()); // 81
var retrieveX = module.getX;
console.log(retrieveX()); // 9
var boundGetX = retrieveX.bind(module);
console.log(boundGetX()); // 81
複製程式碼

在上面的程式碼中,我們定義了一個變數 x 和一個物件 module,該物件中還定義了一個屬性 x 以及一個返回 x 值的函式。

當呼叫函式 getX 時,它返回的是物件內定義的 x 的值,而不是全域性作用域中的 x

另一個變數在全域性作用域中宣告,並呼叫 module物件中的 getX 函式。但由於該變數處於全域性作用域下,因此 getX 中的 this 指向全域性作用域下的 x,返回 9。

最後又定義了另一個變數 boundGetX,該變數呼叫函式 retrieveX,與之前不同的是,這次將函式 retrieveX與物件 module 繫結,返回的是物件內 x 的值。這是由於 bind 將函式中的 this 指向物件中的 x 值而不是全域性 x,因此輸出 81。

結論

現在我們已經瞭解到以上三種方法的基礎用法,但你可能會疑惑:為什麼要用 3 個不同的方法去做相同的事情。為了解決這個問題,你必須反覆練習在不同場景下它們的使用方法,更全面地瞭解什麼時候使用它們,以及如何更好的使用,這肯定會讓你的程式碼更清晰,更強大。

如果您喜歡這篇文章,點個贊?,加個關注? 吧~

[譯] 如何在 JavaScript 中使用 apply(?),call(?),bind(➰)

相關文章