學習並運用JavaScript的原生函式

發表於2016-02-17

簡介

儘管 JavaScript 總是讓人產生誤解,但是它已經成為了最流行的程式語言之一。理解 JavaScript 的內在原理很困難。同樣的,迫使 JavaScript 成為常規規範,如物件導向或函式程式設計,同樣具有挑戰性。這裡我強調闡明 JavaScript 核心部分的原生函式。

在這篇文章中,我將討論以下幾種行為:

  • Call/Apply
  • Bind
  • Map
  • Filter

首先我會定義這個函式(利用Mozilla的宣告方式),然後提供一個例子,最後實現此函式。

為了解釋這些行為,我需要先解釋一下複雜的 this 關鍵字以及類似陣列的 arguments 物件。

thisarguments 物件

JavaScript 的作用域是基於函式而言的,術語一般稱為作用域,變數和方法的作用域都是當前函式。此外,函式執行的作用域是他們被定義的作用域而不是執行的作用域。如果你想了解更多有關於作用域的知識,可以參考你應該知道的4種 JavaScript 設計模式這篇文章。this物件引用當前函式的上下文並且可以以多種方式被呼叫。例如,它可以被繫結到 window 物件(全域性作用域)。

並且變數可以繫結到已存在的函式中,如下:

這裡存在被繫結到每一個呼叫函式的 this 物件。嚴格模式下,如果變數未定義就會丟擲異常/錯誤( TypeErrors )。在生產環境下嚴格模式者會被優先考慮;然而,我故意選擇不使用此模式以避免丟擲異常。下面是嚴格模式下的一個簡單例子:

可能很多 JavaScript 開發人員不知道,建立函式時會有一個arguments物件。這是一個類似陣列的物件(僅具有屬性的長度)。arguments主要有三個屬性,即callee(呼叫方法),length,和caller(呼叫函式的參考)。

在一個函式中宣告變數引數會替換/覆蓋原先的引數物件。

如下列出的一些引數物件:

按照如下所示,用 arguments 建立一個陣列:

Call/Apply

無論 call 還是 apply 都是呼叫物件的一個方法。關於使用點操作符,callapply 都接受其作為第一個引數。如上所述,每一個函式都保持在其所定義的特定作用域內。因此,當你呼叫物件時必須考慮到函式的作用域。

Mozilla 瀏覽器的applycall呼叫宣告如下所示:

通過傳遞 thisArg 引數,在特定的上下文中,被呼叫的函式可以訪問或修改物件。下面的例子闡明瞭 call 的使用。

注意:第一次呼叫預設為全域性作用域(window),然而,第二次為 darthvader

callapply 主要的區別在於他們的宣告方式不同。call 需要引數分開傳遞,而 apply 需要傳入由引數組成的陣列。我是這樣記憶的:“Apply uses an Array。”當你的程式無關乎引數數目時,apply 方法可能會更加適用。

Currying(柯里化)(部分函式應用)是應用 callapply 的一個函數語言程式設計。Currying 允許我們建立返回已知條件的函式。這裡是一個 currying 函式:

雖然 arguments 不是陣列,但是 Array.prototype.slice可以將類陣列的物件轉換成新陣列。

Bind

bind方法用於明確指定呼叫 this 方法。在作用域方面,類似於 callapply 。當你將一個物件繫結到一個函式的 this物件時,你就會用到 bind

如下是bind宣告

通俗地說,我們是通過 bind 向函式 fun 傳遞 thisArg 引數。實質上就是每次 fun 函式都必須通過傳遞 thisArg 引數呼叫 bind 方法。讓我們在一個簡單的例子中仔細看看。

第一個getfather()返回值為 undefined 是因為在這裡 father 屬性沒有被定義。那這時 this 代表什麼呢?只要我們不明確的指定它,它就代表 window 的全域性物件。第二個getfather()返回 “Anakin Skywalker”是因為getfather()中的 this 指代的是 lukeskywalker。許多Java/C++ 開發人員會設想最後一個getfather()的呼叫將返回預想的結果–雖然再次返回全域性物件。

如下這裡是 bind 的實現原理:

這裡 JavaScript 的作用域是合乎邏輯的,返回函式的 this 物件是不同於 bindthis 物件的。因此,將 this 暫時快取給變數 _that 保證了其正確的作用域範圍。否則,this.apply(scope,arguments) 將會未定義。

Map

JavaScript 的 map 函式是遍歷陣列,同時轉換每個元素的函式程式設計技術。它用 modified 元素建立了一個新陣列並以回撥的方式返回。關於我提到的修改或轉換元素,實踐表明,如果元素是物件(而不是原語),這只是克隆物件並不是從物理上改變了原生的。

以下是該方法的宣告:

回撥方法有三個引數,即 currentValueindex,和 array

這裡是一個有關於 map 的簡單例子:

瞭解了 map 是用來做什麼的,讓我們看一下它具體是如何實現的:

注:這是一個簡單的實現。到 ECMAScript 5看全部的實現,並查閱其規範

Filter

filter 方法是陣列的另外一種表現行為。類似於 mapfilter 返回一個新的陣列並接受一個函式和一個可選的 thisArg 引數。然而,返回的陣列僅包含適合在回撥函式測試的特定條件的元素。回撥函式必須返回一個 Boolean –返回 true 的元素才會被接受並插入到返回的陣列。

關於 filter 有許多應用,包括選擇偶數,用一個特定的屬性選擇物件,或選擇有效的電話號碼。

這裡是其中一種宣告方法:

同樣的,thisArg 是可選的引數並且回撥函式接受三個引數,currentValueindexarray

這裡是一個有關於 filter 的例子:

有趣的是,array 方法可以創造有趣的,複雜的操作。

最後,讓我們看看 filter 的實現:

這裡是 ECMAScript 的實現規範

總結

還有更多令人困惑但是很有用的原生函式。它們是值得用陣列和函式來回顧其中的每一種方法。

希望這篇文章可以有助於你理解 JavaScript 的內部原理和詞法作用域。儘管與實踐緊密相連,callapply,和bind 還是很難把握的。為了避免傳統的迴圈技術你可以嘗試使用 mapfilter 方法 。

相關文章