簡介
在絕大數情況下,函數的調用方式決定了this值。this不能在執行期間被復制,並且在每次函數被調用時this的值也可能會不同。
this的值表示當前執行的環境對象,而與聲明環境無關,所以this代表的對象要等函數運行。類似定義函數時的參數列表,只有在函數調用時才傳入真正的對象。
this關鍵字雖然會根據環境變化,但它始終代表的是調用當前函數的對象。
全局環境
無論是否在嚴格模式下,在全局執行環境中(在任何函數體外部),this都指向全局對象。
console.log(this === window); //true
函數環境中的this
在函數內部,this的值取決於函數被調用的方式。
簡單調用
在非嚴格模式下,this的值不是由該調用設置的,所以this的值默認指向全局對象。
function f1() {
return this;
}
f1() === window;
在嚴格模式下,this將保持他進入執行環境時的值,所以this將會默認為undefined:
function f2() {
"use strict";
return this;
}
f2() === undefined;
在嚴格模式下,如果this沒有執行環境定義,那它將保持為undefined。
如果想把this的值從一個環境傳到另一個,就要用call或者apply方法。
//將一個對象作為call和apply的第一個參數,this就會被綁定到這個對象。
var obj = {a: 'Custom '};
var a = "Global";
function whatsThis(arg) {
return this.a;
}
whatsThis(); //'Global'
whatsThis.call(obj); //'Custom'
whatsThis.apply(obj); //'Custom'
當一個函數在其主體中使用this關鍵字時,可以使用函數繼承自Function.prototype的call或apply方法將this值綁定到調用中的特定對象。
使用call和apply函數的時候,如果傳遞給this的值不是一個對象,JavaScript會嘗試使用內部toObject操作將其轉換為對象。
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); //[object Number]
bind方法
ES5引入了Function.prototype.bind。調用f.bind(someObject)會創建一個與f具有相同函數體和作用域的函數,但是在這個新函數中,this將永久的被綁定到了bind的第一個參數,無論這個函數是如何被調用的。
function f() {
return this.a;
}
var g = f.bind({a:"azerty"});
console.log(g()); //azerty
var h = g.bind({a:"yoo"}); //bind只生效一次,所以此處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
箭頭函數
在箭頭函數中,this與普通函數的指向不同,它與封閉詞法環境的this保持一致。在箭頭函數中,this指向創建時所在的環境,這同樣適用於在其他函數內創建的箭頭函數;在普通函數中,this指向執行時的環境。
箭頭函數被調用的時候,不會自動綁定一個this對象。箭頭對象根本沒有自己的this,它的this都是捕獲自其所在上下文的this值。
舉栗子:
var foo = (( => this));
console.log(foo() === window); //true
//作為對象的一個方法調用
var obj = {foo: foo};
console.log(obj.foo() === window); //ture
//使用call來設定this
console.log(foo.call(obj) === window); //true
//使用bind設定this
foo = foo.bind(obj);
console.log(foo() === window); //true
第二個栗子:
//bar返回一個函數,這個函數返回this
//這個返回的函數是以箭頭函數創建的
//所以它的this被綁定到了外層函數的this。
var obj = {
bar: function() {
var x = (() => this);
return x;
}
};
//作為obj對象的一個方法來調用bar,把它的this綁定到obj。將返回的函數的引用復制給fn
var fn = obj.bar();
//直接調用fn而不設置this
//通常不適用箭頭函數的情況,默認為全局對象
//若在嚴格模式則為undefined
console.log(fn() === obj); //true
//如果只是引用obj的方法,而沒有調用它
var fn2 = obj.bar; //fn2 = function() {var x = (() => this); return x;}
//調用箭頭函數后,this指向window,因為它從bar繼承了this
console.log(fn2() === window); /true
作為對象的方法
當函數作為對象里的方法被調用時,它們的this是調用該函數的對象。
栗子:
var o = {
prop: 37
f: function() {
return this.prop;
}
}
console.log(o.f()); //logs 37
函數時從o的f成員調用才是重點。
同樣,this的綁定只受最靠近的成員應用的影響。
o.b = {g: independent, prop: 42}
console.log(o.b.g()) //42
這個栗子說明,這與它是對象o的成員沒有什么關系,最靠近的引用才是最重要的。
原型鏈中的this
對於在對象原型鏈上某處定義的方法,同樣的概念也使用。如果該方法存在於一個對象的原型鏈上,那么this指向的是調用這個方法的對象,就像是該方法在對象上一樣。
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屬性,但是查找股從橫首先從p.f的引用開始,所以函數中的this指向p。也就是說f是作為p的方法調用的,所以它的this指向了p。這是JavaScript的原型繼承中的一個有趣的特征。
getter與setter中的this
同樣的概念適用於,當函數在一個getter或者setter中被調用。用作getter或setter的函數都會把this綁定到設置或獲取屬性的對象。
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: trye
})
console.log(o.average, o.sum); //logs 2, 6
作為構造函數
當一個函數用作構造函數時(使用new關鍵字),它的this被綁定到正在構造的新對象。
雖然構造器返回的默認值是this所指的對象,但是也可以手動地返回其他的對象(如果返回值不是一個對象,則返回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
在這個栗子中,在調用構造函數的過程中,手動的設置了返回對象,所以默認對象就被丟棄了。this.a = 37;這條語句雖然執行了,但是對於外部沒有任何影響。
作為一個DOM事件處理函數
當函數被用作事件處理函數時,它的this指向觸發事件的元素。
作為一個內聯事件處理函數
當代碼被內聯on-event處理函數調用時,它的this指向監聽器所在的DOM元素:
<button onclick="alert(this.tagName.toLowerCase());">
Show this
</button>
這個栗子的alert會顯示button。只有外層代碼中的this是這樣設置的:
<button onclick="alert((function() {return this})());">
Show inner this
</button>
在這種情況下,沒有設置內部函數的this,所以它指向window/global對象,非嚴格模式下,調用的函數未設置this時指向的默認對象。
