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