L4: Call()、Apply() and Bind()
==
###### tags: `JavaScript`
函數就是物件,有自己的屬性和方法,所有的函數都可以使用Call()、Apply() and Bind()
這三個方法都可以控制this變數要指向誰
:::warning
bind 創造函數的拷貝,讓我們設定this關鍵字
apply 和call 呼叫函數,然後設定this,接著傳入其他參數
:::
``` graphviz
digraph graphname{
T [label="Function(a special type of object)"]
P [label="call()"]
A [label="apply()"]
B [label="bind()"]
C [label="Name", color=red,fontcolor=red ]
D [label="CODE", color=red,fontcolor=red ]
T->P
T->A
T->B
T->C[label="optional,
can be anonymous", color=red,fontcolor=red]
T->D[label="invocable", color=red,fontcolor=red]
}
```
### 範例:this 錯誤指向
建立一個person物件,裡面有兩個屬性和一個方法getFullName
logName函數在物件外面,接受兩個變數lang1 和 lang2,這時如果執行`logName()`會出現錯誤 :`TypeError: this.getFullName is not a function`
**因為這裡的this不是 person物件的方法,this會指向全域物件,全域物件裡面沒有getFullName**
```javascript!
const person = {
firstName: 'John',
lastName: 'Doe',
getFullName: function() {
const fullName = this.firstName + ' ' + this.lastName
return fullName
}
}
const logName = function(lang1, lang2) {
console.log('Logged: ' + this.getFullName())
}
logName()
```
## <font color="#3733FF">範例修正:透過Call()、Apply() and Bind()來控制this指向誰</font>
### 透過bind 修正
:::warning
`.bind()` 不會直接執行函式,`.bind()` 會綁定好 this 之後,回傳完成綁定的函式,因此想要函式的調用結果,需要額外執行它。
:::
使用logName 函數當作物件並呼叫.bind,傳入想要this變數指向的物件,新宣告的 logPersonName 會代表一個函數,透過`.bind()`將 this 綁定到 person 物件上,所以 `logPersonName()` 執行時,this 指向 person 物件
詳細來說,
`.bind()` 會回傳一個新的函數logPersonName(我們給的名稱),是從原本的 logName 函數複製而來的,並且在建立新的函數物件時,設定了 this 的值。因此,當`logPersonName()` 被執行時,JavaScript Engine會注意到它是從 bind 建立的,而將 this 的值設定為 person 物件,而不是依照原本執行環境的 this 值。
這樣做的效果是當logPersonName()執行時,this 會指向 person 物件,並可以存取 person 物件的屬性和方法。
```javascript!
const logName = function(lang1, lang2) {
console.log('Logged: ' + this.getFullName()) // Logged: John Doe
}
const logPersonName = logName.bind(person)
logPersonName()
```
另一個簡單的寫法是直接在function後面加上`.bind(person)`,再呼叫`logNAme()`
因為我傳入`.bind()`,this 現在指向這個函數的拷貝,`this.getFullName()`變成 `preson.getFullName()`
無論我們傳入什麼物件給這個方法 person物件傳入bind,person物件就會是this變數指向的東西
```javascript!
const logName = function(lang1, lang2) {
console.log('Logged: ' + this.getFullName()) // Logged: John Doe
}.bind(person)
logName()
```
---
### 透過call 修正
`.call`會直接執行
傳入call的第一個值,是this要指向的東西,`logName.call(person)`,代表把logName裡面的this指向person
如果需要代入參數,同時又需要使用`.call()`,只需把參數代入 `.call(person),...)` 後面的參數中即可
```javascript!
const logName = function(lang1, lang2) {
console.log('Logged: ' + this.getFullName())
console.log('Arguments: ', lang1 + ' ' + lang2)
}
logName.call(person) // Logged: John Doe , Arguments: undefined undefined
logName.call(person, 'en','es') // Logged: John Doe , Arguments: en es
```
---
### 透過apply 修正
`.apply()` 的使用方式和 `.call()` 一樣,只是當函式需要代入參數時,`.apply()` 後面是==使用陣列的方式來代入這些參數==
```javascript!
logName.call(person,'en','es')
logName.apply(person,['en','es'])
```
甚至也可以使用()包住匿名函數,讓語法解析器知道這不是陳述句,但不用在最後加上括號執行它,而是使用`.apply`或`.call`來立刻呼叫
```javascript
(function(lang1, lang2) {
console.log('Logged: ' + this.getFullName())
console.log('Arguments: ', lang1 + ' ' + lang2)
}).apply(person,['en','es'])
```
### 範例:function borrowing
**借用函數**
建立第二個person物件,但沒有getFullName方法,`person.getFullName`代表呼叫person物件裡面的getFullName方法,用call或apply立刻執行呼叫函數,並把this關鍵字指向person2物件裡的firstName和lastName
使用其他物件裡的方法,好像是我們本來就有的,這就是借用的概念
```javascript!
const person2 = {
firstName: 'Jane',
lastName: 'Doe'
}
console.log(person.getFullName.apply(person2)) // Jane Doe
```
### 範例:function currying
:::warning
Function Currying: Creating a copy of a function but with some preset parameters.
Very useful in mathematical situations
:::
和`.bind`有關,他可以複製一個函數,
建立函數 multiply 用來相乘,在建立一個變數 multipleByTwo 存放`multiply.bind(this, 2)`,如果傳入參數2代表,2會設定為複製函數的參考定值,第一個參數永遠是2,等於 a永遠是2
```javascript!
function multiply(a, b) {
return a * b
}
const multipleByTwo = multiply.bind(this, 2)
```
就像是寫成這樣
```javascript!
function multipleByTwo(b) {
const a = 2
return a * b
}
```
我們可以呼叫函數,`console.log(multipleByTwo(4))`,把4傳入給b值,得到8
- 也可以兩個參數都傳給bind
```javascript!
const multipleByTwo = multiply.bind(this, 2, 4)
console.log(multipleByTwo())
```
- 不想被限制參數,也可以不傳入餐數
```javascript!
const multipleByTwo = multiply.bind(this)
console.log(multipleByTwo(2, 4))
```
建立新的函數,然後用一些預設參數,這叫作 currying