這篇文章我們會來談談call( ), apply( )和bind( )的用法,最後則會應用這三個method來做function borrowing和function currying的應用。
觀念提要
在前幾篇筆記中,我們有提到在Creation Phase的時候,包含Variable Environment, This 和Outer Environment都會被建立,而this有些情況指稱的是global environment、有些時候則是指稱到某個物件 Object。
但是,如果我們希望可以去"調整"某個函式當中this所指稱的對象,這是做得到的嗎?
可以的,我們可以使用call, apply和bind來達到這個目的!
為了要清楚了解這三個function,我們要在說明一下First Class Function的概念。之前我們說明過在JavaScript中,function只是物件的一種特例,function其實包含了兩個隱藏的屬性,一個是name property,用來儲存函式的名稱(也可以是匿名函式);另一個是code property,用來儲存函式當中程式碼的內容。
同樣地,因為函式只是物件的一種類型,所以它也可以包含method(記得嗎?我們會把放在物件裡面的函式稱做method),而這裡我們提到的call, apply和bind也就是function裡面的method。這三個method都可以改變我們在函式中所指稱的this。
先來看一段程式碼
在這段程式碼中,我們建立一個object,裡面包含兩個屬性和一個方法,要留意的地方是,我們在getFullName這個method裡面用到了"this"這個關鍵字。
var person = {
firstname: 'Jeremy',
lastname: 'Lin',
getFullName: function(){
var fullname = this.firstname + ' ' + this.lastname;
return fullname;
}
}
接著我們在寫一個function,這裡面同樣用到"this"。
var logName = function(location1,location2){
console.log('Logged: ' + this.getFullName());
console.log('Arguments: ' + location1 + ' ' + location2);
};
整段程式長這樣,我們在最下面的地方再去執行logName這個函式。
因為在global object根本沒有getFullName這個方法,這個方法是被建立在person這個物件裡面,所以如果我們去執行 logName( ) 勢必會產生錯誤訊息。
但如果我們想要指稱logName function中this所指稱的對象,我們可以怎麼做呢?
使用bind
我們可以使用 .bind這個method,只要在JavaScript中建立的函式,都會預設有bind這個方法在內。使用的方法只要在該function後使用.bind,並於( )的地方代入欲替換成this的物件,所以我們會寫成 logName.bind(person) ,因為我們想要讓物件person代入this的位置。
這裡要留意的地方是,記得我們不是寫 logName().bind(person),因為寫"logName( )"會是函式執行後的結果,而這裡我們要用的是該函式裡面的method。
執行後就可以得到預期的結果:
如果你有興趣的話,也可以在 logName 這個function裡面執行 console.log(this) 就會發現這時候的this,呼叫出來會變成person這個物件。透過 bind 這個 method ,我們就可以讓JavaScript引擎在creation phase的時候,把this代入我們想要的物件。
同樣的,我們一樣可以代入logName裡面的參數,像是這樣子:
我們也可以用另一種寫法,直接在logName函式的後面去執行 bind ,方式如下,可以得到一樣的結果:
這個 bind 的運作的方式,實際上是會複製你原本的function,並且將this代成你指定的物件,所以如果你要執行這個function的話,最後還要記得加上( )。
使用call
再來我們來看看如果有使用 call 這個 method來改變this的值可以怎麼做。
首先,call的用法其實就和括號 ( ) 一樣,都是直接去執行(invoke)這個函式,但是不一樣的地方在於,call的後面可以帶入你想要指定this的物件,接著再放入參數,fun.call(thisArg[, arg1[, arg2[, ...]]])。
使用apply
看過了call之後,其實apply和call的用法大同小異,唯一不同的地方在於,使用apply時,放入參數的地方,應該要放入的是陣列(array)。據老師的說明,這種apply的用法特別常用到有許多算數的地方。
這樣的做法,同樣可以應用成IIFEs的方式:
這裡可以留意一下,bind是複製原本的函式,並且將你所指定的this代入這個函式中,所以如果你要在執行這個函式的話,最後要接上( )來執行該函式;而call和apply則是將你所指定的this直接代入該function中並執行,所以最後面不用在加上( )來執行該函式
來看看兩個實際應用的範例吧!
function borrowing
假設我們現在建立了另一個物件叫做person2,但我想要使用person這個物件裡面的getFullName這個方法時,我們就可以利用上面所提的bind, call或apply來借用person物件裡面getFullName的這個method。
讓我們再來看另一個例子。
我先寫一個function稱做multiply,讓給予的兩個參數相乘,這時候如果我想要讓a這個參數的值變成2,只根據b去代入不同的參數值,我們就可以利用 .bind( ) 來達到function currying的效果。
在上面說明bind的時候,我們並沒有在 bind 後面代入多個參數,但在這裡我們可以寫 .bind(this, 2) 這樣寫的意思,就是我們把a這個參數設定成2,然後複製原本multiply這個function變成multipleByTwo。
這時候,如果我們去執行multipleByTwo這個函式,裡面只需要代入一個參數(也就是原本multiply裡的參數b;a已經預設為2)了,最可以直接得到 x 2的結果。
其實,使用 var multipleByTwo = multiply.bind(this, 2) 這樣的寫法,就和下面這樣的意思是一樣的:
如果我想要設定a , b兩個參數的值也是可以的,只需要寫 var multipleByTwo = multiply.bind(this, 2, 5) ,這時候我就等於把 a 的參數設定成2,把 b 的參數設定成 5 ,如此,不論你在 multipleByTwo裡面代入任何的參數值,最後都會得到10的結果。
我們根據一個function,複製它來創造一個新的function並且賦予它該參數的預設值時,就稱做 function currying ,這種方式很常運用在算數上面。
var person = { firstname: 'Jeremy', lastname: 'Lin', getFullName: function(){ var fullname = this.firstname + ' ' + this.lastname; return fullname; } } var logName = function(location1,location2){ console.log('Logged: ' + this.getFullName()); console.log('Arguments: ' + location1 + ' ' + location2); }; /*use apply*/ //logName.apply(person, ['Taiwan','Japan']); /*use call*/ //logName.call(person, 'Taiwan', 'Japan'); /*use bind*/ // var logPersonName = logName.bind(person); // logPersonName(); /*IIFEs*/ /* (function(location1,location2){ console.log('Logged: ' + this.getFullName()); console.log('Arguments: ' + location1 + ' ' + location2); }).apply(person,['Taiwan','Japan']); */ /*function borrowing*/ var person2 = { firstname: 'Chien-Ming', lastname: 'Wang', } // console.log(person.getFullName.apply(person2)); // console.log(person.getFullName.call(person2)); // console.log(person.getFullName.bind(person2)()); function multiply(a, b){ return a*b; } var multipleByTwo = multiply.bind(this,2); console.log(multipleByTwo(4)); // 2*4 = 8 console.log(multipleByTwo(6)); // 2*6 = 12→回到此系列文章目錄
0 意見:
張貼留言