這是一門在Udemy 課程,是同學介紹的。主要是因為想要運用到過年前這段時間好好的增進自己的JavaScript的基礎能力,讓自己能往前端工程師更近一步。主要還是會以筆記的形式做呈現!
在之前的有提到過,我們在執行環境中會有:Variable Environment、Outer Environment、this。
其中this
某些情況是會指向global environment
,某些時候則是指向object
但有些時候我們必須要去control 函式裡面的this所指向的object有辦法嗎?
是有辦法的,就是我們現在要介紹的call
、apply
和bind
。
|First Class Functions
首先我們要先複習一下First Class Functions
:
First Class Functions
並不是 JavaScript 專有的特性,只要該語言的「函式可被視為與其它變數一樣時」,就可以稱為該語言有First Class Functions
的特性。
這些 JavaScript 的函式就具有以下特性:
- 將函式指定到一個變數
- 函式可作為參數來傳遞
- 函式也可以被回傳(return)
- 函式也是物件的物件(傳參考特性、具有屬性)
因此我們可以知道,function它只是一種特殊的object
,而它包含了兩個隱藏的屬性,一個是name property,用來儲存函式的名稱(也可以是匿名函式);另一個是code property,用來儲存函式當中程式碼的內容。
|認識這3個method
就像剛剛說的,function他只是一種特殊的object
所以它具有properties 和 methods。剛剛提到的call
、apply
和bind
就是function裡面的method。
看一下下面這段code:
1 | var person = { |
這段程式碼執行後會出現什麼?如果this觀念有學好應該馬上就可以知道會出現錯誤,原因就在於global object根本沒有getFullName這個方法,這個方法是被建立在person這個物件裡面。所以就會出現:
1 | TypeError: this.getFullName is not a function |
但如果我們想要指稱logName function中this所指稱的對象,這時候就可以使用bind
。
|bind
只要在JavaScript中建立的函式,都會預設有bind這個方法在內。使用的方法只要在該function後使用.bind,並於( )的地方代入欲替換成this的物件。
這裡要特別注意,不是寫成這樣:logName().bind(person)
。因為logName()
會是執行後的結果,而bind(person)
卻是function的method,所以正卻的寫法應該是:var logPersonName = logName.bind(person)
然後在logPersonName()
。
1 | var logName = function(lang1, lang2) { |
也可以直接在logName函式的後面去執行 bind,如下:
1 | var logName = function(lang1, lang2) { |
|call
call的用法其實就和括號 ( ) 一樣,都是直接去執行invoke
這個function,但不一樣的地方在於,call的後面可以帶入你想要指定this的物件,接著再放入參數。
我們使用剛剛的範例來舉例:
1 | var person = {...} |
|apply
apply和call的用法大同小異,唯一不同的地方在於,使用apply時,放入參數的地方,應該要放入的是陣列(array)。講師有提到apply的用法常用到有許多算數的地方。
1 | var person = {...} |
|實例
bind是複製原本的函式,並且將你所指定的this代入這個函式中,所以如果你要在執行這個函式的話,最後要接上( )來執行該函式;而call和apply則是將你所指定的this直接代入該function中並執行,所以最後面不用在加上( )來執行該函式。
理論知道後,來看一些實際案例:
|Function Borrowing
假設我們現在建立了另一個物件叫做person2,但我想要使用person這個物件裡面的getFullName這個方法時,我該怎麼處裡?其實非常簡單:
1 | var person2 = { |
首先我們要先找到getFullName,然後用apply()或是call(),裡面的參數則是放我們要放入的person2就可以了。
|Function Currying
1 | function multiply(a, b) { |
我們需要一個2個數相乘的函式,取名叫multiply並設定需要兩個參數。
這時候我需要a都是固定數值但不要更改到multiply這個函式的內容,所以我們使用了var multipleByTwo = multiply.bind(this, 2)
這個寫法,這寫法的意思就是說我們把a這個參數設定成2,然後複製原本multiply這個function變成multipleByTwo。
所以之後執行multipleByTwo這個函式,就只需要代入一個參數(原本multiply裡的參數b;a已經預設為2)
這時候如果這時候我把這段改成multiply.bind(this, 2, 5)
,就是把 a 的參數設定成2,把 b 的參數設定成 5 。
|Functional Programming
1 | var arr1 = [1, 2, 3] |
這是一段簡單的code,我需要一個陣列arr2,而裡面的元素都是arr1 x 2。
但在AC上課時有說到:身為一個工程師,我們總是想要透過最少的程式碼來達到同樣的效果,而且要避免類似的程式碼重覆。
因此這邊課程教導了我一個概念:Functional Programming
第一步,就是先把程式碼函式化,把會變動的值當成參數處理:
1 | function mapForEach(arr, fn) { |
這邊我們帶入的參數中,第一個是要放入的陣列,取名叫arr;第二個就是要放入要執行的函式,取名叫fn。
接下來就是放入要放入的內容,如下:
1 | var arr1 = [1,2,3]; |
結果就會是我們想要的[2, 4, 6]。
有了mapForEach
這個function後我們就可做很多的延伸變化,例如:
判斷在arr1這個陣列中,有哪些元素值是大於2的:
1 | var arr3 = mapForEach(arr1, function(item) { |
那如果我們不想放function expression而是想放變數呢?
1 | var checkPastLimit = function(limiter, item) { |
為什麼這邊要用到bind?原因就在於mapForEach
這個函式裡面的newArr.push(fn(arr[i]))
這段,fn裡面只會帶入一個參數,但我們在var checkPastLimit = function(limiter, item){...}
卻是要帶入兩個參數。
因此為了解決這個問題,我們需要將其中一個參數變成預設值,因此才會使用bind。
那再往下延伸,如果我也不想每次都要使用bind,我只想每次帶入limiter
這個參數,可以嗎?
也是可以,如下:
1 | var checkPastLimitSimplified = function(limiter) { |
甚至可以再更簡化成:
1 | var checkLimiterSimplified = function(limiter) { |