與其他語言相比,函數(shù)的this關(guān)鍵字在JavaScript中的表現(xiàn)略有不同洒放,此外弛车,在嚴格模式和非嚴格模式之間也會有一些差別齐媒。
在絕大多數(shù)情況下,函數(shù)的調(diào)用方式?jīng)Q定了this的值纷跛。this不能在執(zhí)行期間被賦值喻括,并且在每次函數(shù)被調(diào)用時this的值也可能會不同。ES5引入了bind方法來設(shè)置函數(shù)的this值贫奠,而不用考慮函數(shù)如何被調(diào)用的唬血,ES2015引入了支持this詞法解析的箭頭函數(shù)(它在閉合的執(zhí)行上下文內(nèi)設(shè)置this的值)。
語法
this
全局上下文
無論是否在嚴格模式下唤崭,在全局執(zhí)行上下文中(在任何函數(shù)體外部)this都指代全局對象刁品。
// 在瀏覽器中, window 對象同時也是全局對象:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b) //"MDN"
console.log(b) //"MDN"
函數(shù)上下文
在函數(shù)內(nèi)部,this的值取決于函數(shù)被調(diào)用的方式浩姥。
1. 直接調(diào)用
因為下面的代碼不是在嚴格模式下執(zhí)行挑随,且this的值不是通過調(diào)用設(shè)置的,所以this的值默認指向全局對象勒叠。
function f1(){
return this;
}
//在瀏覽器中:
f1() === window;? //在瀏覽器中兜挨,全局對象是window
//在Node中:
f1() === global;
然而,在嚴格模式下眯分,this將保持他進入執(zhí)行上下文時的值拌汇,所以下面的this將會默認為undefined。
function f2(){
"use strict"; // 這里是嚴格模式
return this;
}
f2() === undefined; // true
所以弊决,在嚴格模式下噪舀,如果this未在執(zhí)行的上下文中定義,那它將會默認為undefined飘诗。
在第二個例子中与倡,this的確應(yīng)該是undefined,因為f2是被直接調(diào)用的昆稿,而不是作為對象的屬性/方法調(diào)用的(比如window.f2())纺座。有一些瀏覽器最初在支持嚴格模式時沒有正確實現(xiàn)這個功能,于是它們錯誤地返回了window對象溉潭。
2. call和apply方法
如果要想把this的值從一個context傳到另一個净响,就要用call,或者apply方法喳瓣。
譯者注:call()和apply()方法屬于間接調(diào)用(indirect invocation)馋贤。
// 一個對象可以作為call和apply的第一個參數(shù),并且this會被綁定到這個對象畏陕。
var obj = {a: 'Custom'};
// 這個屬性是在global對象定義的配乓。
var a = 'Global';
function whatsThis(arg) {
return this.a;? // this的值取決于函數(shù)的調(diào)用方式
}
whatsThis();? ? ? ? ? // 直接調(diào)用,? ? ? 返回'Global'
whatsThis.call(obj);? // 通過call調(diào)用,? 返回'Custom'
whatsThis.apply(obj); // 通過apply調(diào)用 扰付,返回'Custom'
當一個函數(shù)的函數(shù)體中使用了this關(guān)鍵字時,通過call()方法和apply()方法調(diào)用仁讨,this的值可以綁定到一個指定的對象上羽莺。call()和apply()的所有函數(shù)都繼承自Function.prototype。
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {a: 1, b: 3};
// 第一個參數(shù)是作為‘this’使用的對象
// 后續(xù)參數(shù)作為參數(shù)傳遞給函數(shù)調(diào)用
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
// 第一個參數(shù)也是作為‘this’使用的對象
// 第二個參數(shù)是一個數(shù)組洞豁,數(shù)組里的元素用作函數(shù)調(diào)用中的參數(shù)
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
使用call和apply函數(shù)的時候要注意盐固,如果傳遞的this值不是一個對象,JavaScript將會嘗試使用內(nèi)部ToObject操作將其轉(zhuǎn)換為對象丈挟。因此刁卜,如果傳遞的值是一個原始值比如?7 或 'foo' ,那么就會使用相關(guān)構(gòu)造函數(shù)將它轉(zhuǎn)換為對象曙咽,所以原始值7通過new Number(7)被轉(zhuǎn)換為對象蛔趴,而字符串'foo'使用new?String('foo')轉(zhuǎn)化為對象,例如:
function bar() {
console.log(Object.prototype.toString.call(this));
}
//原始值 7 被隱式轉(zhuǎn)換為對象
bar.call(7); // [object Number]
3. bind?方法
ECMAScript 5 引入了Function.prototype.bind例朱。調(diào)用f.bind(某個對象)會創(chuàng)建一個與f具有相同函數(shù)體和作用域的函數(shù)孝情,但是在這個新函數(shù)中,this將永久地被綁定到了bind的第一個參數(shù)洒嗤,無論這個函數(shù)是如何被調(diào)用的箫荡。
function f(){
return this.a;
}
//this被固定到了傳入的對象上
var g = f.bind({a:"azerty"});
console.log(g()); // azerty
var h = g.bind({a:'yoo'}); //bind只生效一次!
console.log(h()); // azerty
var o = {a:37, f:f, g:g, h:h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
4. 箭頭函數(shù)
在箭頭函數(shù)中渔隶,this是根據(jù)當前的詞法作用域來決定的羔挡,就是說,箭頭函數(shù)會繼承外層函數(shù)調(diào)用的this綁定(無論this綁定到什么)间唉。在全局作用域中绞灼,它會綁定到全局對象上:
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
注意:如果將thisArg傳遞給call、bind呈野、或者apply镀赌,它將被忽略(譯者注:thisArg即傳入三個函數(shù)中的第一個參數(shù))。不過你仍然可以為調(diào)用添加參數(shù)际跪,不過第一個參數(shù)應(yīng)該設(shè)置為null商佛。
// 接著上面的代碼
// 作為對象的一個方法調(diào)用
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true
// 嘗試使用call來設(shè)定this
console.log(foo.call(obj) === globalObject); // true
// 嘗試使用bind來設(shè)定this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
無論如何,foo的this被設(shè)置為它被創(chuàng)建時的上下文(在上面的例子中姆打,就是global對象)良姆。這同樣適用于在其他函數(shù)中創(chuàng)建的箭頭函數(shù):這些箭頭函數(shù)的this被設(shè)置為外層執(zhí)行上下文。
// 創(chuàng)建一個含有bar方法的obj對象幔戏,bar返回一個函數(shù)玛追,這個函數(shù)返回它自己的this,
// 這個返回的函數(shù)是以箭頭函數(shù)創(chuàng)建的,所以它的this被永久綁定到了它外層函數(shù)的this痊剖。
// bar的值可以在調(diào)用中設(shè)置韩玩,它反過來又設(shè)置返回函數(shù)的值。
var obj = {bar: function() {
var x = (() => this);
return x;
}
};
// 作為obj對象的一個方法來調(diào)用bar陆馁,把它的this綁定到obj找颓。
// x所指向的匿名函數(shù)賦值給fn。
var fn = obj.bar();
// 直接調(diào)用fn而不設(shè)置this叮贩,通常(即不使用箭頭函數(shù)的情況)默認為全局對象击狮,若在嚴格模式則為undefined
console.log(fn() === obj); // true
// 但是注意,如果你只是引用obj的方法益老,而沒有調(diào)用它(this是在函數(shù)調(diào)用過程中設(shè)置的)
var fn2 = obj.bar;
// 那么調(diào)用箭頭函數(shù)后彪蓬,this指向window,因為它從 bar 繼承了this捺萌。
console.log(fn2()() == window); // true
在上面的例子中档冬,一個賦值給了obj.bar的函數(shù)(稱它為匿名函數(shù)A) ,返回了另一個箭頭函數(shù)(稱它為匿名函數(shù)B)桃纯。因此捣郊,函數(shù)B被調(diào)用時,它的this被永久地設(shè)置為obj.bar(匿名函數(shù)A)的this慈参。
而且當這個返回的函數(shù)B被調(diào)用時呛牲,它的this將始終為最初設(shè)定的值。
在上面的代碼示例中驮配,函數(shù)B的 this被設(shè)定為函數(shù)A的this娘扩,也就是obj,所以即使以某種默認方式調(diào)用它(比如默認讓它指向全局對象或者undefined壮锻,或者在前面示例中的任何其他方法)琐旁,它仍然會指向obj.
5. 作為對象的一個方法
當以對象里的方法的方式調(diào)用函數(shù)時,它們的this是調(diào)用該函數(shù)的對象.
下面的例子中猜绣,當o.f()被調(diào)用時灰殴,函數(shù)內(nèi)的this將綁定到o對象。
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // logs 37
請注意掰邢,這樣的行為牺陶,根本不受函數(shù)定義方式或位置的影響。在前面的例子中辣之,我們在定義對象o的同時掰伸,將成員f定義了一個匿名函數(shù)。但是怀估,我們也可以首先定義函數(shù)狮鸭,然后再將其附屬到o.f合搅。這樣做會導(dǎo)致相同的行為:
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // logs 37
這說明this的值只與 函數(shù)從o的成員f中調(diào)用的方式 有關(guān)系。
類似的歧蕉,this的綁定只受最靠近的成員引用的影響灾部。在下面的這個例子中,我們把一個方法g當作對象o.b的函數(shù)調(diào)用惯退。在這次執(zhí)行期間赌髓,函數(shù)中的this將指向o.b。事實上蒸痹,這與對象本身的成員沒有多大關(guān)系,最靠近的引用才是最重要的呛哟。
o.b = {
g: independent,
prop: 42
};
console.log(o.b.g()); // logs 42
6. 原型鏈中的this
相同的概念在定義在原型鏈中的方法也是一致的叠荠。如果該方法存在于一個對象的原型鏈上,那么this指向的是調(diào)用這個方法的對象扫责,就好像該方法本來就存在于這個對象上榛鼎。
var o = {
f : function(){
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
在這個例子中,對象p沒有屬于它自己的f屬性鳖孤,它的f屬性繼承自它的原型者娱。但是這對于最終在o中找到f屬性的查找過程來說沒有關(guān)系;查找過程首先從p.f的引用開始苏揣,所以函數(shù)中的this指向p黄鳍。也就是說,因為f是作為p的方法調(diào)用的平匈,所以它的this指向了p框沟。這是JavaScript的原型繼承中的一個有趣的特性。
7. getter 與 setter 中的 this
再次增炭,相同的概念也適用時的函數(shù)作為一個getter或者?一個setter調(diào)用忍燥。用作getter或setter的函數(shù)都會把this綁定到正在設(shè)置或獲取屬性的對象。
function sum() {
return this.a + this.b + this.c;
}
var o = {
a: 1,
b: 2,
c: 3,
get average() {
return (this.a + this.b + this.c) / 3;
}
};
Object.defineProperty(o, 'sum', {
get: sum, enumerable: true, configurable: true});
console.log(o.average, o.sum); // logs 2, 6
8. 作為一個構(gòu)造函數(shù)
當一個函數(shù)用作構(gòu)造函數(shù)時(使用new關(guān)鍵字)隙姿,它的this被綁定到正在構(gòu)造的新對象梅垄。
注意:雖然構(gòu)造器返回的默認值是this所指的那個對象,但它仍可以手動返回其他的對象(如果返回值不是一個對象输玷,則返回this對象)队丝。
/*
* 構(gòu)造函數(shù)這樣工作:
*
* function MyConstructor(){
*? // 函數(shù)實體寫在這里
*? // 根據(jù)需要在this上創(chuàng)建屬性,然后賦值給它們欲鹏,比如:
*? this.fum = "nom";
*? // 等等...
*
*? // 如果函數(shù)具有返回對象的return語句炭玫,則該對象將是 new 表達式的結(jié)果。
*? // 否則貌虾,表達式的結(jié)果是當前綁定到 this 的對象吞加。
*? //(即通常看到的常見情況)。
* }
*/
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {a:38};
}
o = new C2();
console.log(o.a); // logs 38
在剛剛的例子中(C2)衔憨,因為在調(diào)用構(gòu)造函數(shù)的過程中叶圃,手動的設(shè)置了返回對象,與this綁定的默認對象被丟棄了践图。(這基本上使得語句“this.a = 37;”成了“僵尸”代碼掺冠,實際上并不是真正的“僵尸”,這條語句執(zhí)行了码党,但是對于外部沒有任何影響德崭,因此完全可以忽略它)。
9. 作為一個DOM事件處理函數(shù)
當函數(shù)被用作事件處理函數(shù)時揖盘,它的this指向觸發(fā)事件的元素(一些瀏覽器在使用非addEventListener的函數(shù)動態(tài)添加監(jiān)聽函數(shù)時不遵守這個約定)眉厨。
// 被調(diào)用時,將關(guān)聯(lián)的元素變成藍色
function bluify(e){
console.log(this === e.currentTarget); // 總是 true
// 當 currentTarget 和 target 是同一個對象是為 true
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// 獲取文檔中的所有元素的列表
var elements = document.getElementsByTagName('*');
// 將bluify作為元素的點擊監(jiān)聽函數(shù)兽狭,當元素被點擊時憾股,就會變成藍色
for(var i=0 ; i
elements[i].addEventListener('click', bluify, false);
}
10. 作為一個內(nèi)聯(lián)事件處理函數(shù)
當代碼被內(nèi)聯(lián)處理函數(shù)調(diào)用時,它的this指向監(jiān)聽器所在的DOM元素:
Show this
上面的alert會顯示button箕慧。注意只有外層代碼中的this是這樣設(shè)置的:
Show inner this
在這種情況下服球,沒有設(shè)置內(nèi)部函數(shù)的this,所以它指向global/window對象(即非嚴格模式下調(diào)用的函數(shù)未設(shè)置 this?時指向的默認對象)颠焦。