0%

JS核心-(40)-函式以及This的運作-this:call, apply, bind 與 嚴謹模式

前言

我們要介紹的是 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)

我們可以看到運用簡易呼叫的方式, this 的指向當然是指向 Window 物件,並且傳入的參數是 12


call

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

這邊要在執行的 function 後面接上 .call() ,並且第一個參數是給予要讓前面的 function 執行環境中的 this 要指向

哪個對象,通常是給予物件型別的資料,再來後面就是依據傳入該 function要傳入的參數

所以結果會是

那麼在使用 call 的時候要特別注意,他是立刻執行這個函式,跟我們利用簡易呼叫的方式有點像,

主要的差別就是可以透過 call 改變 this 的指向


apply

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

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

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

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


bind

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

先做一些處理。

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

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

就會自動替換 functionthis 指向。


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

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


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

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

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

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

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


進階觀念

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

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

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

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

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

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

var myName = '真心鎮大冒險';

var family = {
    myName: '小明家'
};

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

存檔之後的結果就是

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

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

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

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

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

nullundefined的話,就會被置換成是全域變數。

這樣應該就能夠了解為什麼剛剛傳入的是 undefined,但後來 this 會指向為 window了。


嚴格模式

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

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

  • 加入'use strict'即可運作
  • 並不會影響不支援嚴格模式的瀏覽器
  • 可以依據執行環境設定 'use strict'
  • 透過拋出錯誤的方式消除一些安靜的錯誤 (防止小錯誤)
  • 禁止使用一些有可能被未來版本 ECMAScript 定義的語法

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

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

特定的函式內進行嚴格模式,也就是說只有在特定的大括號內中執行的程式碼才會套用嚴格模式。當然如果在全域的

環境使用的話,你所有的程式碼都會進入嚴格模式

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

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

像上方這個例子就是,如果在嚴格模式下,沒有使用 varletconst 等宣告變數的方式進行宣告的話,他會直接跳錯。

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

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

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


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

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

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

這樣會發生甚麼事情呢 ?

可以看到在嚴格模式下,this 的指向就是我們透過 call 所傳入的 數字 1 ,並且沒有被封裝成建構式的形式

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

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

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

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

傳入 undefined 的時候就還是會維持 undefined

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

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

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

為什麼會這樣呢 ?

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

所以是 undefined


那麼如果在嚴格模式下的話,當然就會是 undefined;如果是非嚴格模式(sloppy mode)下,雖然會自動將 this

自動指向到 window 物件,可是它的本質其實是 undefined。這也是為什麼前面說的,在使用簡易呼叫的時候,

盡量不要去調用他的 this, 因為它的本質其實是 undefined