前言
前段時間,看了《你不知道的JavaScript》中this的全面解析,講的特別好,還沒看過的小伙伴抓緊去學(xué)習(xí),在這邊特意整理總結(jié)了一波,一起分享學(xué)習(xí)。在這之前可以先理解一下關(guān)于this、call、applay和bind這篇文章。
調(diào)用棧
在了解this
綁定原理之前,首頁要先了解JavaScript
的執(zhí)行棧。(執(zhí)行棧也叫做調(diào)用棧)如果對調(diào)用棧還不了解,可以先詳細(xì)了解深入淺出JavaScript執(zhí)行上下文和執(zhí)行棧。
JavaScript
是單線程的,所有這決定了同一時間只能做一件事情,其他的活動或事情只能排隊等候了,于是就生成出一個等候隊列的執(zhí)行棧(Execution Stack)。簡單來說,執(zhí)行棧就是為了到達(dá)當(dāng)前執(zhí)行位置所調(diào)用的所有函數(shù)。而調(diào)用位置就在當(dāng)前正在執(zhí)行的函數(shù)的前一個調(diào)用中。
function baz() {
// 當(dāng)前執(zhí)行棧是:baz
// 調(diào)用位置是baz前一個調(diào)用中,因此是全局作用域
console.log('baz');
bar();
}
function bar() {
// 當(dāng)前執(zhí)行棧是:bar
// 調(diào)用位置是bar前一個調(diào)用中,因此是baz
onsole.log('bar');
}
baz() // baz的調(diào)用位置
根據(jù)上面的規(guī)則,則不難判斷出上述代碼的調(diào)用棧和調(diào)用位置。而調(diào)用棧和調(diào)用位置則決定了this
的綁定對象。其綁定規(guī)則有四種,只有找到調(diào)用位置,才能判斷需要應(yīng)用四條規(guī)則中的哪一條。
默認(rèn)綁定
在非嚴(yán)格模式下,this
指向全局對象,嚴(yán)格模式下this
則會指向undefined
function foo() {
console.log(this.a); // this指向全局對象
}
var a = 2;
foo(); // 2
function foo2() {
"use strict"; // 嚴(yán)格模式this綁定到undefined
console.log(this.a);
}
foo2(); // TypeError:a undefined
但是如果在嚴(yán)格模式下調(diào)用其他函數(shù),則不影響默認(rèn)綁定。
function foo() {
console.log(this.a); // foo函數(shù)不是嚴(yán)格模式 默認(rèn)綁定全局對象
}
var a = 2;
function foo2(){
"use strict";
foo(); // 嚴(yán)格模式下調(diào)用其他函數(shù),不影響默認(rèn)綁定
}
foo2();
上述為獨立的函數(shù)調(diào)用,其調(diào)位位置為全局作用域,所有this
綁定在全局作用域上。
隱式綁定
函數(shù)在調(diào)用位置,是否有上下文對象,如果有,那么this
就會隱式綁定到這個對象上。
也可以簡單理解為是否被某個對象包含了。
function foo() {
console.log(this.a);
}
var a = "Oops, global";
let obj2 = {
a: 2,
foo: foo
};
let obj1 = {
a: 22,
obj2: obj2
};
obj2.foo(); // 2 this指向調(diào)用函數(shù)的對象
obj1.obj2.foo(); // 2 this指向最后一層調(diào)用函數(shù)的對象
// 隱式綁定丟失
let bar = obj2.foo; // bar只是一個函數(shù)別名 是obj2.foo的一個引用
bar(); // "Oops, global" - 指向全局
上述代碼中函數(shù)foo
調(diào)用位置在obj2
上下文中,所有this
綁定在obj2
作用域中,所以obj2.foo()
、obj1.obj2.foo()
最終都為2,而let bar = obj2.foo
實際上就是函數(shù)的引用賦給變量bar
,調(diào)用時,并沒有上下文對象,所以會導(dǎo)致隱式綁定丟失。
顯式綁定
通過apply
、call
、bind
將函數(shù)中的this
強制綁定到指定對象上。
function foo() {
console.log(this.a);
}
let obj = {
a: 2
};
foo.call(obj); // 2
需要注意的是:
- 如果傳入了一個原始值(字符串,布爾類型,數(shù)字類型),來當(dāng)做
this
的綁定對象,這個原始值會轉(zhuǎn)換成它的對象形式。 - 如果把
null
或者undefined
作為this
的綁定對象傳入call
、apply
、bind
,這些值會在調(diào)用時被忽略,實際應(yīng)用的是默認(rèn)綁定規(guī)則。
new綁定
如果對
new
關(guān)鍵字不太了解,可以先看這篇關(guān)于new命令。
使用構(gòu)造調(diào)用的時候,this會自動綁定在new期間創(chuàng)建的對象上。
function foo(a) {
this.a = a; // this綁定到bar上
}
let bar = new foo(2);
console.log(bar.a); // 2
四種綁定規(guī)則的優(yōu)先級
- 顯式綁定 > 隱式綁定 > 默認(rèn)綁定
- new綁定 > 隱式綁定 > 默認(rèn)綁定
function foo() {
this.a = 100;
}
var obj1 ={
foo: foo;
}
var obj2 = {}
obj1.foo.call(obj2, 2); // 2 this指向obj2 顯式綁定比隱式綁定優(yōu)先級高。
new obj1.foo(4); // thsi指向new新創(chuàng)建的對象 new綁定比隱式綁定優(yōu)先級高。
顯式綁定和new綁定無法直接比較(會報錯),默認(rèn)綁定是不應(yīng)用其他規(guī)則之后的兜底綁定所以優(yōu)先級最低。
箭頭函數(shù)this指向
箭頭函數(shù)this不會使用這四條綁定規(guī)則。
function foo() {
return (a) => {
// this繼承自foo
console.log(this.a);
};
}
let obj1 = {
a: 2
};
let obj2 = {
a: 3
};
let bar = foo.call(obj1); // foo this指向obj1
bar.call(obj2); // 輸出2 這里執(zhí)行箭頭函數(shù) 并試圖綁定this指向到obj2
從上述可以得出,箭頭函數(shù)的this
規(guī)則:
- 箭頭函數(shù)中的
this
繼承于它外面第一個不是箭頭函數(shù)的函數(shù)的this
指向。如果沒有則指向全局。 - 箭頭函數(shù)的
this
一旦綁定了上下文,就不會被任何代碼改變。
小結(jié)
如果要判斷一個運行中函數(shù)this
綁定,就需要找到這個函數(shù)直接調(diào)用位置。找到之后就可以順序應(yīng)用下面四條規(guī)則來判讀this
綁定對象。
- 有
new
調(diào)用,綁定到新創(chuàng)建對象。 - 由
call
或者apply
、bind
調(diào)用,綁定到指定對象。 - 由上下文對象調(diào)用,綁定到那個上下文對象。
- 默認(rèn):在嚴(yán)格模式綁定到
undefined
,否則綁定到全局
箭頭并不適用與上述四條規(guī)則,而是由他的外層函數(shù)繼承而來。
更多優(yōu)質(zhì)文章可以訪問GitHub博客,歡迎帥哥美女前來Star!!!