JavaScript this
當(dāng)前執(zhí)行代碼的環(huán)境對(duì)象,在非嚴(yán)格模式下,總是指向一個(gè)對(duì)象,在嚴(yán)格模式下可以是任意值。(MDN)
this 指向的是當(dāng)前的代碼上下文環(huán)境,所以不同情況下的 this 指向也不同。
1. 全局下的 this
在全局環(huán)境下,this
指向全局對(duì)象。
全局對(duì)象和宿主環(huán)境相關(guān),在瀏覽器下,全局對(duì)象就是 window
對(duì)象,在 node.js
中,全局對(duì)象是 global
對(duì)象。
window === this; // 輸出:true
新的標(biāo)準(zhǔn)提供了
globalThis
關(guān)鍵字來(lái)獲取全局對(duì)象,這樣就能抹平宿主的差異來(lái)操作處理全局對(duì)象了。
2. 函數(shù)中的 this
函數(shù)在不同情況下,其 this
的指向也不同。
2.1 對(duì)象下的方法
方法也是一個(gè)函數(shù),如果通過(guò)對(duì)象調(diào)用一個(gè)函數(shù),函數(shù)的 this
就會(huì)指向這個(gè)對(duì)象。
var person = {
age: 14,
name: '鴿子王',
skill: '放鴿子',
say: function() {
console.log('來(lái)一段自我介紹:');
console.log('我是' + this.name);
console.log('我今年' + this.age + '歲');
console.log('我最擅長(zhǎng)' + this.skill);
},
};
person.say();
say函數(shù)
作為對(duì)象下的方法,在被調(diào)用后,其 this
指向的是他所在的對(duì)象,在這里就是 person
對(duì)象。
2.2 原型鏈上方法的 this
原型鏈上的方法,this 指向的也是調(diào)用該方法的對(duì)象。
var __proto__ = {
sum: function() {
return this.number1 + this.number2;
},
};
var object = Object.create(__proto__);
object.number1 = 1;
object.number2 = 2;
console.log(
object.sum(),
); // 輸出:3
Object.create
做就就是將參數(shù)作為原型,創(chuàng)建一個(gè)對(duì)象。
所以 object
的第一原型就是 __proto__
對(duì)象。
number1
和 number2
都是 object
變量的屬性,但卻可以被 sum
方法中的 this
訪問(wèn)到,所以在原型鏈的方法中,this 指向的就是調(diào)用該方法的對(duì)象。
2.3 getter / setter 下的 this
getter
和 setter
下的 this 也會(huì)指向調(diào)用該 getter
和 setter
的對(duì)象。
var object = {
_name: '鴿子王',
get name() {
return this._name;
},
set name(val) {
console.log(val);
this._name = val;
}
};
console.log(object.name); // 輸出:鴿子王
object.name = '鴿子天王'; // 輸出:鴿子天王
console.log(object.name); // 輸出:鴿子天王
getter
和 setter
本質(zhì)上也可以理解成兩個(gè)函數(shù),作為對(duì)象下的函數(shù),在調(diào)用的時(shí)候 this
也會(huì)指向該對(duì)象。
2.4 作為 DOM 節(jié)點(diǎn)的事件處理器
作為 DOM 節(jié)點(diǎn)的事件處理器的時(shí),函數(shù)的 this
會(huì)指向這個(gè) DOM 對(duì)象。
<div>
<button>點(diǎn)擊我</button>
</div>
<script>
document.querySelector('button').addEventListener('click', function() {
this.innerHTML = '被點(diǎn)擊了!';
});
</script>
2.5 作為一個(gè)內(nèi)聯(lián)的事件處理器
內(nèi)聯(lián)的事件處理器,其 this
指向的是 DOM 節(jié)點(diǎn)自身。
<div>
<button onclick="console.log(this); console.log(this === document.querySelector('button'))">點(diǎn)擊我</button>
</div>
這個(gè)規(guī)則有局限性,只有最外層的 this 符合這個(gè)規(guī)則。
<div>
<button onclick="function test() { console.log(this) }; test();">點(diǎn)擊我</button>
</div>
test
函數(shù)的 this 指向的是全局對(duì)象 window
。
2.6 其他大部分情況下
排開(kāi)上述的幾個(gè)情況,剩下的函數(shù)大部分情況下在調(diào)用時(shí),this 指向的是全局對(duì)象,在瀏覽器中就是 window
對(duì)象。
function fn() {
console.log(this);
console.log(this === window);
}
fn();
這樣調(diào)用函數(shù),其 this 指向的就是 window 對(duì)象了。
有的時(shí)候可能會(huì)搞混以下情況:
var object = {
username: '咸魚(yú)',
fn: function() {
console.log(this.username);
function thisTest() {
console.log(this.username);
console.log(this === window);
}
thisTest();
},
};
object.fn();
這里 thisTest
方法輸出的 username
就會(huì)是個(gè) undefined,因?yàn)樗?this 指向的是 window,因?yàn)樗粚儆?object
對(duì)象的一個(gè)方法,所以 this 就指向了 window。
在回調(diào)函數(shù)中經(jīng)常會(huì)碰到這個(gè)問(wèn)題:
var info = {
account: '123',
password: '456',
login: function(cb) {
setTimeout(function() {
cb({
account: this.account,
password: this.password,
});
}, 1000);
}
};
info.login(function(info) {
console.log(info);
});
這里回調(diào)函數(shù)獲取的賬號(hào)和密碼是 undefined
,原因就是 this 的指向問(wèn)題。
通常會(huì)使用保留上層 this 的方式解決這個(gè)問(wèn)題。
var info = {
account: '123',
password: '456',
login: function(cb) {
var _this = this;
setTimeout(function() {
cb({
account: _this.account,
password: _this.password,
});
}, 1000);
}
};
info.login(function(info) {
console.log(info);
});
這樣就能解決這個(gè)問(wèn)題。
另外一個(gè)情況也很容易混淆 this :
var object = {
user: 'no.1',
say: function() {
console.log(this.user);
},
};
var say = object.say;
object.say(); // 輸出:"no.1"
say(); // 輸出:undefined
這是因?yàn)榘?object
下的 say
方法單獨(dú)賦值給 say 變量的時(shí)候,其就作為了 window 下的一個(gè)方法,所以他的 this 指向的是 window。
在嚴(yán)格模式中,這種情況下的 this
會(huì)變成 undefined
。
2.7 構(gòu)造函數(shù)
在 JavaScript 構(gòu)造函數(shù)也被成為 對(duì)象構(gòu)造器
,用于產(chǎn)生對(duì)象。
構(gòu)造函數(shù)的聲明和普通函數(shù)幾乎沒(méi)有區(qū)別:
function Point(x, y) {
this.x = x;
this.y = y;
}
var point = new Point(1, 2);
console.log(point.x); // 輸出:1
console.log(point.y); // 輸出:2
構(gòu)造函數(shù)使用 new
關(guān)鍵字來(lái)構(gòu)造對(duì)象。所以當(dāng)一個(gè)函數(shù)被使用 new
關(guān)鍵字調(diào)用時(shí),這個(gè)函數(shù)就會(huì)作為一個(gè)構(gòu)造函數(shù)。
在一個(gè)構(gòu)造函數(shù)被調(diào)用后,其內(nèi)部的 this
會(huì)指向一個(gè)對(duì)象,具體的內(nèi)容可以參考 構(gòu)造函數(shù)
章節(jié)。
3. 修改this
3.1 call 方法和 apply 方法
函數(shù)具有 call
方法和 apply
方法,這兩個(gè)方法可以在調(diào)用函數(shù)的時(shí)候指定函數(shù)的 this。
var object = {
user: 'no.1',
};
function say() {
console.log(this.user);
}
say(); // 輸出:undefined
say.call(object); // 輸出:"no.1"
say.apply(object); // 輸出:"no.1"
通過(guò) call
和 apply
方法將 say 函數(shù)執(zhí)行時(shí)候的 this 設(shè)置為 object
對(duì)象。
call 方法從第二個(gè)參數(shù)開(kāi)始,表示是要傳遞給當(dāng)前函數(shù)的參數(shù)。
var object = {
user: 'no.1',
};
function fn(arg1, arg2, arg3) {
console.log(
this,
arg1,
arg2,
arg3,
);
}
fn.call(object, 1, 2, 3);
apply 的第二個(gè)參數(shù)是個(gè)數(shù)組,數(shù)組里面的項(xiàng)會(huì)按數(shù)組的順序作為參數(shù)傳遞給函數(shù)。
var object = {
user: 'no.1',
};
function fn() {
console.log(
this,
arguments,
);
}
fn.apply(object, [1, 2, 3]);
通過(guò) arguments
關(guān)鍵字就可以看到當(dāng)前函數(shù)的參數(shù),通常在需要修改 this ,又不確定參數(shù)的情況下,會(huì)使用 apply
來(lái)修改 this。
3.2 bind
bind 方法用于給一個(gè)函數(shù)永久綁定一個(gè)指定的 this,bind 不會(huì)修改原函數(shù),會(huì)返回一個(gè)新的函數(shù)。
var obj1 = { value: '今天打磚' };
var obj2 = { value: '明天打轉(zhuǎn)' };
var fn = function() {
console.log(this);
};
var bindFn1 = fn.bind(obj1)
var bindFn2 = bindFn1.bind(obj2);
bindFn1();
bindFn2();
可以看到 bindFn1
被綁定了 obj1
作為 this,之后不論怎么操作,他的 this 都會(huì)是 obj1
。
bind 還有更多靈活的用法,參數(shù)也可以綁定,有關(guān) bind、call、apply 這三個(gè)方法的更詳細(xì)的信息可以查閱對(duì)應(yīng)的文檔。
4. 小結(jié)
理解好 this 的處理機(jī)制可以設(shè)計(jì)出更加完善的 JavaScript 應(yīng)用程序。
this 在 ES6 的箭頭函數(shù)中的表現(xiàn)也有所不同,可以查閱 ES6 中有關(guān)箭頭函數(shù)的內(nèi)容。