iT邦幫忙

0

Javascript 進階 5-8 this:call, apply, bind 與 嚴謹模式

這篇文章要來介紹三種會影響 this 的方法

這是我們一開始的程式碼

var myName = '真心鎮大冒險';
        
var family = {
    myName: '小明家'
};

function fn (para1, para2) {
    console.log(this, para1, para2);
}

那麼我們要介紹的是 call, apply, bind 這三種方法,來改變 this 的指向

這三個的方法觀念都很相近,只是呼叫方法上不太一樣,我們來看看上一個章節我們提到的 簡易呼叫(Simple Call),利用這個 fn 的函式印出來的結果是如何?

var myName = '真心鎮大冒險';
        
var family = {
    myName: '小明家'
};

function fn (para1, para2) {
    console.log(this, para1, para2);
}

fn(1, 2); // 簡易呼叫(Simple Call)

https://ithelp.ithome.com.tw/upload/images/20200130/20121770c1OXr4vxAo.png

我們可以看到運用 簡易呼叫(Simple Call) 的方式, this 的指向當然是指向 Window 物件。並且傳入的參數是 1 跟 2,這個應該沒甚麼問題。

call

fn.call(family, 1, 2);
// fn.call(要讓 this 指向的對象, 參數1, 參數2);

這邊再要執行的function後面接上 .call(),並且第一個參數是給予要讓前面的 function 執行環境中的 this 要指向哪個對象,通常是給予物件型別的資料,再來後面就是依據傳入該 function要傳入的參數。

所以結果會是

https://ithelp.ithome.com.tw/upload/images/20200130/20121770PSuL0LZkf7.png

那麼在使用 call 的時候要特別注意,他是立刻執行這個函式,跟我們利用簡易呼叫(Simple Call)的方式有點像,主要的差別就是可以透過 call 改變 this 的指向

apply

apply 的跟 call 很像,只是傳入參數給 function 的形式不太一樣

fn.apply(family, [3, 4]);
// fn.apply(要讓 this 指向的對象, [參數1, 參數2]);

使用 apply 方法的時候,要傳入的參數必須包在一個陣列裡面,當作第二個參數傳入。

同樣也是立即執行,第一個參數也是用來改變執行環境的 this 指向。

https://ithelp.ithome.com.tw/upload/images/20200130/20121770vADku7QHJ0.png

bind

bind的方法跟call, apply的主要差異在於,他不會立刻執行前面的 function,因此要使用bind的方法的時候呢,必須先做一些處理。

var fn2 = fn.bind(family, '小明', '杰倫');
fn2();

先把 fn.bind(family, '小明', '杰倫') 的內容用變數盛裝以後,再利用執行函式的方式執行這個變數。

那麼在執行的過程中,就會自動替換 function 的 this 指向。

那麼在 fn2() 的小括號中帶入參數的話會怎麼樣嗎?

答案是不會,因為我們一開始在定義 fn2 的時候就已經定義好要帶入的參數了。

但是如果一開始沒有定義好帶入的參數的話呢?

var fn2 = fn.bind(family, '小明');
fn2(1, 2);

這樣的話,少了一個參數,但我又在fn2的小括號中帶入1跟2的參數,那麼會用哪個參數呢?

https://ithelp.ithome.com.tw/upload/images/20200130/20121770UFlYsylLBB.png

答案是會從 fn2 傳入的第一個參數當作是 fn 的第二的參數,依序補足不夠的參數。

另外一個要注意的是,雖然 fn2() 這樣的形式是 簡易呼叫(Simple Call),但是 fn 的 this 在一開始的時候就被指定了,所以這邊就算是用簡易呼叫(Simple Call)的方式, fn 的this 還是會被 bind 綁定給指定的物件。

進階觀念

到目前為止,我們知道可以透過 call, apply, bind 來改變 this 的指向。

那麼如果是下面這樣的狀況,會發生甚麼事情呢?

fn.call(1, '小明', '杰倫');

https://ithelp.ithome.com.tw/upload/images/20200130/20121770UaO5jzock3.png

如果是傳入其他非物件型別的原始型別資料,會自動被轉換成建構式方式的物件型別。

fn.call('文字', '小明', '杰倫');

https://ithelp.ithome.com.tw/upload/images/20200130/20121770AEZbT6Wv84.png

我們再將 fn 多加上一個 typeof(this)

var myName = '真心鎮大冒險';
        
var family = {
    myName: '小明家'
};

function fn (para1, para2) {
    console.log(this, typeof(this), para1, para2);
}

存檔之後的結果就是

