與其他語言相比,函數的 this 關鍵字在 JavaScript 中的表現略有不同,此外,在嚴格模式和非嚴格模式之間也會有一些差別。
目錄
在絕大多數情況下,函數的調用方式決定了this的值。this不能在執行期間被賦值,並且在每次函數被調用時this的值也可能會不同。ES5引入了bind方法來設置函數的this值,而不用考慮函數如何被調用的,ES2015 引入了支持this詞法解析的箭頭函數(它在閉合的執行環境內設置this的值)。
當前執行代碼的環境對象,在非嚴格模式下,總是指向一個對象,在嚴格模式下可以是任意值
1. 全局環境
無論是否在嚴格模式下,在全局執行環境中(在任何函數體外部)this
都指向全局對象。
// 在瀏覽器中, window 對象同時也是全局對象:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "jhj";
console.log(window.b) // "jhj"
console.log(b) // "jhj"
2. 函數(運行內)環境
在函數內部,
this
的值取決於函數被調用的方式。(重點)
因爲下面的代碼不在嚴格模式下,且 this
的值不是由該調用設置的,所以 this
的值默認指向全局對象。
function f1(){
return this;
}
//在瀏覽器中:
f1() === window; //在瀏覽器中,全局對象是window
//在Node中:
f1() === global;
然而,在嚴格模式下,this
將保持他進入執行環境時的值,所以下面的this
將會默認爲undefined
。
function f2(){
"use strict"; // 這裏是嚴格模式
return this;
}
f2() === undefined; // true
所以,在嚴格模式下,如果 this
沒有被執行環境(execution context)定義,那它將保持爲 undefined
。
3.call
和 apply
如果要想把 this
的值從一個環境傳到另一個,就要用 call
和 apply
方法。
// 將一個對象作爲call和apply的第一個參數,this會被綁定到這個對象。
var obj = {a: 'Custom'};
// 這個屬性是在global對象定義的。
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
方法將 this
值綁定到調用中的特定對象。
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {a: 1, b: 3};
// 第一個參數是作爲‘this’使用的對象
// 後續參數作爲參數傳遞給函數調用
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
// 第一個參數也是作爲‘this’使用的對象
// 第二個參數是一個數組,數組裏的元素用作函數調用中的參數
add.apply(o, [10, 20]); // 1 + 3 + 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));
}
//原始值 7 被隱式轉換爲對象
bar.call(7); // [object Number]
bar.call('foo'); // [object String]
4. bind方法
bind
方法會返回函數的拷貝值,但帶有綁定的上下文! 它不會立即執行!!!
調用f.bind(someObject)
會創建一個與f
具有相同函數體和作用域的函數,但是在這個新函數中,this
將永久地被綁定到了bind
的第一個參數,無論這個函數是如何被調用的,只生效一次
function f(){
return this.a;
}
var g = f.bind({a:"jhj"});
console.log(g()); // jhj
var h = g.bind({a:'yoo'}); // bind只生效一次!
console.log(h()); // jhj
var o = {a:37, f:f, g:g, h:h};
console.log(o.a, o.f(), o.g(), o.h()); // 37, 37, jhj, jhj
5. 作爲對象的方法
當函數作爲對象裏的方法被調用時,它們的 this
是調用該函數的對象。
下面的例子中,當 o.f()
被調用時,函數內的this
將綁定到o
對象。
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37
請注意,這樣的行爲,根本不受函數定義方式或位置的影響。在前面的例子中,我們在定義對象o
的同時,將函數內聯定義爲成員 f
。但是,我們也可以先定義函數,然後再將其附屬到o.f
。這樣做會導致相同的行爲:
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // 37
6. 箭頭函數的this
箭頭函數中的this 與被設置爲他被創建時的環境的this 在全局代碼中,它將被設置爲全局對象
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
如果將
this
傳遞給call
、bind
、或者apply
來調用箭頭函數,它將被忽略。不過你仍然可以爲調用添加參數,不過第一個參數(thisArg
)應該設置爲null
。
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
// 作爲對象的一個方法調用
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true
// 嘗試使用call來設定this
console.log(foo.call(obj) === globalObject); // true
// 嘗試使用bind來設定this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
就是不管你咋調用咋折騰都是沒有用的,this是不會改變的,他只會和定義時候的環境有關。
無論如何,foo
的 this
被設置爲他被創建時的環境(在上面的例子中,就是全局對象)。這同樣適用於在其他函數內創建的箭頭函數:這些箭頭函數的this
被設置爲封閉的詞法環境的。
看下邊的例子會有一些啓發
// 創建一個含有bar方法的obj對象,
// bar返回一個函數,
// 這個函數返回this,
// 這個返回的函數是以箭頭函數創建的,
// 所以它的this被永久綁定到了它外層函數的this。
// bar的值可以在調用中設置,這反過來又設置了返回函數的值。
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;
// 那麼調用箭頭函數後,this指向window,因爲它從 bar 繼承了this。
console.log(fn2()() == window); // true
7. 原型鏈上的this
原型鏈中的 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屬性繼承自它的原型。雖然在對 f
的查找過程中,最終是在 o
中找到 f
屬性的,這並沒有關係;查找過程首先從 p.f
的引用開始,所以函數中的 this
指向p
。也就是說,因爲f
是作爲p
的方法調用的,所以它的this
指向了p
。這是 JavaScript 的原型繼承中的一個有趣的特性。
8. 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: true});
console.log(o.average, o.sum); // logs 2, 6
9. 構造函數中的this
當一個函數用作構造函數時(使用new關鍵字),它的this被綁定到正在構造的新對象。
/*
* 構造函數這樣工作:
*
* function MyConstructor(){
* // 函數實體寫在這裏
* // 根據需要在this上創建屬性,然後賦值給它們,比如:
* this.fum = "nom";
* // 等等...
*
* // 如果函數具有返回對象的return語句,
* // 則該對象將是 new 表達式的結果。
* // 否則,表達式的結果是當前綁定到 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
綁定的默認對象被丟棄了。(這基本上使得語句 “this.a = 37;
”成了“殭屍”代碼,實際上並不是真正的“殭屍”,這條語句執行了,但是對於外部沒有任何影響,因此完全可以忽略它)。
10. DOM事件處理函數的this
當函數被用作事件處理函數時,它的this
指向觸發事件的元素(一些瀏覽器在使用非addEventListener
的函數動態添加監聽函數時不遵守這個約定)。
// 被調用時,將關聯的元素變成藍色
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作爲元素的點擊監聽函數,當元素被點擊時,就會變成藍色
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}
當代碼被內聯事件調用時,它的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
,所以它指向 global/window 對象(即非嚴格模式下調用的函數未設置this
時指向的默認對象)。
文章參考: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this