前言
我們要介紹的是 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 物件,並且傳入的參數是 1 跟 2
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, '小明', '杰倫') 的內容用變數盛裝以後,再利用執行函式的方式執行這個變數。那麼在執行的過程中,
就會自動替換 function 的 this 指向。
如果我們在 fn2() 的小括號中帶入參數的話會怎麼樣嗎 ?
答案是不會,因為我們一開始在定義 fn2 的時候就已經定義好要帶入的參數了。
但是如果一開始沒有定義好帶入的參數的話呢 ?
var fn2 = fn.bind(family, '小明');
fn2(1, 2);
這樣的話,少了一個參數,但我又在 fn2 的小括號中帶入 1 跟 2 的參數,那麼會用哪個參數呢 ?
答案是會從 fn2 傳入的第一個參數當作是 fn 的第二的參數,依序補足不夠的參數。
另外一個要注意的是,雖然
fn2()這樣的形式是 簡易呼叫,但是fn的this在一開始的時候就被指定了,所以這邊就算是用簡易呼叫的方式,fn的this還是會被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 的文件怎麼說:
也就是說,如果再非嚴格模式的狀態下,就會將傳入的非物件型別資料封裝,也就是利用建構式封裝,並且如果傳入的是
null、undefined的話,就會被置換成是全域變數。
這樣應該就能夠了解為什麼剛剛傳入的是 undefined,但後來 this 會指向為 window了。
嚴格模式
因為 Javascript 是相對寬鬆的程式,因此有很多不嚴謹的程式碼也能夠運行,但在維護上就會相對麻煩,並且不容易除錯。
在 ES5 之後就提供了開發者這種語法受限的模式,那麼這個模式下呢就有以下這些特點:
- 加入
'use strict'即可運作 - 並不會影響不支援嚴格模式的瀏覽器
- 可以依據執行環境設定
'use strict' - 透過拋出錯誤的方式消除一些安靜的錯誤 (防止小錯誤)
- 禁止使用一些有可能被未來版本 ECMAScript 定義的語法
嚴格模式,只要在程式碼中加入 'use strict' 的字串就可以。
主要是因為只撰寫 'use strict' 的字串是屬於一種表達式,這個程式碼不會影響不支援嚴格模式的瀏覽器。並且可以加在
特定的函式內進行嚴格模式,也就是說只有在特定的大括號內中執行的程式碼才會套用嚴格模式。當然如果在全域的
環境使用的話,你所有的程式碼都會進入嚴格模式
(function () {
;
// 這個執行環境進入了嚴格模式
})();
在嚴格模式中,透過拋出錯誤的方式來提醒開發者一些安靜的錯誤,也可以避免掉一些開發的不好習慣,同時也有利於偵錯。
像上方這個例子就是,如果在嚴格模式下,沒有使用 var 、 let 、 const 等宣告變數的方式進行宣告的話,他會直接跳錯。
在嚴格模式下,他不允許你直接對變數賦予值,他會要你先宣告這個變數之後,再對變數賦予值。
其他更詳細,有關嚴格模式的內容,可以參考 MDN 的文件
特別注意:在嚴格模式的狀態下,每個瀏覽器的結果可能會有不同,所以有時候文件跟實際的狀況有出入也是屬於正常的。
接著我們再來看一下嚴格模式的其他例子
function callStrict (para1, para2) {
;
console.log(this, typeof(this), para1, para2);
}
callStrict.call(1, '小明', '杰倫');
這樣會發生甚麼事情呢 ?
可以看到在嚴格模式下,this 的指向就是我們透過 call 所傳入的 數字 1 ,並且沒有被封裝成建構式的形式
那再來看看傳入 undefined 給大家看看
function callStrict (para1, para2) {
;
console.log(this, typeof(this), para1, para2);
}
callStrict.call(undefined, '小明', '杰倫');
原本我們在非嚴格模式(sloppy mode)下,傳入 undefined 的話就會將 this 指向全域的window 物件。那麼再嚴格模式下,
傳入 undefined 的時候就還是會維持 undefined
那麼我如果簡易呼叫 callStrict 的話會怎麼樣呢 ?
function callStrict (para1, para2) {
;
console.log(this, typeof(this), para1, para2);
}
callStrict('小明', '杰倫');
為什麼會這樣呢 ?
其實你使用簡易呼叫的時候,就等於是直接套用 call 這個方式進行呼叫,只是我們第一個參數沒有傳入,
所以是 undefined。
那麼如果在嚴格模式下的話,當然就會是 undefined;如果是非嚴格模式(sloppy mode)下,雖然會自動將 this
自動指向到 window 物件,可是它的本質其實是 undefined。這也是為什麼前面說的,在使用簡易呼叫的時候,
盡量不要去調用他的 this, 因為它的本質其實是 undefined