https://ithelp.ithome.com.tw/upload/images/20200130/20121770FahHSsuzMJ.png

那我們再來傳一次不同的值看看

fn.call(undefined, '小明', '杰倫');

https://ithelp.ithome.com.tw/upload/images/20200130/20121770Ar0yhNrRfb.png

沒想到傳入了 undefined,居然會直接被指向到 window?!?!?1

為甚麼呢?我們來看一下 MDN的文件 怎麼說:

https://ithelp.ithome.com.tw/upload/images/20200130/20121770AW8DjMTcOA.png

也就是說,如果再 非嚴格模式 的狀態下,就會將傳入的非物件型別資料封裝,也就是利用建構式封裝。

並且如果傳入的是 null undefined的話,就會被置換成是全域變數。

喔喔~這樣應該就能夠了解為什麼剛剛傳入的是 undefined。

但後來 this 會指向為 window了。

但到底甚麼是嚴格模式阿???

嚴格模式

https://ithelp.ithome.com.tw/upload/images/20200222/20121770tHyxEIu7LL.png

因為 Javascript 是相對寬鬆的程式,因此有很多不嚴謹的程式碼也能夠運行,但在維護上就會相對麻煩,並且不容易除錯。

在 ES5 之後就提供了開發者這種語法受限的模式,那麼這個模式下呢就有以下這些特點

https://ithelp.ithome.com.tw/upload/images/20200222/20121770u3bOxwkaCC.png

嚴格模式呢,只要在程式碼中加入 'use strict' 的字串就可以。

主要是因為只撰寫 'use strict' 的字串 是屬於一種表達式,那麼這個程式碼不會影響不支援嚴格模式的瀏覽器。

並且可以加在特定的函式內進行嚴格模式,也就是說只有在特定的大括號內中執行的程式碼才會套用嚴格模式。當然如果在全域的環境使用的話,你所有的程式碼都會進入嚴格模式喔!

(function () {
    'use strict';
    // 這個執行環境進入了嚴格模式
})();

那麼在嚴格模式中,透過拋出錯誤的方式來提醒開發者一些安靜的錯誤。也可以避免掉一些開發的不好習慣,同時也有利於偵錯。

https://ithelp.ithome.com.tw/upload/images/20200222/20121770m3edkNlHoS.png

其中一個例子就是,如果在嚴格模式下,沒有使用 var let const 等宣告變數的方式進行宣告的話,他會直接跳錯。

在嚴格模式下呢,他不允許你直接對變數賦予值,他會要你先宣告這個變數之後,再對變數賦予值。

那麼其他更詳細,有關嚴格模式的內容,可以參考 MDN的文件

不過要特別注意的是,在嚴格模式的狀態下,每個瀏覽器的結果可能會有不同,所以有時候文件跟實際的狀況有出入也是屬於正常的。要特別有心理準備~XDD

那麼接著我們再來看一下嚴格模式的其他例子吧~

function callStrict (para1, para2) {
    'use strict';
    console.log(this, typeof(this), para1, para2);
}

callStrict.call(1, '小明', '杰倫');

這樣會發生甚麼事情呢?

https://ithelp.ithome.com.tw/upload/images/20200222/2012177019yWf6VkSR.png

可以看到在嚴格模式下,this 的指向就是我們透過 call 所傳入的 數字1。

並且沒有被封裝成建構式的形式!

那再來看看傳入 undefined 給大家看看

callStrict.call(undefined, '小明', '杰倫');

https://ithelp.ithome.com.tw/upload/images/20200222/201217709Nce6zFEQQ.png

原本我們再非嚴格模式(sloppy mode)下,傳入 undefined 的話就會將 this 指向全域的window 物件。

那麼再嚴格模式下,傳入 undefined 的時候就還是會維持 undefined。

那麼我如果簡易呼叫 callStrict 的話會怎麼樣呢?

callStrict('小明', '杰倫');

https://ithelp.ithome.com.tw/upload/images/20200222/20121770jHOFdxi2nH.png

那麼為什麼會這樣呢?

其實你使用簡易呼叫的時候,就等於是直接套用call這個方式進行呼叫。只是我們第一個參數沒有傳入,所以是 undefined。

那麼如果再嚴格模式下的話,當然就會是 undefined;如果是非嚴格模式(sloppy mode)下,雖然會自動將 this 自動指向到 window 物件,可是它的本質其實是 undefined。

這也是為什麼前面說的,在使用簡易呼叫的時候,盡量不要去調用他的 this, 因為它的本質其實是 undefined。

好~那麼這篇文章就介紹到這邊,沒問題就往下繼續看下去吧~!汪汪


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言