title | slug |
---|---|
this |
Web/JavaScript/Reference/Operators/this |
{{jsSidebar("Operators")}}
JavaScript 函式內的 this
關鍵字表現,和其他語言相比略有差異。在嚴格模式與非嚴格模式下也有所不同。
通常,this
值由被呼叫的函式來決定。它不能在執行期間被指派,每次函式呼叫調用的值也可能不同。ES5 引入了 bind
方法去設置函式的 this
值,而不管它怎麼被呼叫。ECMAScript 2015 也導入了定義 this
詞法範圍的箭頭函式(它的 this
值會維持在詞法作用域)。
{{EmbedInteractiveExample("pages/js/expressions-this.html")}}
this
this
值在所有函式以外的全域執行環境下,會被當作全域物件,無論是否處於嚴格模式。
console.log(this.document === document); // true
// 在網路瀏覽器中,window 物件也是全域物件。
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b); // "MDN"
console.log(b); // "MDN"
在函式內的 this
值取決於該函式如何被呼叫。
因為以下程式碼並不處於嚴謹模式下、而 this
值也沒被呼叫(call)設定,this
會變成全域物件,在瀏覽器之下則會變成 window
。
function f1() {
return this;
}
//在瀏覽器中:
f1() === window; // true
//Node中:
f1() === global; // true
然而,在嚴格模式下,this
值會在進入執行環境時建立並維持該狀態。因此,下例的 this
預設值是 undefined
:
function f2() {
"use strict"; // 嚴格模式
return this;
}
f2() === undefined; //true
所以在嚴格模式下,如果 this
沒有定義到執行環境內,其預設值就會是 undefined
。
Note
在第二個例子裡面,this
應為 undefined
,因為 f2
是直接被呼叫,而不是在其為某個物件的方法或屬性的情況下(例如 window.f2()
)被直接呼叫。某些瀏覽器首次支援嚴格模式時沒導入這個特徵,它們會因此錯誤的回傳 window
物件。
要從某個語境訪問另一個 this
語境的值,請使用 call 或 apply:
// 物件可以被當作call或apply的第一個參數,而this會綁定該物件
var obj = { a: "Custom" };
// 此屬性a為全域物件
var a = "Global";
function whatsThis(arg) {
return this.a; // this 值取決於此函數如何被呼叫
}
whatsThis(); // 'Global'
whatsThis.call(obj); // 'Custom'
whatsThis.apply(obj); // 'Custom'
當函式內部調用 this
關鍵字時,其值會和所有繼承自 Function.prototype
並使用 call
或 apply
方法呼叫的特定物件綁定。
function add(c, d) {
return this.a + this.b + c + d;
}
var o = { a: 1, b: 3 };
// 第一個參數(parameter)是調用了 this 的物件,
// 後續參數(parameters)會作為函式呼叫內的參數(arguments)而通過
add.call(o, 5, 7); // 16
// 第一個參數(parameter)是調用了 this 的物件,
// 第二個參數的陣列作為函式呼叫內的參數(arguments)之構件
add.apply(o, [10, 20]); // 34
使用 call
和 apply
時,如果被輸入作為 this
的值不是物件,JavaScript 內建的 ToObject
運算符會試著把被輸入的值轉變為物件。如果被輸入的值是一個原始型別,例如 7
或是 'foo'
,它們會自動被相關的建構方法轉變為物件。因此,原始數值7
會轉變成類似用new Number(7)
產生的物件,而字串'foo'
會轉變成類似用new String('foo')
產生的物件。
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
bar.call("foo"); // [Object String]
ECMAScript 5 導入了 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 只能使用一次!
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
值。在全域程式碼內,則設為全域物件:
var globalObject = this;
var foo = () => this;
console.log(foo() === globalObject); // true
Note
如果這參數被傳遞給箭頭函式的 call, bind, apply 調用,該參數會被忽略。你仍然可以將參數預先調用到 call,但第一個參數(thisArg)必須設置為空。
// 作為物件的方法呼叫
var obj = { foo: foo };
console.log(obj.foo() === globalObject); // true
// 使用呼叫以嘗試設置 this
console.log(foo.call(obj) === globalObject); // true
// 使用 bind 以嘗試設置 this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
無論以上哪種,foo
的 this
在建立的時候,都會設為原本的樣子(以上面的例子來說,就是全域物件)。這同樣適用於在其他函式內創建的箭頭函式:它們的 this
是設置為外部執行上下文。
// 建立一個物件,其方法 bar 含有回傳自己的 this 函式。回傳函式作為箭頭函數而建立,
// 因此該函式的 this 將永遠與外圍函式(enclosing function)的 this 綁定。
// bar 的值可在呼叫內設立,which in turn sets the value of the returned function.
var obj = {
bar: function () {
var x = () => this;
return x;
},
};
// 將 bar 作為物件的方法呼叫,把它的 this 設為物件
// 指派 fn 作為回傳函數的參照(reference)
var fn = obj.bar();
// 在不設置 this 情況下呼叫的 fn,通常默認為全域物件,在嚴格模式下則是 undefined
console.log(fn() === obj); // true
以上面的程式碼為例,稱作匿名函數(anonymous function)A 的函數被指定為 obj.bar
,它回傳的函數(稱作匿名函數 B)作為箭頭函數而建立。因而,函數 B 的 this
在呼叫時,將永遠設為 obj.bar
(函數 A)的 this
。呼叫被回傳的函數(函數 B)時,它的 this
將一直是原本的樣子。
再以上面的程式碼為例,函數 B 的 this
被設為函數 A 的 this
,而它屬於物件,所以它依然會設為 obj
,就算在 this
設為 undefined
或全域物件的呼叫方式下(或在全域執行環境下,上例的任何方法)
如果一個函式是以物件的方法呼叫,它的 this
會設為該呼叫函式的物件。
以下面的程式碼為例,呼叫 o.f()
的時候,函式內的 this
會和 o
物件綁定。
var o = {
prop: 37,
f: function () {
return this.prop;
},
};
console.log(o.f()); // logs 37
請注意這個行為,不會受函式如何或何處定義所影響。以上面為例,在我們定義 o
時,也定義了行內函式 f
作為構件(member)。不過,我們也能先定義函式,接著讓它附屬到 o.f
。這麼做會得出相同的效果:
var o = { prop: 37 };
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // 37
這表明了 this
只和 f
作為 o
的構件呼叫有所關聯。
同樣的,this
綁定只會受最直接的構件引用(most immediate member reference)所影響。在下面的例子,我們將物件 o.b
的方法 g
作為函式呼叫。在執行的期間,函式內的 this
會參照 o.b
。物件是否為 o
的構件無關緊要,最直接的引用才是最緊要的。
o.b = { g: independent, prop: 42 };
console.log(o.b.g()); // logs 42
相同概念也能套用定義物件原型鏈的方法。如果方法放在物件的原型鏈上,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
屬性,p
繼承了 o
的原型。但是查找 f
最終在 o
上找到它的成員名為 f 並不重要。查找開始作為 p.f
的引用,所以 this
在函式內部物件的值被當作是p
的引用。也就是說,f
作為 p
的調用方法以來,它的 this
指的就是 p
。這是一個非常有趣的 JavaScript 原型繼承特性。
當函式從 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: true,
});
console.log(o.average, o.sum); // logs 2, 6
若函式以建構子的身份呼叫(使用 new
關鍵字) this
會和被建構的新物件綁定。
Note
建構子預設透過 this
回傳該物件的參照,但它其實能回傳其他物件。如果回傳值不是物件的話,就會回傳 this
這個物件。
/*
* 建構子會如此做動:
*
* function MyConstructor(){
* // 實際的函式主體碼在這裡
* // 在|this| 上創建屬性
* // 希望通過分配給他們,如:
* this.fum = "nom";
* // et cetera...
*
* // 如果函式有返回狀態它將返回一個物件
* // 那個物件將是新表達式的結果。
* // 換句話來說,表達式的結果是現在綁定 |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
,由於物件在建構的時候被呼叫,新的物件 this
was bound to simply gets discarded。這也實質上令 this.a = 37;
宣告死亡:不是真的死亡(因為已經執行了),但它在毫無 outside effects 的情況下遭到消滅。
當一個函式用作事件處理器的話,this
值會設在觸發事件的元素(某些瀏覽器如果不用 addEventListener
方法的話,在動態添加監聽器時,就不會遵循這個常規)
// 當監聽器被調用,相關元素變為藍色
function bluify(e) {
// 永遠是真
console.log(this === e.currentTarget);
// 當當前目標和目標為相同物件為真
console.log(this === e.target);
this.style.backgroundColor = "#A5D9F3";
}
// 取得文件內所有的元素
var elements = document.getElementsByTagName("*");
// Add bluify as a click listener so when the
// element is clicked on, it turns blue
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener("click", bluify, false);
}
當程式碼從行內的 on 事件處理器呼叫的話,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 物件(例如在非嚴格模式下,呼叫函數沒設定 this
的預設物件)
{{Specifications}}
{{Compat}}
- 嚴格模式
- All this,一篇關於
this
上下文不同的相關文章 - 親和地解釋 JavaScript 的「this」關鍵字