1. 關(guān)于This
相比在Java中this
只指代當(dāng)前類的用法薛耻,Javascript中this
的并不是一個(gè)固定的指代,其指代存在綁定規(guī)則
1.1 使用原因
其實(shí)大部分情況下,使用詞法作用域已經(jīng)可以解決所有需要使用this
的場景苦丁,代碼中完全可以不使用this
。但是使用this
可以帶來函數(shù)調(diào)用過程中對于函數(shù)參數(shù)傳遞的優(yōu)化物臂。舉個(gè)例子:
function foo(a, b){
console.log(a + b);
}
f(1, 2); // 3
在函數(shù)調(diào)用過程中需要傳遞兩個(gè)參數(shù)進(jìn)行調(diào)用旺拉,但是如果參數(shù)是某個(gè)對象中的某些屬性,那么使用這種調(diào)用方式就變成如下情況:
var obj = {a: 1 ,b: 2};
foo(obj.a, obj.b); // 3
在傳遞參數(shù)的時(shí)候明顯比較麻煩棵磷,尤其在于如果要利用對象中的復(fù)數(shù)屬性的時(shí)候蛾狗,參數(shù)傳遞就變得特別多,這個(gè)時(shí)候使用this
就可以進(jìn)行簡化:
function foo() {
console.log(this.a + this.b);
}
var obj = {a: 1, b: 2};
foo.apply(obj); // 3
當(dāng)然我們完全可以將obj
作為參數(shù)進(jìn)行傳遞仪媒,所以說使用this
并非開發(fā)中必須沉桌。
1.2 綁定要點(diǎn)
this
到底代表什么關(guān)鍵在于this
的綁定規(guī)則,但無論什么規(guī)則算吩,首先要明確兩點(diǎn):第一留凭,this
不代表自身,第二偎巢,this
和詞法作用域沒有必然聯(lián)系例:
function foo() {
console.log(this.a); // 這里的this并不指代foo函數(shù)本身蔼夜,也和詞法作用域無關(guān)
}
以上兩點(diǎn)從根本上來說指在函數(shù)進(jìn)行聲明的時(shí)候,并不能確定this
代表什么压昼,只有在函數(shù)調(diào)用的時(shí)候求冷,this
的綁定關(guān)系才可以確定(就像動(dòng)態(tài)作用域一樣)
this
只有在函數(shù)調(diào)用的時(shí)候確定綁定關(guān)系
this
只有在函數(shù)調(diào)用的時(shí)候確定綁定關(guān)系
this
只有在函數(shù)調(diào)用的時(shí)候確定綁定關(guān)系
重要的事情說三遍,不過再補(bǔ)充一下:
箭頭函數(shù)是特別的
箭頭函數(shù)是特別的
箭頭函數(shù)是特別的
2. 綁定規(guī)則
根據(jù)Javascript中的用法窍霞,this
有以下的幾種綁定規(guī)則:
2.1 全局綁定
直接在Javascript全局中使用this
匠题,這個(gè)時(shí)候this
指向一個(gè)全局對象,在瀏覽器中代表window
對象但金,node中代表global
對象:
console.log(this);
2.2 Function Invoke(默認(rèn)綁定)
直接調(diào)用方法的時(shí)候韭山,如果在嚴(yán)格模式下,this
的綁定為undefined
(之所以返回undefined
,是因?yàn)楹瘮?shù)調(diào)用的時(shí)候執(zhí)行的上下文并不確定钱磅,嚴(yán)格模式于是將執(zhí)行中的this
綁定為undefined
)巩踏,非嚴(yán)格模式下,this
綁定為全局對象续搀,同 2.1 全局綁定 :
// 1. 非嚴(yán)格模式下
function foo(){
console.log(this);
}
foo(); // window
// 2. 嚴(yán)格模式下
(function(){
'use strict'
function foo() {
console.log(this);
}
foo(); // undefined
})()
// 3. 嚴(yán)格模式下調(diào)用
(function(){
'use strict'
foo(); // window
})()
請注意例子中的第三個(gè)調(diào)用塞琼,在調(diào)用的時(shí)候使用了嚴(yán)格模式,但是打印結(jié)果并不是undefined
禁舷,因此說明嚴(yán)格模式是在函數(shù)聲明的時(shí)候?qū)?code>this的綁定產(chǎn)生影響彪杉,在調(diào)用的時(shí)候?qū)Υ瞬]有影響。
如果之后所有的規(guī)則都不符合的時(shí)候牵咙,將使用這一條規(guī)則派近,來進(jìn)行this
的綁定。
2.3 Method Invoke(隱式綁定)
當(dāng)使用對象調(diào)用的時(shí)候洁桌,this
將綁定為該對象:
var obj = {
a: 1,
foo: function() {
console.log(this.a);
}
}
obj.foo(); // 1
但是隱式綁定過程存在兩種隱式丟失的情況(可以使用箭頭函數(shù)解決渴丸,詳見 2.6 箭頭函數(shù)),使用函數(shù)別名和回調(diào)函數(shù):
var obj = {
a: 1,
foo: function() {
console.log(this.a);
}
}
// 1. 使用函數(shù)別名
var baz = obj.foo;
baz(); // undefined
// 2. 使用回調(diào)函數(shù)
setTimeout(obj.foo , 0); // undefined
原因在于在使用函數(shù)別名和回調(diào)函數(shù)的時(shí)候另凌,可以理解為進(jìn)行了以下的操作
var baz = obj.foo = function() {
console.log(this.a);
}
于是谱轨,在調(diào)用baz
的時(shí)候,將根據(jù) 2.2 默認(rèn)綁定 對this
進(jìn)行處理
2.4 apply, call, bind (顯示綁定)
使用顯示綁定的時(shí)候吠谢,this
綁定為傳入的對象:
function foo(a, b, c){
console.log(this.a + a + b + c);
}
foo.apply({a: 1}, [1, 2, 3]) // 1 + 1 + 2 + 3 = 7
foo.call({a: 1}, 1, 2, 3) // 7
foo.bind({a: 1})(1, 2, 3) // 7
apply
和call
的使用本質(zhì)上沒有區(qū)別土童,只是傳遞參數(shù)不同
bind
是ES5之后Function
原型鏈上增加的方法,先將this
綁定到傳遞的對象工坊,但是使用bind
進(jìn)行綁定的時(shí)候是硬綁定献汗,硬綁定是指這個(gè)綁定只能綁定一次,只有第一次綁定可以生效:
function foo() {
console.log(this.a);
}
foo.bind({a: 1}).bind({a: 2})(); // 1
因?yàn)楝F(xiàn)在很多內(nèi)置對象提供了原型鏈上的方法王污,于是通過這種原型鏈上API的調(diào)用是顯示調(diào)用的過程
function foo(i) {
console.log(this.a + i);
}
[1,2,3].forEach(foo, {a: 1}); // 2 3 4
2.5 new(構(gòu)造調(diào)用)
使用new
進(jìn)行綁定的時(shí)候罢吃,this
綁定為構(gòu)建函數(shù)構(gòu)造的新對象,例:
function F() {
this.a = 1;
}
var f = new F(); // 等同于 var f = new F;
console.log(f.a); // 1
這里使用new
進(jìn)行構(gòu)造的時(shí)候昭齐,Javascript會(huì)按照以下的過程執(zhí)行
創(chuàng)建一個(gè)新對象 --> 將對象和構(gòu)造方法的prototype
做關(guān)聯(lián) --> 將this
綁定到該對象 --> 如果構(gòu)造函數(shù)無返回值則返回該對象尿招,否則返回新對象
所以糟袁,轉(zhuǎn)換為代碼理解也就是:
// 1. 無返回的構(gòu)造函數(shù)
function F() {
this.a = 1;
}
/* 使用new以后异赫,函數(shù)內(nèi)部變化為
function F() {
var _o_ = {} ; // 創(chuàng)建一個(gè)新對象
_o_.prototpye = F.prototype; // 原型鏈關(guān)聯(lián)
_o_.a = 1; // this綁定到對象
return _o_; // 返回該對象
}
*/
var f = new F();
console.log(f.a); // 1
// 2. 有返回的構(gòu)造函數(shù)
function F() {
this.a = 1;
return {b: 2};
}
/* 使用new以后,函數(shù)內(nèi)部變化為
function F() {
var _o_ = {} ; // 創(chuàng)建一個(gè)新對象
_o_.prototpye = F.prototype; // 原型鏈關(guān)聯(lián)
_o_.a = 1; // this綁定到對象
return {b: 2}; // 返回原返回值
}
*/
var f = new F();
console.log(f.a) // undefined
console.log(f.b) // 2
于是,如果原構(gòu)造函數(shù)存在返回值的情況下啊易,this
綁定為了函數(shù)內(nèi)部創(chuàng)建的新對象,但是這個(gè)新創(chuàng)建的對象并沒有返回饮睬,所以沒辦法再對這個(gè)綁定的內(nèi)容進(jìn)行引用了租谈。
2.6 箭頭函數(shù)
使用箭頭函數(shù)進(jìn)行綁定的時(shí)候,this
綁定外層(函數(shù)或者全局)作用域環(huán)境:
var obj = {
a: 1,
foo: () => {
console.log(this);
}
}
obj.foo(); // window
這個(gè)例子看上去和 2.3 隱式綁定 是差不多的,但是區(qū)別這里的this
指向發(fā)生變化割去,this不再指向調(diào)用對象本身窟却,而是直接使用外層函數(shù)全局對象。
但是下面的例子是需要注意的:
function f(){
return function() {
console.log(this.a);
}
}
f.apply({a:1}).apply({a:2}) // 2;
// 箭頭函數(shù)
function f(){
return() => {
console.log(this.a);
}
}
f.apply({a:1}).apply({a:2}) // 1
在第一次調(diào)用 apply
的時(shí)候呻逆,生成了一個(gè)外層的作用域夸赫,此時(shí)this
指向了該外層全局對象的{a: 1}
,之后不會(huì)再發(fā)生變化
2.7 事件綁定
使用事件函數(shù)進(jìn)行綁定的時(shí)候咖城,根據(jù)使用情況不同茬腿,this
通常指向當(dāng)前事件節(jié)點(diǎn),例:
// 1. 使用addEventListener等事件綁定宜雀,this指向綁定的節(jié)點(diǎn)
<html>
<body>
<button id="d">click</button>
<script>
document.getElementById('d').addEventListener('click', function(e){
console.log(this === e.currentTarget); // true
console.log(this === e.target); // true
})
</script>
</body>
</html>
// 2. 使用html事件綁定切平,this指向當(dāng)前dom節(jié)點(diǎn)
<html>
<body>
<button id="d" onclick="console.log(this);">click</button>
</body>
</html>
// 3. 使用html事件綁定,增加IIFE辐董,this指向window
<html>
<body>
<button id="d" onclick="(function(){console.log(this)})()">click</button>
</body>
</html>
3. 優(yōu)先級
當(dāng)我們?nèi)绻褂玫那闆r比較復(fù)雜悴品,同時(shí)存在以上幾種綁定關(guān)系的時(shí)候,我們該如何處理简烘?實(shí)際上綁定的關(guān)系存在一個(gè)優(yōu)先級苔严,按照優(yōu)先級來進(jìn)行處理(這里除開 2.7 事件綁定,因?yàn)橥ǔJ录壎ㄏ虏粫?huì)存在那么復(fù)雜的關(guān)系)
2.5 構(gòu)造綁定 > 2.4 顯示綁定 > 2.3 隱式綁定 > 2.2 默認(rèn)綁定
2.6 箭頭函數(shù) 有特殊表現(xiàn)
例:
// 1. 隱式綁定 > 默認(rèn)綁定
function foo() {
console.log(this.a);
}
var obj = {a: 1, foo: foo};
obj.foo(); // 1
// 2. 顯示綁定 > 隱式綁定
obj.foo.apply({a: 2}) // 2
// 3. 構(gòu)造綁定 > 顯示綁定
function F(a) {
this.a = a;
}
var obj = {};
var bar = F.bind(obj);
bar(1);
console.log(obj.a); // 1
var obj2 = new bar(2);// 使用new修改了this的綁定
console.log(obj.a); // 1 原來的值沒有發(fā)生變化
console.log(obj2.a); // 2 新的值發(fā)生了變化
// 4. 箭頭函數(shù)的特殊表現(xiàn)
var a = 2;
var f = () => {
console.log(this.a);
}
f.apply({a: 3}); // 2
4. 其他
4.1 顯示綁定中null
的使用
在進(jìn)行顯示綁定的時(shí)候孤澎,如果傳遞綁定對象為null
時(shí)邦蜜,因?yàn)椴环衔覀冋f的其他規(guī)則,所以將使用默認(rèn)規(guī)則:
function foo() {
console.log(this);
}
foo.apply(null) // window
但是如果直接傳遞null
進(jìn)行方法調(diào)用存在變量泄漏的風(fēng)險(xiǎn):
function foo() {
this.a = 1;
}
foo.apply(null);
console.log(a); // 1
4.2 Object.create(null)
為了解決 4.1 顯示綁定中null的使用 存在的變量泄漏風(fēng)險(xiǎn)亥至,于是可以使用Object.create(null)
創(chuàng)建一個(gè)空對象悼沈,使用這種方式創(chuàng)建的空對象,比{}
更純粹姐扮,因?yàn)樗焕^承Object.prototype
function foo() {
this.a = 1;
}
foo.apply(Object.create(null));
console.log(a); // ReferenceError
5. 參考
《你不知道的Javascript(上卷)》
MDN this
Gentle explanation of 'this' keyword in JavaScript
Function.prototype.call
Function.prototype.apply
Function.prototype.bind
6. 練習(xí)
慣例最后來個(gè)練習(xí)吧
function foo() {
console.log(this.a);
}
var obj = {
a: 1,
foo: foo
}
var obj2 = {
a: 1,
foo: () => {
console.log(this.a);
}
}
function F() {
this.a = 2;
}
var f = new F();
foo(); // undefined
obj.foo(); // 1
obj.foo.bind({a: 2})(); // 2
obj2.foo.bind({a: 2})(); // undefined
console.log(f.a); // 2