前言
this
在JavaScript代碼中幾乎隨處可見,同時(shí),它也是JavaScript中相對(duì)復(fù)雜的機(jī)制之一熊户。前端開發(fā)者在缺乏清晰認(rèn)識(shí)的情況下,this
完全就是一種魔法吭服,難以捉摸嚷堡。
全局代碼中的this
在這里一切都是簡(jiǎn)單。在全局代碼中艇棕,this始終指向全局對(duì)象本身蝌戒。
// 顯式定義全局對(duì)象的屬性
this.a = 1;
console.log(a); // 1
// 隱式定義全局變量
// 全局變量都是全局對(duì)象的屬性
b = 2;
console.log(this.b); // 2
// 顯式定義全局變量
// 全局變量都是全局對(duì)象的屬性
c = 3;
console.log(this.c); //3
函數(shù)代碼中的this
1 誤解
在函數(shù)代碼中使用this
是很有趣的,人們很容易把this
理解成指向函數(shù)自身沼琉,其實(shí)并不是北苟,讓我們看看下面這段代碼:
function foo(num) {
console.log("foo: " + num);
// 記錄foo被調(diào)用的次數(shù)
this.count++;
}
foo.count = 0;
for (var i = 1; i < 3; i++) {
foo(i);
}
// foo: 1
// foo: 2
// foo被調(diào)用了多少次?
console.log(foo.count); // 0 -- WTF?
console.log
語(yǔ)句確實(shí)輸出了2次刺桃,但是foo.count
仍然是0粹淋,顯然從字面意思來(lái)理解this
是錯(cuò)誤的。
執(zhí)行foo.count = 0
時(shí)瑟慈,確實(shí)向函數(shù)對(duì)象foo
添加了一個(gè)屬性count
桃移。但是函數(shù)內(nèi)部中this.count
的this
并不是指向那個(gè)函數(shù)對(duì)象。那么葛碧,this到底是指向誰(shuí)呢借杰?
2 this到底是什么
this是在運(yùn)行時(shí)進(jìn)行綁定的,并不是編寫時(shí)(定義時(shí))綁定进泼,它綁定規(guī)則取決于函數(shù)調(diào)用時(shí)的各種條件蔗衡。
2.1 默認(rèn)綁定
首先要介紹的是最常用的函數(shù)調(diào)用類型:獨(dú)立函數(shù)調(diào)用纤虽,也就是直接函數(shù)名(參數(shù))
的調(diào)用方式〗实耄可以把這條規(guī)則看作是無(wú)法應(yīng)用其他規(guī)則時(shí)的默認(rèn)規(guī)則:
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2
你應(yīng)該注意到逼纸,聲明在全局作用域的變量var a = 2
(也是全局對(duì)象的屬性)在函數(shù)foo()
調(diào)用時(shí)被打印出來(lái)了!由此我們得知济蝉,默認(rèn)綁定中的this指向全局對(duì)象杰刽。
PS: 如果是在嚴(yán)格模式下,默認(rèn)綁定會(huì)是undefined王滤,而不是全局對(duì)象(不知道嚴(yán)格模式的可暫時(shí)忽略)
2.2 隱式綁定
當(dāng)函數(shù)是作為某個(gè)對(duì)象的方法(其實(shí)都是屬性)來(lái)調(diào)用時(shí)贺嫂,則屬于隱式綁定規(guī)則:
function foo() {
console.log(this.a);
}
var a = 1;
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
這里的函數(shù)foo
是被引用到對(duì)象obj
的屬性中的,然后以對(duì)象.方法()
來(lái)調(diào)用雁乡。此時(shí)我們可以看到this.a
得到obj.a
的值第喳,可以得知this
指向的是obj
(由哪個(gè)對(duì)象調(diào)用則指向哪個(gè)對(duì)象)了
另外注意一個(gè)隱式丟失的問(wèn)題:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函數(shù)別名!
var a = "oops, global"; // a是全局對(duì)象的屬性
bar(); // "oops, global"
雖然bar
是obj.foo
的一個(gè)引用踱稍,但實(shí)際上曲饱,他們兩個(gè)都只是引用foo
函數(shù)本身,因此bar()
只是一個(gè)默認(rèn)綁定的調(diào)用D洹妓忍!
隱式丟失通常會(huì)在回調(diào)函數(shù)中見到注暗,可以用下面說(shuō)到的顯示綁定解決诞帐!
2.3 顯式綁定
當(dāng)函數(shù)是通過(guò)call
和apply
方法進(jìn)行調(diào)用的時(shí)候尉尾,我們稱之為顯示綁定:
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(obj); // 2
foo.apply(obj); // 2
通過(guò)foo.call(obj)
和foo.apply(obj)
深胳,我們就已經(jīng)把foo
中的this
指向了obj
從
this
的綁定角度來(lái)說(shuō)择浊,call
和apply
是一樣的寨闹,它們的區(qū)別體現(xiàn)在其他參數(shù)上础嫡,請(qǐng)自行查看api
另外ES5中提供了bind
方法氛谜,該方法只是用來(lái)綁定函數(shù)的this
指向掏觉,不會(huì)執(zhí)行函數(shù):
function foo(something) {
console.log(this.a, something);
}
var obj = {
a: 2
};
var bar = foo.bind(obj);
bar(3); // 2 3
我們可以看到foo.bind(obj)
只是返回了一個(gè)綁定了this
的函數(shù),因此調(diào)用bar(3)
時(shí)已經(jīng)不是默認(rèn)綁定了值漫,我們也可以利用這個(gè)方法解決上面說(shuō)到的隱式丟失問(wèn)題:
// 隱式丟失原代碼
function foo() {
console.log(this.a);
}
var obj = {
a: 'obj',
foo: foo
};
var a = 'global';
setTimeout(obj.foo, 100); // "global"
// 用bind解決隱式丟失
function foo() {
console.log(this.a);
}
var obj = {
a: 'obj',
foo: foo
};
var a = 'global';
setTimeout(obj.foo.bind(obj), 100); // "obj"
我們可以看到澳腹,原代碼中把obj.foo
作為回調(diào)參數(shù)傳進(jìn)setTimeout
中,this
的指向就丟失了杨何,為什么呢酱塔?我們說(shuō)過(guò)this
是根據(jù)調(diào)用的方式來(lái)確定指向的,這里只是把函數(shù)引用傳進(jìn)去危虱,還沒(méi)有調(diào)用羊娃,內(nèi)部調(diào)用的時(shí)候可能就是應(yīng)用了默認(rèn)綁定規(guī)則,然后this
就指向了全局對(duì)象埃跷,setTimeout
的偽代碼如下:
function setTimeout (fn, delay) {
// 等待delay毫秒
fn(); // 調(diào)用回調(diào)函數(shù)蕊玷,可以看到這里是獨(dú)立調(diào)用函數(shù)
}
而我們把用了bind
進(jìn)行綁定返回的函數(shù)作為回調(diào)參數(shù)邮利,就解決了this
丟失指向的問(wèn)題!
2.4 new綁定
這是最后一條綁定規(guī)則垃帅,使用new
來(lái)調(diào)用構(gòu)造函數(shù)時(shí)延届,就是new綁定規(guī)則:
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
使用new
來(lái)調(diào)用函數(shù),會(huì)執(zhí)行下面的操作:
- 創(chuàng)建(構(gòu)造)一個(gè)新的對(duì)象
- 對(duì)這個(gè)對(duì)象進(jìn)行原型鏈接
bar.__proto__ = foo.prototype
(這不是重點(diǎn)) - 這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的
this
,也就是this.a = a
的this
指向新對(duì)象 - 如果函數(shù)沒(méi)有返回其他對(duì)象贸诚,那么就返回這個(gè)新對(duì)象
總的來(lái)說(shuō)祷愉,使用new
來(lái)調(diào)用foo()
時(shí),函數(shù)中的this
已經(jīng)指向該函數(shù)創(chuàng)建的新對(duì)象了赦颇。
2.5 優(yōu)先級(jí)
現(xiàn)在我們已經(jīng)知道了函數(shù)調(diào)用中this
綁定的四條規(guī)則二鳄,你需要做的就是找到函數(shù)調(diào)用的位置進(jìn)行判斷其應(yīng)用了哪條規(guī)則,你就可以知道this
的指向了媒怯。
但是订讼,如果應(yīng)用了多條規(guī)則,最終會(huì)哪個(gè)規(guī)則勝出呢扇苞?你們可以自行嘗試……這里就不多做說(shuō)明欺殿,直接給出答案。
new綁定 > 顯式綁定 > 隱式綁定 > 默認(rèn)綁定
2.6 ES6中的箭頭函數(shù)
作為ES6中的新姿勢(shì)鳖敷,箭頭函數(shù)的this是凌駕于以上規(guī)則之外的脖苏,是根據(jù)外層作用域來(lái)決定this:
function foo() {
setTimeout(() => {
// 這里的this在此法上繼承自foo()
console.log(this.a);
}, 100);
}
var obj = {
a: 2
};
foo.call(obj); // 2
我們通過(guò)顯式綁定foo
后,內(nèi)部的箭頭函數(shù)的this
也繼承了過(guò)來(lái)定踱。其實(shí)這只是一個(gè)語(yǔ)法糖棍潘,只是用了一個(gè)私有變量來(lái)存儲(chǔ)this
,轉(zhuǎn)換了一下代碼:
function foo() {
var self = this; // 定義了一個(gè)私有變量來(lái)存儲(chǔ)foo的`this`
setTimeout(function() {
console.log(self.a);
}, 100);
}
var obj = {
a: 2
};
foo.call(obj); // 2
不過(guò)寫法上來(lái)說(shuō)還是挺帥氣的~哈哈崖媚!
結(jié)語(yǔ)
總的來(lái)說(shuō)亦歉,只要找到調(diào)用位置,分析其規(guī)則即可正確找到this
畅哑。
第一次寫博文肴楷,可能寫的有點(diǎn)亂,請(qǐng)拿起磚頭往死里拍……
知識(shí)吸取自《你不知道的JavaScript》