this 的問題

與其他語言相比,函數(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的確應該是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ù)應該設(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。這樣做會導致相同的行為:

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 的對象。

*? //(即通??吹降某R娗闆r)。

* }

*/

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?時指向的默認對象)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容