前言
這節課將來討論 JavaScript 中的三個方法 call()
、apple()
、bind()
,而這三個東西與我們前面所講的東西有關係。
在探討這三個方法之前必須先瞭解什麼一級函數,一開始我們已經知道一級函數是特殊形態
- 它可以有名稱及沒有名稱 (也就是匿名函數)
- 它有程式區塊,裡面可以撰寫程式碼,然後程式是可以被呼叫
- 函數就是物件,所以本身也可以有屬性及方法
- 所有函數都包含著
call()
、apply()
與 bind()
,而這三個都與 this
以及我們傳入的參數有關。
![一級函數]()
call()、apply()與 bind()
接下來我們試著連跟著課程範例了解。
1 2 3 4 5 6 7 8
| var person = { fistname: 'John', lastname: 'Doe', getFullName: function() { var fullname = this.fistname + ' ' + this.lastname; return fullname; }, };
|
那 call()、apply()與 bind() 該怎麼使用呢?假設我們有一個 logName
匿名函數
1 2 3 4 5
| var logName = function (lang1, lang2) { console.log('Logged' + this.getFullName()); }
logName();
|
課程來看,其實我們大概都可以知道這樣會出現錯誤,因為 this
指向的是 window
,而 window
下並沒有一個函數叫 getFullName
。
![圖片]()
bind
但是如果今天我們可以控制 this 指向誰的話呢?所以這邊就要試著使用看看 bind
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var person = { fistname: 'John', lastname: 'Doe', getFullName: function() { var fullname = this.fistname + ' ' + this.lastname; return fullname; }, };
var logName = function (lang1, lang2) { console.log('Logged' + this.getFullName()); }
var logPersonName = logName.bind(person)
logPersonName();
|
bind 這個方法會回傳新的函數,當 JavaScript 看到 bind 將會重新指向 this 到傳入 bind 方法的東西。
![圖片]()
所以 bind 創造任何我們呼叫的函數拷貝,不論我們傳入函數還是物件都可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var person = { fistname: 'John', lastname: 'Doe', getFullName: function() { var fullname = this.fistname + ' ' + this.lastname; return fullname; }, };
var logName = function (lang1, lang2) { console.log('Logged: ' + this.getFullName()); console.log('Arguments: ' + lang1 + ' ' + lang2) console.log('-----------'); }
var logPersonName = logName.bind(person)
logPersonName(); logPersonName('en');
|
![圖片]()
call
接下來看 call
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var person = { fistname: 'John', lastname: 'Doe', getFullName: function() { var fullname = this.fistname + ' ' + this.lastname; return fullname; }, };
var logName = function (lang1, lang2) { console.log('Logged: ' + this.getFullName()); console.log('Arguments: ' + lang1 + ' ' + lang2) console.log('-----------'); }
logName.call(person)
|
![圖片]()
call 與 bind 一樣可以傳入要拷貝的函數,但是可以傳入參數並執行他
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var person = { fistname: 'John', lastname: 'Doe', getFullName: function() { var fullname = this.fistname + ' ' + this.lastname; return fullname; }, };
var logName = function (lang1, lang2) { console.log('Logged: ' + this.getFullName()); console.log('Arguments: ' + lang1 + ' ' + lang2) console.log('-----------'); }
logName.call(person,'en','es')
|
![圖片]()
bind 只是單純拷貝並沒有執行函數,但 call 可以拷貝並執行函數。
apply
apply 呢?與 call 非常像。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var person = { fistname: 'John', lastname: 'Doe', getFullName: function() { var fullname = this.fistname + ' ' + this.lastname; return fullname; }, };
var logName = function (lang1, lang2) { console.log('Logged: ' + this.getFullName()); console.log('Arguments: ' + lang1 + ' ' + lang2) console.log('-----------'); }
logName.apply(person,'en','es');
|
但是如果我們試著跟 call 一樣傳入參數就會出現錯誤。
![圖片]()
所以應該怎麼使用 apply?,apply 。主要接受的參數是陣列,而不是普通的字串、數值等等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var person = { fistname: 'John', lastname: 'Doe', getFullName: function() { var fullname = this.fistname + ' ' + this.lastname; return fullname; }, };
var logName = function (lang1, lang2) { console.log('Logged: ' + this.getFullName()); console.log('Arguments: ' + lang1 + ' ' + lang2); console.log('-----------'); }
logName.apply(person, ['en', 'es']);
|
![圖片]()
我們可以從上面發現 apply 非常強大,在傳入參數後會自動幫我們對應載入相對應的參數。
所以我們這邊也可以透過匿名函數 + apply or call 立刻執行呼叫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| var person = { fistname: 'John', lastname: 'Doe', getFullName: function() { var fullname = this.fistname + ' ' + this.lastname; return fullname; }, };
(function (lang1, lang2) { console.log('Logged: ' + this.getFullName()); console.log('Arguments: ' + lang1 + ' ' + lang2); console.log('-----------'); }).apply(person, ['en', 'es']);
(function (lang1, lang2) { console.log('Logged: ' + this.getFullName()); console.log('Arguments: ' + lang1 + ' ' + lang2); console.log('-----------'); }).call(person,'en','es');
|
![圖片]()
真實例子
那我們該如何套用這些到真實例子上?
函數借用 (function borrowing)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var person = { fistname: 'John', lastname: 'Doe', getFullName: function() { var fullname = this.fistname + ' ' + this.lastname; return fullname; }, };
var person2 = { fistname: 'Jack', lastname: 'Mary', };
person.getFullName.apply(person2);
|
![圖片]()
在這個例子我們可以將原有 person
底下的函數 (getFullName) this
轉指到 person2
。
而 call 也是一樣。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var person = { fistname: 'John', lastname: 'Doe', getFullName: function() { var fullname = this.fistname + ' ' + this.lastname; return fullname; }, };
var person2 = { fistname: 'Jack', lastname: 'Mary', };
person.getFullName.call(person2);
|
![圖片]()
function currying
這個例子與 bind 有關,在前面使用 call 及 apply 傳入參數就只是傳入參數,但是 bind 可以創造函數拷貝。
1 2 3 4 5 6
| function multiply(a, b) { return a * b; };
var multpleByTwo = multiply.bind(this, 2); multpleByTwo();
|
注意 bind 只會拷貝並不會執行,但給他的參數將會被設定成拷貝函數的永久參數值,所以當我們第一個參數放 2 代表永遠都是 2 , a 永遠都是 2。
![圖片]()
此時你可能會問怎麼是 NaN?試著思考一下變數的預設值是什麼 undefined,當 2 * undefined 那當然會是 NaN,這並不是一個數值,所以我們可以在執行函數時傳入數字。
1 2 3 4 5 6
| function multiply(a, b) { return a * b; };
var multpleByTwo = multiply.bind(this, 2); multpleByTwo(3);
|
![圖片]()
當然我們可以看一下 a 跟 b 在還沒傳入參數與傳入參數後的狀況是怎樣。
1 2 3 4 5 6 7 8
| function multiply(a, b) { console.log('a: ' + a); console.log('b: ' + b); return a * b; };
var multpleByTwo = multiply.bind(this, 2); multpleByTwo(3);
|
![圖片]()
1 2 3 4 5 6 7 8
| function multiply(a, b) { console.log('a: ' + a); console.log('b: ' + b); return a * b; };
var multpleByTwo = multiply.bind(this, 2); multpleByTwo();
|
![圖片]()
所以如果我們將 bind 傳入兩個參數,那你就知道了 :D
![圖片]()
function currying 建立一個函數拷貝,並設定預設的參數。
圖源
JavaScript 全攻略:克服 JS 奇怪的部分