翻譯不完整。請協助 翻譯此英文文件。
JavaScript 函式內的 this
關鍵字表現,和其他語言相比略有差異。在嚴格模式與非嚴格模式下亦有所不同。
通常,this
值由被呼叫的函式來決定。它不能在執行期間被指派,每次函數呼叫所用的值也可能不同。ES5 引入了 bind
方法去設置函式的 this
值,而不管其如何被呼叫。ECMAScript 2015 也導入了定義 this
詞法範圍的箭頭函數(it is set to the this
value of the enclosing execution context)。
語法
this
全域環境下
無論是否處於嚴格模式,在全域執行環境(處於所有函式以外)下 this
會被當作全域物件。
console.log(this.document === document); // true // 在網路瀏覽器下,window 物件也是全域物件。 console.log(this === window); // true this.a = 37; console.log(window.a); // 37
函式環境下
在函式內,this
值取決於如何呼叫該函式。
簡易呼叫
function f1(){ return this; } f1() === window; // 全域物件
在這個例子裡,this
值並沒有透過呼叫而建立。也因為不在嚴格模式下執行,this
值會一直是物件,因此它被預設為全域物件。
function f2(){ "use strict"; // 嚴格模式 return this; } f2() === undefined;
在嚴格模式下,this
值會在進入執行環境時建立並維持之。如果它未定義,就會維持在 undefined。它可以設為任何值,例如 null
或 42
或 "我不是 this"。
this
應為 undefined
,因為 f2
在不是某種物件的方法或屬性(property)的情況下(例如 window.f2()
)被直接呼叫。某些瀏覽器首次支援嚴格模式時沒導入這個特徵,它們會因此錯誤的回傳 window
物件。箭頭函數
在箭頭函數下,this
將設為 lexically,例如 enclosing execution context 的 this
值。在全域程式碼則設為全域物件:
var globalObject = this; var foo = (() => this); console.log(foo() === globalObject); // true
如何呼叫 foo
並不重要,它的 this
會一直是全域物件。This also holds if it's called as a method of an object (which would usually set its this
to the object), with call
or apply
or bind
is used:
// 作為物件的方法呼叫 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
在建立的時候,都會設為原本的樣子(以上面的例子來說,就是全域物件)The same applies for arrow functions created inside other functions: their this
is set to that of the outer execution context.
// 建立一個物件,其方法 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()); // logs 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
相同概念也能套用定義物件原型鏈的方法。如果方法放在物件的原型鏈上,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
In this example, the object assigned to the variable p
doesn't have its own f
property, it inherits it from its prototype. But it doesn't matter that the lookup for f
eventually finds a member with that name on o
; the lookup began as a reference to p.f
, so this
inside the function takes the value of the object referred to as p
. That is, since f
is called as a method of p
, its this
refers to p
. This is an interesting feature of JavaScript's prototype inheritance.
this
with a getter or setter
Again, the same notion holds true when a function is invoked from a getter or a setter. A function used as getter or setter has its this
bound to the object from which the property is being set or gotten.
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
會和被建構的新物件綁定。
建構子預設透過 this
回傳該物件的參照,但它其實能回傳其他物件。如果回傳值不是物件的話,就會回傳 this
這個物件。
/* * 建構子會如此做動: * * function MyConstructor(){ * // Actual function body code goes here. * // Create properties on |this| as * // desired by assigning to them. E.g., * this.fum = "nom"; * // et cetera... * * // If the function has a return statement that * // returns an object, that object will be the * // result of the |new| expression. Otherwise, * // the result of the expression is the object * // currently bound to |this| * // (i.e., the common case most usually seen). * } */ 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 的情況下遭到消滅。
call
與 apply
當函式內部調用 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); // 1 + 3 + 5 + 7 = 16 // 第一個參數(parameter)是調用了 this 的物件, // 第二個參數的陣列作為函式呼叫內的參數(arguments)之構件 add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
Note that with call
and apply
, if the value passed as this
is not an object, an attempt will be made to convert it to an object using the internal ToObject
operation. So if the value passed is a primitive like 7
or 'foo'
, it will be converted to an Object using the related constructor, so the primitive number 7
is converted to an object as if by new Number(7)
and the string 'foo'
to an object as if by new String('foo')
, e.g.
function bar() { console.log(Object.prototype.toString.call(this)); } bar.call(7); // [object Number]
bind
方法
ECMAScript 5 導入了 Function.prototype.bind
。呼叫 f.bind(someObject)
會建立和 f
的 body 與 scope 相同之新函式;但無論函數怎麼被調用,原始函數的 this
在新函數將永遠與 bind
的第一個參數綁定起來。
function f(){ return this.a; } var g = f.bind({a:"azerty"}); console.log(g()); // azerty var o = {a:37, f:f, g:g}; console.log(o.f(), o.g()); // 37, azerty
作為 DOM 事件處理器
當一個函式用作事件處理器的話,this
值會設在觸發事件的元素(某些瀏覽器如果不用 addEventListener
方法的話,在動態添加監聽器時,就不會遵循這個常規)
// When called as a listener, turns the related element blue function bluify(e){ // Always true console.log(this === e.currentTarget); // true when currentTarget and target are the same object 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
的預設物件)
規範
瀏覽器相容性
特徵 | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
基本支援 | (Yes) | (Yes) | (Yes) | (Yes) | (Yes) |
特徵 | Android | Chrome for Android | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile |
---|---|---|---|---|---|---|
基本支援 | (Yes) | (Yes) | (Yes) | (Yes) | (Yes) | (Yes) |
參見
- 嚴格模式
- All this,一篇關於
this
上下文不同的相關文章 - 親和地解釋 JavaScript 的「this」關鍵字