轉(zhuǎn)載地址: this 指向詳細(xì)解析(箭頭函數(shù))
前言
this 指向問題是入坑前端必須了解知識點,現(xiàn)在迎來了ES6時代,因為箭頭函數(shù)的出現(xiàn),所以感覺有必要對 this 問題梳理一下,遂有此文
在非箭頭函數(shù)下, this 指向調(diào)用其所在函數(shù)的對象,而且是離誰近就是指向誰(此對于常規(guī)對象,原型鏈, getter & setter等都適用);構(gòu)造函數(shù)下,this與被創(chuàng)建的新對象綁定;DOM事件,this指向觸發(fā)事件的元素;內(nèi)聯(lián)事件分兩種情況,bind綁定, call & apply 方法等, 容以下一步一步討論。箭頭函數(shù)也會穿插其中進行討論。
全局環(huán)境下
在全局環(huán)境下,this 始終指向全局對象(window), 無論是否嚴(yán)格模式;
console.log(this.document === document); // true
// 在瀏覽器中,全局對象為 window 對象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
函數(shù)上下文調(diào)用
函數(shù)直接調(diào)用
普通函數(shù)內(nèi)部的this分兩種情況,嚴(yán)格模式和非嚴(yán)格模式。
非嚴(yán)格模式下,this 默認(rèn)指向全局對象window
function f1(){
return this;
}
f1() === window; // true
而嚴(yán)格模式下, this為undefined
function f2(){
"use strict"; // 這里是嚴(yán)格模式
return this;
}
f2() === undefined; // true
對象中的this
對象內(nèi)部方法的this指向調(diào)用這些方法的對象,
- 函數(shù)的定義位置不影響其this指向,this指向只和調(diào)用函數(shù)的對象有關(guān)。
- 多層嵌套的對象,內(nèi)部方法的this指向離被調(diào)用函數(shù)最近的對象(window也是對象,其內(nèi)部對象調(diào)用方法的this指向內(nèi)部對象, 而非window)。
//1
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); //37
var a = o.f;
console.log(a()): //undefined
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // logs 37
//2
o.b = {
g: independent,
prop: 42
};
console.log(o.b.g()); // logs 42
原型鏈中this
原型鏈中的方法的this仍然指向調(diào)用它的對象,與以上討論一致;看個例子,
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,當(dāng)執(zhí)行p.f()時,會查找p的原型鏈,找到 f 函數(shù)并執(zhí)行,但這與函數(shù)內(nèi)部this指向?qū)ο?p 沒有任何關(guān)系,只需記住誰調(diào)用指向誰。
以上對于函數(shù)作為getter & setter 調(diào)用時同樣適用。
構(gòu)造函數(shù)中this
構(gòu)造函數(shù)中的this與被創(chuàng)建的新對象綁定。
注意:當(dāng)構(gòu)造器返回的默認(rèn)值是一個this引用的對象時,可以手動設(shè)置返回其他的對象,如果返回值不是一個對象,返回this。
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {a:38};
}
var b = new C2();
console.log(b.a); // logs 38
以上兩個例子內(nèi)部的this都指向?qū)ο髈, 看到這里的你不妨在控制臺執(zhí)行下以上代碼,看看對象 o 和 b ,這些是屬于構(gòu)造函數(shù)的內(nèi)容了,此處不多做介紹。(C2函數(shù)中的this.a = 37 對整個過程完全沒有影響的, 可以被忽略的)。
call & apply
當(dāng)函數(shù)通過Function對象的原型中繼承的方法 call() 和 apply() 方法調(diào)用時, 其函數(shù)內(nèi)部的this值可綁定到 call() & apply() 方法指定的第一個對象上, 如果第一個參數(shù)不是對象,JavaScript內(nèi)部會嘗試將其轉(zhuǎn)換成對象然后指向它。
例子:
function add(c, d){
return this.a + this.b + c + d;
}
var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
function tt() {
console.log(this);
}
// 返回對象見下圖(圖1)
tt.call(5); // Number {[[PrimitiveValue]]: 5}
tt.call('asd'); // String {0: "a", 1: "s", 2: "d", length: 3, [[PrimitiveValue]]: "asd"}

