序
隨著最近這幾年web前端的飛速發(fā)展,越來越多的人投入到前端開發(fā)的行列。前端的特點是入門簡單、精通極難。很多人在剛接觸前端時很容易產(chǎn)生一些錯誤的認知。大批的前端工程師中,很多開發(fā)者覺得會用jQuery的API,搜集各種jq插件,能夠用這些東西構建出體驗還不錯的小東西就覺足矣。導致目前前端工程師兩極分化嚴重,流弊的能流弊到讓人分分鐘獻上膝蓋、五體投地,技術比較low的讓人大跌眼鏡。其實,不論學習任何技術,對基礎和原理的理解永遠是開發(fā)者的不斷成長和進步的本源,也是衡量一個人能力的關鍵。因此筆者打算將工作生活中研究積累的一些東西和大家分享出來,整理到#deep in JavaScript#,希望和大家共同學習研究核心的、基礎的東西。
問題
該學jQuery還是該學JavaScript?事實上,我們弄清楚了它們之間的關系就覺得這個問題很可笑,但這個問題經(jīng)常會被一些初入前端開發(fā)的開發(fā)者所提及。jQuery是對原生JavaScript的封裝,提供了一些列方便的API,并且優(yōu)化了性能,屏蔽了底層的兼容性等,確實是個不錯的東西。說到這里,JavaScript的重要性十分明了。因此我們應該剖析事務的本質(zhì),深入挖掘JavaScript中的一些問題。
話題
今天我們要討論的是JavaScript中的this,如果不專門去學習研究,很難弄清楚其本源。誠然,因為我是從Java開發(fā)轉(zhuǎn)過來的,在剛接觸js沒多久,覺得js的某些情況下的表現(xiàn)和Java中的this比較類似,就自然而然歸為同一類東西了。在后續(xù)的學習中,看別人的博客和總結總能發(fā)現(xiàn)自己之前的理解有失偏頗,就像專門話精力去深入研究、總結。直到最近看的一本《你不知道的JavaScript(上卷)》中對JavaScript中的this的講解,才把我之前對this的理解全都打通,讓我豁然開朗。
究竟什么是this,this指向?
其實很多編程語言里面都有this,大部分的面向?qū)ο蟮拈_發(fā)語言中,this都是指當前當前對象。由于JavaScript是一個非面向?qū)ο蟮娜躅愋驼Z言,也不存在類的說法。在ES5中,function是一等公民。
在上述的背景下,很多人會聯(lián)想到:this指向函數(shù)本身,指對函數(shù)本身的引用;考慮如下代碼:
function sayHello () {
this.content = 'hello';
}
console.log(content); // ReferenceError: content is not defined
sayHello();
console.log(content); // hello
代碼很簡單,在未調(diào)用函數(shù)時,全局作用域中沒有content的定義,所以報引用錯誤;調(diào)用函數(shù)時,這里將content定義為this的一個屬性,結合最后的輸出來看,這個content被定義為全局對象(瀏覽器中是window,Node中是global)的屬性。可見,在方法定義時,this默認指向了全局對象,這種情況也是非常常見的。
既然不指向函數(shù),看似是指向當某個對象?
前面的代碼,我們更改如下:
function sayHello () {
console.log( window === this );
}
sayHello(); // true
嚴格相等的比較得出的結果是function內(nèi)部(函數(shù)調(diào)用)的this指向了全局對象window(宿主對象為瀏覽器),為什么會這樣呢?我們知道,直接作用域下的定義事實上都定義在了全局對象下,例如:
var a = 23;
console.log( window.a === a ); // true
function sayHello () {
......
}
console.log( window.sayHello === sayHello ); // true
因此sayHello()可以看作是window.sayHello()(事實上就是這樣),因而sayHello()內(nèi)部的this就指向了window,看來this真的是指向某個對象。
指向與函數(shù)調(diào)用
上面的代碼,我們思考下,因為調(diào)用者是window對象,因此sayHello內(nèi)部的this指向window,如果是其他的對象調(diào)用呢?思考如下代碼:
function sayHello () {
console.log( this === window );
console.log( this === obj);
}
var obj = {
sayHello: sayHello
};
// 筆誤:更正為obj.sayHello(),對各位造成的困惑深表歉意,以后一定 嚴格校稿 2017/1/23
// sayHello(); // false true
obj.sayHello(); // false true
由于調(diào)用對象更改為obj了,因此this指向發(fā)生了更改,指向了obj。綜合上面所有的討論,我們可以得出結論:this就是一個指向當前對象的引用,具體指向在運行時得以確認。
引用指向的變更
在JavaScript中,this指向通常在三種情況下發(fā)生變更:
- 創(chuàng)建對象
- 使用call或apply強制更改
- 對象引用
第一種很好理解,根據(jù)ECMA5+的定義,在創(chuàng)建new Function類別的對象時,會將對象內(nèi)部this指向當前對象,因此有如下代碼:
// 用以創(chuàng)建對象的function name首字母大寫是最佳實踐,這里只是作對比用
function sayHello () {
console.log( this === window );
}
new sayHello(); // false
```
這是this指向發(fā)生了改變,指向了new sayHello()語句所創(chuàng)建的新對象;
第二種是調(diào)用Function對象內(nèi)建的call或apply方法(方法作用完全相同,只是第二個參數(shù)傳參不同,apply方法以數(shù)組形式傳參),這種方式會強制將當前this指向call或apply調(diào)用傳參的第一個參數(shù):
```JavaScript
var obj = {
content: 20;
};
var content = 10;
function sayHello () {
console.log( this.content );
}
sayHello.call(obj); // 20
```
第三種即下面例子提到的,體現(xiàn)了this由運行時調(diào)用位置的特點:
```JavaScript
function sayHello () {
console.log( this === window );
console.log( this === obj);
}
var obj = {
sayHello: sayHello
};
sayHello(); // false true
```
# 特殊情況
考慮到新版的ECMAscript6標準已經(jīng)發(fā)布,其應用范圍正在飛速擴大。雖然瀏覽器的支持度不是太好,隨著babel的橫空出世,眾多的開發(fā)人員在日常開發(fā)中都已經(jīng)在享受最新版JavaScript帶來的樂趣。如果你是一名前端開發(fā)人員,我強烈建議你在日常開發(fā)中將babel加入你的工作流技術棧。
新版的JavaScript引入了箭頭函數(shù)() => {};使創(chuàng)建匿名函數(shù)更加簡潔。尤其要注意一點的是,this在這里的指向在其書寫定義的時候就已經(jīng)確定了:
```JavaScript
var sayHello = () => {
console.log( this === window );
console.log( this === obj );
};
var obj = {
sayHello: sayHello
};
obj.sayHello(); // true false
```
# 總結
> 技術日新月異,不變的是那些經(jīng)常被腦殘瞧不起的最簡單、最基本的東西。
> -- Kai Zou
# 說明
本人所有文章不間斷更新,除非哪天我不做前端了(基本不可能^_^)。另外本人能力有限,疏漏支出在所難免。如果你對其中某處有不同的看法,歡迎留言討論。