本文章預計耗時15 - 20分鐘,包含執行代碼驗證的時間。很是建議仔細閱讀並手動執行代碼,以驗證不一樣操做的結果。整理及驗證不易,若是你收穫了新知識,請不吝惜的點個贊吧。若是你讀完後對this指向還有疑問,歡迎在底部留言~javascript
與其餘語言相比,函數的 this
關鍵字在 JavaScript 中的表現略有不一樣,此外,在嚴格模式和非嚴格模式之間也會有一些差異。前端
this
的值。this
不能在執行期間被賦值,而且在每次函數被調用時this
的值也可能會不一樣。this
值,而不用考慮函數如何被調用的,this
詞法解析的箭頭函數(它在閉合的執行環境內設置this
的值)。this指當前執行代碼的環境對象,在非嚴格模式下,老是指向一個對象,在嚴格模式下能夠是任意值java
不管是否在嚴格模式下,在全局執行環境中(在任何函數體外部)this
都指向全局對象。node
// 在瀏覽器中, 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"
複製代碼
能夠直接使用
globalThis
來獲取不一樣環境下的全局this
對象(也就是全局對象自身)瀏覽器
在函數內部,this
的值取決於函數被調用的方式。app
在非嚴格模式下,直接在全局做用域下調用函數, this
的值不是由該調用設置的,因此 this
的值默認指向全局對象。異步
若是函數名前沒有加任何東西,那麼默認爲簡單調用,函數
function f1(){
return this;
}
//在瀏覽器中:
f1() === window; //在瀏覽器中,全局對象是window
//在Node中:
f1() === global;
複製代碼
在嚴格模式下,this
將保持他進入執行環境時的值,因此下面的this
將會默認爲undefined
。post
function f2(){
"use strict"; // 這裏是嚴格模式
return this;
}
f2() === undefined; // true
複製代碼
嚴格模式下,若是
this
沒有被執行環境(execution context)定義,那它將保持爲undefined
。ui
call
或者apply
方法能夠將其主體中使用的 this
綁定到某個對象。// 將一個對象做爲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
的值不是一個對象,JavaScript 會嘗試使用內部ToObject
操做將其轉換爲對象。所以,若是傳遞的值是一個原始值好比7
或'foo'
,那麼就會使用相關構造函數將它轉換爲對象,因此原始值7
會被轉換爲對象,像new Number(7)
這樣,而字符串'foo'
轉化成new String('foo')
bind
方法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.a, o.f(), o.g(), o.h()); // 37, 37, azerty, azerty
複製代碼
當普通函數做爲對象裏的方法被調用時,它們的 this
是調用該函數的對象。
下面的例子中,當 o.f()
被調用時,函數內的this
將綁定到o
對象。
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37
複製代碼
請注意,函數內部的this指向調用函數的對象,跟函數所在位置無關。以下面例子:
// 接上面代碼, 建議在瀏覽器控制檯運行下面代碼,node環境下你只會一臉懵逼,好比我
var prop = 10;
const func = o.f;
// 在全局環境下直接調用函數
console.log(func()); // 10
// 定義新的對象
const o1 = {
prop: 15
}
// 將o1中的func指向o中的函數f
o1.func = o.f;
// 經過o1調用, 函數內this指向o1
console.log(o1.func()); // 15
複製代碼
對於在對象原型鏈上某處定義的方法,一樣的概念也適用。若是該方法存在於一個對象的原型鏈上,那麼this
指向的是調用這個方法的對象,就像該方法在對象上同樣。
當一個函數用做構造函數時(使用new關鍵字),它的this
被綁定到正在構造的新對象。
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
複製代碼
當函數被用做事件處理函數時,它的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與調用函數所在環境的this無關
function foo() {
console.log(this)
function boo() {
console.log(this)
}
boo()
}
// 直接調用
foo() // window window
// new 對象
const f = new foo() // foo實例 window
複製代碼
這裏我我的的理解是:函數內部調用的函數,由於沒有顯式指定調用對象,因此內部this指向全局。
微任務中的簡單調用的函數this指向window嚴格下指向undefined,而定時器中的回調函數無論在嚴格仍是非嚴格環境下this永遠指向window,
異步任務中簡單調用的函數都是進入隊列,最後由全局環境調用
在箭頭函數中,this
與封閉詞法環境的this
保持一致。
在全局代碼中,它將被設置爲全局對象:
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
傳遞給call
、bind
、或者apply
來調用箭頭函數操做是無效的。
箭頭函數不會建立本身的this,
它只會從本身的做用域鏈的上一層繼承this。
下面代碼請在控制檯中執行
// 仍是這個對象
var o = {
prop: 37,
f: function() {
return this.prop;
},
g: () => {
return this.prop;
}
};
// 分別調用
console.log(o.f()); // 37,this指向對象 o
console.log(o.g()); // undefined,this指向全局
// 在全局中聲明 prop
var prop = 10;
// 分別調用
console.log(o.f()); // 37
console.log(o.g()); // 10
複製代碼
你能夠參考一下Object.defineProperty()
的示例,大概就能夠理解爲何對象方法裏的箭頭函數內部this指向全局了:
'use strict';
var obj = {
a: 10
};
Object.defineProperty(obj, "b", {
get: () => {
console.log(this.a, typeof this.a, this);
return this.a+10;
// 表明全局對象 'Window', 所以 'this.a' 返回 'undefined'
}
});
複製代碼
在下面的代碼中,傳遞給setInterval
的函數內的this
與封閉函數中的this
值相同:
function Person(){
this.age = 0;
setTimeout(() => {
this.age++; // |this| 正確地指向 p 實例
}, 1000);
}
var p = new Person();
p.age; // 1
複製代碼
換一種方式調用:
// 聲明全局變量
var prop = 10;
// 聲明一個對象
const o1 = {
prop: 15
}
function func1() {
const prop = 11;
const te = () => {
return this.prop;
}
return te();
}
console.log(func1()) // 10
console.log(func1.call(o1)) // 15
複製代碼
從上面的代碼能夠看出,在函數內部定義的箭頭函數,其內部使用的this繼承自外部函數,此時,箭頭函數的this就不是固定的了,而是會在外部函數func1執行時肯定。
再換一種方式調用:
// 聲明全局變量
var prop = 10;
// 聲明一個對象
const o1 = {
prop: 15
}
function func1() {
const prop = 11;
const te = () => {
return this.prop;
}
return te;
}
// func1在全局環境下調用
o1.func = func1();
console.log(o1.func()) // 10,箭頭函數的this指向全局對象
// func1在o1方法下調用
o1.func1 = func1;
console.log(o1.func1()()) // 15, 箭頭函數的this指向o1
複製代碼
結論:函數內部的箭頭函數的this繼承外部函數的this,所以不是固定的,而是會在外部函數func1執行時肯定。
請注意,當使用this.te = () => { return this.prop }
,te做爲對象的屬性而不是函數的局部變量,所以內部this再也不繼承自函數,而是指向全局,具體參考上面箭頭函數做爲對象方法一節。
最後,來講明一下怎麼使對象內部的箭頭函數方法的this再也不固定,沒錯,就是在函數內部定義對象,對象內部聲明方法。
// 聲明全局變量
var prop = 10;
// 聲明一個對象
const o1 = {
prop: 15
}
function test(){
const prop = 20;
const o2 = {
prop: 30,
f: () => {
return this.prop;
}
}
return o2.f();
}
// 在全局環境下調用
console.log(test()) // 10
// 使用o1調用
console.log(test.call(o1)) // 15
複製代碼
如今你明白this會指向哪裏了麼?
相關係列: 從零開始的前端築基之旅(超級精細,持續更新~)
若是你收穫了新知識,請給做者點個贊吧~
參考文檔:
this