-
目的
最近完成了一個(gè)項(xiàng)目和即將要進(jìn)入一個(gè)新的項(xiàng)目聪铺,在這段時(shí)間內(nèi)相對(duì)來說,可以靜下來看看書萄窜,索性重頭過一遍js铃剔。故記錄在簡(jiǎn)書,如果我的文字有問題查刻,請(qǐng)各位評(píng)論區(qū)評(píng)論键兜。
-
正文
一、 var a = 2 時(shí)穗泵,發(fā)生了什么普气?
var a = 2;
這句代碼,任何一個(gè)寫過js的同學(xué)肯定會(huì)寫過這行聲明和賦值語句佃延。但是當(dāng)我們寫下這句話時(shí)现诀,js是如何解析的?
首先履肃,這整個(gè)過程需要編譯器仔沿,引擎,作用域三者來合作尺棋。編譯器會(huì)先去詢問作用域是否存在變量a, 如果是已經(jīng)聲明過的于未,那么 var a
就會(huì)被忽略,如果不存在呢陡鹃,就會(huì)創(chuàng)建一個(gè)名為a的變量烘浦。然后編譯器繼續(xù)編譯代碼給引擎運(yùn)行。引擎運(yùn)行時(shí)萍鲸,會(huì)去查詢變量a闷叉,如果找到了,就會(huì)進(jìn)行賦值操作脊阴,將2賦值給a握侧。反之,則一直往上面的作用域找嘿期,一直找到全局的作用域品擎,如果這個(gè)時(shí)候還找不到,就拋出一個(gè)ReferenceError的錯(cuò)誤备徐。
其中引擎的查詢方式有兩種萄传,LHS和RHS。前者意味查詢變量是否存在蜜猾,一般是為了賦值秀菱,后者是查詢值振诬,即查詢值的來源,一般用于獲取值衍菱。舉個(gè)例子:
function b (a) {
console.log(a);
}
b(1) // 1
這里呢赶么,首先第四行是一個(gè)RHS查詢,因?yàn)橐鎴?zhí)行到這里時(shí)脊串,它會(huì)去查詢b這個(gè)變量辫呻,獲取b, 然后執(zhí)行b這個(gè)函數(shù)。然后第二行的為了將a的值傳進(jìn)給console.log也是會(huì)進(jìn)行一次RHS查詢琼锋。而調(diào)用函數(shù)b時(shí)傳入一個(gè)1的參數(shù)印屁,這個(gè)1賦值給a這個(gè)形參時(shí),會(huì)發(fā)生一個(gè)LHS查詢斩例。
二、作用域
前面有提到了作用域从橘,js有兩種作用域念赶,一個(gè)是全局作用域,一個(gè)是函數(shù)作用域恰力。
(function () {
var a = 2;
console.log(a);
if (true) {
var a = 1;
console.log(a);
}
console.log(a);
})();
按照C++這類有塊級(jí)作用域的語言叉谜,這里是會(huì)輸出2,1踩萎,2停局,但是js的話,結(jié)果只會(huì)是2香府,1董栽,1。這說明了js這種寫法是不存在塊級(jí)作用域的企孩。如果使用let的話锭碳,就可以達(dá)到塊級(jí)作用域的效果。而作用域之間的關(guān)系勿璃,函數(shù)級(jí)作用域被包含在全局作用域中擒抛,而函數(shù)級(jí)作用域可以包含別的函數(shù)級(jí)作用域。如下圖的代碼:
var a = 1;
function outer () {
var a = 2;
function inner () {
console.log(a);
}
inner();
}
outer();
全局作用域中有變量a和函數(shù)outer, 函數(shù)outer的作用域中變量a和函數(shù)inner, 而函數(shù)inner也有一個(gè)作用域补疑。所以說這里是三層作用域的嵌套歧沪。之前也說了,當(dāng)我們?cè)谧饔糜蛑幸樵円粋€(gè)變量或函數(shù)時(shí)莲组,引擎會(huì)進(jìn)行查詢诊胞,而查詢的方式就是從當(dāng)前的作用域,一直向上面的作用域?qū)ふ仪妈荆钡饺肿饔糜蛳峋绻也坏骄蜁?huì)發(fā)出一個(gè)錯(cuò)誤鳞尔,告訴你該引用不存在。而作用域具有隱蔽性早直。意思是寥假,子作用域可以訪問父作用域,而父作用域是不可能訪問得到子作用域霞扬。
var a = 1;
function outer () {
var a = 2;
function inner () {
console.log(a);
}
inner();
}
inner(); // throw Error : inner is not defined
總之記住糕韧,在當(dāng)前作用域使用一個(gè)變量或者調(diào)用一個(gè)函數(shù)時(shí),只會(huì)從當(dāng)前作用域出發(fā)喻圃,去查詢萤彩,當(dāng)前查不到,就會(huì)往父作用域查詢斧拍,不會(huì)往子作用域查詢的雀扶,一直查詢到全局作用域,最后還查不到就說明沒有聲明肆汹∮弈梗可以思考下,下面的代碼輸出什么昂勉?
var a = 1;
function outer () {
var a = 2;
inner();
}
function inner () {
console.log(a);
}
outer();
另外要注意的兩點(diǎn):
- 在當(dāng)前作用域內(nèi)(非全局作用域)聲明一個(gè)變量時(shí)浪册,一定得用var, let 或者 const
這樣的代碼是不會(huì)報(bào)錯(cuò)的,因?yàn)樗鼤?huì)在隱式在全局聲明一個(gè)變量a(非a = 1;
嚴(yán)格模式下)岗照,這不是添亂么村象。。所以要避免攒至。 - 當(dāng)js越寫越多代碼時(shí)厚者,很容易會(huì)造成作用域污染。比如在你不經(jīng)意間聲明了一個(gè)變量迫吐,它與你若干天前聲明的一個(gè)變量的名字一毛一樣籍救,而且在同一個(gè)作用域下運(yùn)作,比如全局作用域渠抹,那往往會(huì)給你和你的合作者帶來很多麻煩蝙昙,因?yàn)檫@根本不會(huì)報(bào)錯(cuò)好吧。js只會(huì)忽略你的這個(gè)新的 聲明語句梧却,然后給那個(gè)已經(jīng)存在的變量賦值奇颠。這就很難debug了。所以呢放航,要謹(jǐn)慎使用js的作用域烈拒。后面我會(huì)出一篇文章說下這方面的解決方法。
三、提升
大家來看下下面的代碼荆几,如果你是強(qiáng)類型語言的愛好者吓妆,我覺得會(huì)給你帶來很大的疑惑,答案相信大家熟悉提升之后吨铸,就會(huì)知道了行拢。
(() => {
console.log(a);
a();
var a = 1;
console.log(a);
function a () {
console.log(2);
}
a();
})()
js里有一種很不一樣的機(jī)制就是提升機(jī)制了。其中有兩種東西可以提升诞吱。一個(gè)是變量聲明提升和函數(shù)聲明提升舟奠。以下面的例子講解變量聲明提升:
(() => {
console.log(a); // undefined
if (true) {
var a = 2;
}
console.log(a); // 2
})()
按道理來說,強(qiáng)類型語言的代碼是有順序的房维,沒有執(zhí)行到 var a = 2; 時(shí)沼瘫,變量a應(yīng)該未聲明的,但是這里卻輸出了undefined咙俩。其實(shí)耿戚,這就是js的變量聲明的提升了,上面的代碼阿趁,其實(shí)執(zhí)行順序是這樣的:
(() => {
var a;
console.log(a); // undefined
if (true) {
a = 2;
}
console.log(a); // 2
})()
變量a的聲明被提升到最開始的位置膜蛔,而賦值語句則被留在了原地。而函數(shù)聲明也類似:
(() => {
fun(); // 1
function fun () {
console.log(1);
}
})()
函數(shù)fun會(huì)被執(zhí)行歌焦,這是因?yàn)楹瘮?shù)聲明被提前到fun()之前了。但是注意砚哆,函數(shù)表達(dá)式是不具備提升的独撇,即下面的代碼是不會(huì)正常運(yùn)行的。
(() => {
fun(); // fun is not a function
var fun = () => {
console.log(1);
}
})()
上面的代碼可以理解為躁锁,對(duì)fun的聲明被提到最前纷铣,但那個(gè)時(shí)候fun還是undefined,所以當(dāng)成函數(shù)來調(diào)用就會(huì)拋出了錯(cuò)誤战转。
那么問題又來了搜立,當(dāng)一個(gè)函數(shù)內(nèi),同時(shí)具有變量和函數(shù)槐秧,那么這兩者的提升是誰更優(yōu)先呢啄踊?答案是,函數(shù)的提升比變量的提升更優(yōu)先刁标,不信颠通?可以試試下面的代碼:
(() => {
console.log(a);
function a () {
console.log(1);
}
var a = 1;
})()
因此,個(gè)人建議膀懈,在一個(gè)函數(shù)內(nèi)在最開始的地方把需要用到的變量都聲明好顿锰,那就可以盡量避免提升帶來不必要的麻煩。
結(jié)語
這次先到這,如果有錯(cuò)請(qǐng)?jiān)u論吧硼控。寫這類文刘陶,一是讓自己去總結(jié)知識(shí),一方面是交流知識(shí)牢撼。有時(shí)候措辭不當(dāng)或者理解有誤匙隔,出了錯(cuò)誤,在所難免浪默,望大家監(jiān)督和寬容牡直。
(忘了說,最近看的書是 《你不知道的JavaScript》)