iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 23
0
Modern Web

JavaScript基礎二三事系列 第 23

Day23 函式內建方法:bind()、call()與apply()

今天來看看bind()call()apply()這三個函式內建方法。

當函式執行環境被創造出來,會一併創造arguments關鍵字,保存帶入自己的參數;
也會一併創造出this關鍵字,指向函數目前所處的物件,關於this可以參考這天的筆記

若我們希望修改this指向的對象,有辦法達到這個目的嗎?

我們可以利用bind()call()apply()這些函式內建方法來達成目的

先來看看bind()的例子

var superMan = {
    earthName: 'Clark Kent',
    KryptonName: 'Kal-El',
    getAllName: function(){
  
       var allname = this.earthName + ' and ' + this.KryptonName;
       return allname;

    }
}
console.log(superMan.getAllName())

結果是

接著在設定一個函式表示式logName

var logName = function(log1,log2){
	console.log('Logged:' + this.getAllName())
	console.log('Arguments:' + log1 + ' ' + log2)
	console.log(this)
}

直接呼叫logName函式會發生錯誤,因為第一個console.log()裡的this.getAllName()有問題,現在logName函式的this指向全域,而不是superMan指向的物件。

logName();

現在可以利用bind的方法來解決這個問題:

var logPersonName = logName.bind(superMan);
logPersonName('en');

這裡logName用了.運算子去取用函式內建方法bind(),同時把superMan當參數傳入,此時電腦記憶體會複製一個logName函式,這個複製版logName函式的this指向superMan物件,也就是說,變數logPersonName指向的是新的(另一個)logName函式。
 
接著呼叫並帶入參數'en'
結果是:

現在logPersonName函式,this指向物件superMan而不是全域,所以可以順利印出Clark Kent and Kal-El,而en undefinedundefined,是因為我們只有傳一個參數en進去的緣故,另一個參數自然就是undefined囉。

logPersonName函式(logName函式複製版)的this指向superMan,那...原本的logName函式有受到影響嗎?再次呼叫執行logName()看看

logName();

可以看到logName函式還是不受影響,因為用bind()綁定this的函式...也就是變數logPersonName指向的函式,並不是真正的logName,而是logName函式的複製版本。

也就是說bind()可以創造函式的拷貝版,並且可以透過傳入物件來綁定(指定)它的this是誰

 
 

接著來看看call()這個內建方法
call()bind()有點類似,可以透過傳入物件來綁定(指定)它的this是誰,但call()會馬上執行該函式,所以在()傳入物件當參數綁定時,也可以一併傳入這個函式執行所需的其他參數。

將上面bind例子修改成call版本

var superMan = {
    earthName: 'Clark Kent',
    KryptonName: 'Kal-El',
    getAllName: function(){
  
       var allname = this.earthName + ' and ' + this.KryptonName;
       return allname;

    }
}

var logName = function(log1,log2){
  console.log('Logged:' + this.getAllName())
  console.log('Arguments:' + log1 + ' ' + log2)
  console.log(this)
}

logName.call(superMan);

一執行時就印出東西來,因為call()會馬上執行使用它的函式,我們也可以帶入函式所需參數

logName.call(superMan,'en','es');

也就是說

同樣修改this對象,bind()會複製函數,但不會馬上執行,call()則會直接執行

至於logName,再次單獨呼叫看看,它的this會指向誰呢?

logName();


 
 

接著再看看apply()這個內建方法

apply()也可以透過傳入物件來綁定(指定)它的this是誰,並且和call()一樣會直接執行呼叫它的函式,使用時也可以帶入函式所需參數,只是這個參數必須是陣列形式。

將bind的例子修改成apply版本

var superMan = {
    earthName: 'Clark Kent',
    KryptonName: 'Kal-El',
    getAllName: function(){
  
       var allname = this.earthName + ' and ' + this.KryptonName;
       return allname;

    }
}

var logName = function(log1,log2){
  console.log('Logged:' + this.getAllName())
  console.log('Arguments:' + log1 + ' ' + log2)
  console.log(this)
}

logName.apply(superMan,'en','es');

執行結果是

這是因為使用apply()除了綁定的物件外,其他傳入的參數必須是陣列,所以要改成這樣

logName.apply(superMan,['en','es']);

至於原本的logName,我們一樣呼叫看看,它的this會指向誰呢?

logName();

 
 

恩...那麼什麼時候會用到bind()call()apply()呢?

影片裡有兩個例子:

function borrowing 可以跟別的物件借方法來操作

我們再創建一個物件superMan2,和物件superMan一樣,只是少了一個getAllName

var superMan = {
    earthName: 'Clark Kent',
    KryptonName: 'Kal-El',
    getAllName: function(){
  
       var allname = this.earthName + ' and ' + this.KryptonName;
       return allname;

    }
}

var superMan2 = {
    earthName: 'Clark Kent',
    KryptonName: 'Kal-El'
}

當superMan2物件也需要用到函式getAllName時,就可以跟superMan「借」來用:

console.log(superMan.getAllName.apply(superMan2));

function currying
建立一個函式的拷貝,並設定要傳入的參數預設值,有許多數學運算需處理時很有妙用

bind()除了可以帶入欲綁定this的物件,也可以帶入函式的參數,但bind帶入的參數會成為這個函式參數的預設值,例如:

function multiply(a, b){
  return a*b;
}

var multiplyByTwo = multiply.bind(this, 2); 
console.log(multiplyByTwo(4)); 

multiply.bind(this, 2);的this,就是欲綁定this的物件(在這裡範例沒什麼作用),反倒是2,會對應到multiply(a, b)函式的a,所以綁定後的函式只要帶入一個參數,這個參數就會對應到multiply(a, b)的b。

結果是

 
 
 
 
 
小結
平常頂多用變數保留this,以解決this指向的問題,而不會特別改變this指向的對象,至少目前是如此啦。
這節影片看下來,覺得若要撰寫框架、函式庫、plugins才會頻繁用到這幾個方法,尤其克服JS的奇怪部分,在第七章以後會進入研究框架的章節,或許講師到時候會介紹它們的其他用法。

今天的筆記內容可以參照Udemy課程:JavaScript 全攻略:克服JS的奇怪部分4-50


上一篇
Day22 Function Factories、閉包與回呼
下一篇
Day24 函數程式設計
系列文
JavaScript基礎二三事30

尚未有邦友留言

立即登入留言