bind 方法
bind方法在ES5引入, 在Function的原型鏈上, Function.prototype.bind。通過bind方法綁定后, 函數(shù)將被永遠(yuǎn)綁定在其第一個參數(shù)對象上, 而無論其在什么情況下被調(diào)用。
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 事件處理函數(shù)中的 this & 內(nèi)聯(lián)事件中的 this
DOM事件處理函數(shù)
- 當(dāng)函數(shù)被當(dāng)做監(jiān)聽事件處理函數(shù)時, 其 this 指向觸發(fā)該事件的元素 (針對于addEventListener事件)
// 被調(diào)用時,將關(guān)聯(lián)的元素變成藍(lán)色
function bluify(e){
//在控制臺打印出所點擊元素
console.log(this);
//阻止時間冒泡
e.stopPropagation();
//阻止元素的默認(rèn)事件
e.preventDefault();
this.style.backgroundColor = '#A5D9F3';
}
// 獲取文檔中的所有元素的列表
var elements = document.getElementsByTagName('*');
// 將bluify作為元素的點擊監(jiān)聽函數(shù),當(dāng)元素被點擊時,就會變成藍(lán)色
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}
以上代碼建議在網(wǎng)頁中執(zhí)行以下,看下效果。
內(nèi)聯(lián)事件
內(nèi)聯(lián)事件中的this指向分兩種情況:
- 當(dāng)代碼被內(nèi)聯(lián)處理函數(shù)調(diào)用時,它的this指向監(jiān)聽器所在的DOM元素
- 當(dāng)代碼被包括在函數(shù)內(nèi)部執(zhí)行時,其this指向等同于 ****函數(shù)直接調(diào)用****的情況,即在非嚴(yán)格模式指向全局對象window, 在嚴(yán)格模式指向undefined

頁面的代碼塊

在瀏覽器內(nèi)顯示三個按鈕

依次點擊上邊的三個按鈕后在控制臺的輸出結(jié)果,
建議自己操作一遍以便于更好的理解。
setTimeout & setInterval
對于延時函數(shù)內(nèi)部的回調(diào)函數(shù)的this指向全局對象window(當(dāng)然我們可以通過bind方法改變其內(nèi)部函數(shù)的this指向)
看下邊代碼及截圖
//默認(rèn)情況下代碼
function Person() {
this.age = 0;
setTimeout(function() {
console.log(this);
}, 3000);
}
var p = new Person();//3秒后返回 window 對象
==============================================
//通過bind綁定
function Person() {
this.age = 0;
setTimeout((function() {
console.log(this);
}).bind(this), 3000);
}
var p = new Person();//3秒后返回構(gòu)造函數(shù)新生成的對象 Person{...}


箭頭函數(shù)中的 this
由于箭頭函數(shù)不綁定this, 它會捕獲其所在(即定義的位置)上下文的this值, 作為自己的this值,
- 所以 call() / apply() / bind() 方法對于箭頭函數(shù)來說只是傳入?yún)?shù),對它的 this 毫無影響。
- 考慮到 this 是詞法層面上的,嚴(yán)格模式中與 this 相關(guān)的規(guī)則都將被忽略。(可以忽略是否在嚴(yán)格模式下的影響)
因為箭頭函數(shù)可以捕獲其所在上下文的this值 所以
function Person() {
this.age = 0;
setInterval(() => {
// 回調(diào)里面的 `this` 變量就指向了期望的那個對象了
this.age++;
}, 3000);
}
var p = new Person();
以上代碼可以得到我們所以希望的值,下圖可以看到,在setTimeout中的this指向了構(gòu)造函數(shù)新生成的對象,而普通函數(shù)指向了全局window對象

var adder = {
base : 1,
add : function(a) {
var f = v => v + this.base;
return f(a);
},
addThruCall: function inFun(a) {
var f = v => v + this.base;
var b = {
base : 2
};
return f.call(b, a);
}
};
console.log(adder.add(1)); // 輸出 2
console.log(adder.addThruCall(1)); // 仍然輸出 2(而不是3,其內(nèi)部的this并沒有因為call() 而改變,其this值仍然為函數(shù)inFun的this值,指向?qū)ο骯dder
bind() & apply() 讀者可以自行測試
對于是否嚴(yán)格模式示例代碼(可以復(fù)制進控制臺進行驗證)
var f = () => {'use strict'; return this};
var p = () => { return this};
console.log(1,f() === window);
console.log(2,f() === p());
//1 true
//2 true

以上的箭頭函數(shù)都是在方法內(nèi)部,總之都是以非方法的方式使用,如果將箭頭函數(shù)當(dāng)做一個方法使用會怎樣呢?
上例子
var obj = {
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log( this.i, this)
}
}
obj.b(); // undefined window{...}
obj.c(); // 10 Object {...}

可以看到,作為方法的箭頭函數(shù)this指向全局window對象,而普通函數(shù)則指向調(diào)用它的對象
以上為個人學(xué)習(xí)整理內(nèi)容, 文中例子參考MDN, 歡迎交流學(xué)習(xí)
參考文獻(xiàn)
this - JavaScript | MDN
箭頭函數(shù) - JavaScript | MDN
如需轉(zhuǎn)載請注明出處。