2016年6月15日

[筆記] 了解function borrowing和function currying ─ bind(), call(), apply() 的應用


這篇文章我們會來談談call( ), apply( )和bind( )的用法,最後則會應用這三個method來做function borrowing和function currying的應用。

觀念提要


在前幾篇筆記中,我們有提到在Creation Phase的時候,包含Variable Environment, This 和Outer Environment都會被建立,而this有些情況指稱的是global environment、有些時候則是指稱到某個物件 Object。

但是,如果我們希望可以去"調整"某個函式當中this所指稱的對象,這是做得到的嗎?

可以的,我們可以使用call, applybind來達到這個目的!

為了要清楚了解這三個function,我們要在說明一下First Class Function的概念。之前我們說明過在JavaScript中,function只是物件的一種特例,function其實包含了兩個隱藏的屬性,一個是name property,用來儲存函式的名稱(也可以是匿名函式);另一個是code property,用來儲存函式當中程式碼的內容。

同樣地,因為函式只是物件的一種類型,所以它也可以包含method(記得嗎?我們會把放在物件裡面的函式稱做method),而這裡我們提到的callapplybind也就是function裡面的method。這三個method都可以改變我們在函式中所指稱的this。


先來看一段程式碼


在這段程式碼中,我們建立一個object,裡面包含兩個屬性和一個方法,要留意的地方是,我們在getFullName這個method裡面用到了"this"這個關鍵字。


var person = {
    firstname: 'Jeremy',
    lastname: 'Lin',
    getFullName: function(){
  
        var fullname = this.firstname + ' ' + this.lastname;
       return fullname;

    }
}


這裡,如果你對this的觀念夠清楚的話,應該會知道這個this會指稱到person這個object。如果還是不太確定的話,你也可以在getFullName裡面寫上console.log(this);來看一下。

接著我們在寫一個function,這裡面同樣用到"this"。

var logName = function(location1,location2){

    console.log('Logged: ' + this.getFullName());
    console.log('Arguments: ' + location1 + ' ' + location2);

};

同樣地,如果對於this的觀念夠清楚的話,這個this會指稱到的是window object(global object)。

整段程式長這樣,我們在最下面的地方再去執行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 currying

讓我們再來看另一個例子。

我先寫一個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 意見:

張貼留言