引言
可以說(shuō)是取名廢了,把這幾個(gè)關(guān)鍵詞放在一起也是因?yàn)榭赐晗?shū)相關(guān)章節(jié)和很多技術(shù)文章之后覺(jué)得這幾個(gè)概念是相互滲透的,需要放在一起理解。而在這之前,我對(duì)這幾個(gè)概念都是久聞其名,真正遇到了似懂非懂還很心虛,每每看完一篇零零散散的技術(shù)文章總覺(jué)得明白了,但是下一次遇到了還是不明白。這種其實(shí)充其量只能說(shuō)記住了一些知識(shí)點(diǎn)碎片,而學(xué)習(xí)不能只是碎片,必須是找到碎片與碎片之間的關(guān)聯(lián)與規(guī)律,才能將知識(shí)碎片連成完整的知識(shí)體系吃下去。所以,某位大佬說(shuō)得對(duì),有產(chǎn)出的學(xué)習(xí)才是真的學(xué)習(xí),畢竟如果不是真的搞清楚了,你什么也寫(xiě)不出來(lái),何況寫(xiě)一遍還能加深印象。如果你也有類(lèi)似的情況,請(qǐng)重視起來(lái),一起加油叭!
本章對(duì)標(biāo)犀牛書(shū)第6版3.9變量聲明、3.10變量作用域和第8大章8.6之前的章節(jié),之后兩小節(jié)值得在后面的文章里更詳細(xì)的單獨(dú)研究!
變量聲明和函數(shù)定義
你可能覺(jué)得這過(guò)于基礎(chǔ)了,但事實(shí)上如果忽略其中的一些細(xì)節(jié)會(huì)影響后面的理解。
變量聲明
我們知道在ES6之前,我們通常用var關(guān)鍵字來(lái)聲明一個(gè)或多個(gè)變量var i,sum;,還可以將變量的初始賦值和變量聲明寫(xiě)在一起var i = 0, j = 1, k = 2;,如果沒(méi)有在var聲明語(yǔ)句中給變量指定初始值,那么雖然聲明了變量,但在給他賦值之前,它的值就是undefined。
如果試圖讀取一個(gè)沒(méi)有聲明的變量,會(huì)觸發(fā)一個(gè)報(bào)錯(cuò)。在嚴(yán)格模式下,給一個(gè)未聲明的變量賦值也會(huì)觸發(fā)一個(gè)報(bào)錯(cuò),在非嚴(yán)格模式下,給一個(gè)未聲明的變量賦值,js實(shí)際上會(huì)給全局對(duì)象創(chuàng)建一個(gè)同名屬性,使其工作起來(lái)像一個(gè)正常聲明的全局變量。
var聲明的屬性是不可配置的,也就是不可以用delete操作符刪除,而給未聲明的變量賦值創(chuàng)建的全局對(duì)象同名屬性是正常的可配置屬性,是可以被刪除的
函數(shù)定義
函數(shù)定義可以通過(guò)兩種方式來(lái)進(jìn)行:
- 函數(shù)定義表達(dá)式
var func = function(){...}; - 函數(shù)聲明
function func(){...}
雖然兩者都包含相同的函數(shù)名,都創(chuàng)建了一個(gè)新的函數(shù)對(duì)象,但還是有所區(qū)別,區(qū)別就體現(xiàn)在聲明提升的時(shí)候。
作用域和聲明提升
全局作用域和函數(shù)作用域
ES6之前,JS是沒(méi)有常用的不受約束的塊級(jí)作用域的,只有全局作用域和函數(shù)作用域。全局作用域是頂級(jí)作用域,顧名思義,在頂級(jí)聲明的所有變量全局中都可訪問(wèn);函數(shù)作用域是指在函數(shù)內(nèi)聲明的所有變量在函數(shù)體內(nèi)始終可見(jiàn),包括嵌套函數(shù)內(nèi)。以及,函數(shù)體內(nèi)的局部變量?jī)?yōu)先級(jí)高于同名的全局變量,如果函數(shù)內(nèi)聲明的全局變量或函數(shù)參數(shù)帶有的變量和全局變量同名,那么全局變量就被局部變量覆蓋。例如:
var scope = 'global';
function f() {
var scope = 'local';
console.log(scope); //local 同名的全局變量被局部變量覆蓋
function f2() {
console.log(scope); //local 函數(shù)體內(nèi)聲明的變量在嵌套函數(shù)內(nèi)也可見(jiàn)
}
f2();
}
f();
聲明提升
正常情況下我們認(rèn)為js語(yǔ)句是由上到下一句一句執(zhí)行的,這完全沒(méi)錯(cuò),但是有一種情況會(huì)使我們很迷惑。
a = 2;
var a;
console.log(a); //2
console.log(a); //undefined
var a = 2;
實(shí)際上這是因?yàn)榘ㄗ兞亢秃瘮?shù)在內(nèi)的所有聲明(但不涉及賦值)都會(huì)在如何代碼被執(zhí)行前也就是js的預(yù)編譯階段被首先處理,會(huì)被”提升“到相應(yīng)作用域的頂部,這個(gè)過(guò)程就是聲明提升,關(guān)于聲明提升,我們只需要注意以下幾點(diǎn):
-
函數(shù)聲明也會(huì)被提升,但函數(shù)表達(dá)式不會(huì)。也就是說(shuō)上述兩種函數(shù)定義的方式,使用函數(shù)聲明語(yǔ)句,函數(shù)變量名和函數(shù)體均會(huì)提升,但使用
var的表達(dá)式,只是變量聲明被提升,函數(shù)體會(huì)留在原來(lái)的位置等待執(zhí)行;f(); function f(){ console.log(a); //會(huì)被執(zhí)行且輸出undefined var a = 2; } f(); //TypeError f is not a function var f = function bar() { console.log(a); var a = 2; } -
var定義的具名的函數(shù)表達(dá)式,函數(shù)名變量也不會(huì)被提升;f(); //TypeError f is not a function bar(); //ReferenceError: bar is not defined var f = function bar() { console.log(a); var a = 2; } //注意這里f()拋出的是TypeError而不是ReferenceError 是因?yàn)樽兞棵呀?jīng)被提升但沒(méi)有被賦值,所以此時(shí)f是undefined,對(duì)undefined做調(diào)用執(zhí)行,因此拋TypeError異常。 //這段代碼實(shí)際上被編譯成以下形式: var f; f(); //TypeError bar(); //ReferenceError f = function() { var bar = self; var a a = 2; } -
函數(shù)聲明優(yōu)先,已知變量聲明和函數(shù)聲明都會(huì)被提升,如果同一作用域里有相同命名的函數(shù)聲明和變量聲明提升,函數(shù)聲明優(yōu)先。
foo(); // foo2 var foo = function() { console.log('foo1'); } foo(); // foo1,foo重新賦值 function foo() { console.log('foo2'); } foo(); // foo1 //這段代碼實(shí)際上被形容成 function foo() { console.log('foo2'); } foo(); foo = function() { console.log('foo1'); } foo(); foo(); //盡管var foo出現(xiàn)在function foo(){..}之前,但是函數(shù)聲明會(huì)被提升到普通變量聲明之前,因此重復(fù)的聲明會(huì)被忽略,但會(huì)被重新賦值。此外,后面的函數(shù)聲明還是可以覆蓋前面的,比如: foo(); // foo2 function foo() { console.log('foo1'); } function foo() { console.log('foo2'); } -
條件語(yǔ)句中的函數(shù)聲明:
這個(gè)東西迷惑了我好久!??!浪費(fèi)我的時(shí)間!?。?/strong>
MDN中的相關(guān)解釋?zhuān)?a target="_blank">https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function
Segmentfault中回復(fù)樓的相關(guān)解釋?zhuān)?a target="_blank">https://segmentfault.com/q/1010000005337550
以及下面的(【阮一峰】ES6入門(mén))中也有相關(guān)解釋?zhuān)?a target="_blank">【阮一峰】ES6入門(mén)
foo(); var a = true; if(a) { function foo(){console.log('foo1')} } else { function foo(){console.log('foo2')} } //理論上,這段代碼會(huì)輸出foo2 //經(jīng)實(shí)驗(yàn),實(shí)際上在大多數(shù)瀏覽器控制臺(tái)里會(huì)拋出TypeError foo is not a function,在Safari里輸出了foo2 (function () { if (false) { // 重復(fù)聲明一次函數(shù)f function f() { console.log('I am inside!'); } } f(); //TypeError foo is not a function }()); //總之,在塊級(jí)作用域和條件語(yǔ)句中聲明函數(shù),在不同瀏覽器中解釋的標(biāo)準(zhǔn)都不一樣,考慮到環(huán)境導(dǎo)致的行為差異太大,不要在塊級(jí)作用域或條件語(yǔ)句中聲明函數(shù)!?。?// 等我有時(shí)間了再來(lái)糾結(jié)這個(gè)吧。。。。。。不過(guò)感覺(jué)遙遙無(wú)期。。。。。
塊級(jí)作用域
偷個(gè)懶~ 【阮一峰】ES6入門(mén)
注釋一點(diǎn):
絕大部分的文章里都提到在ES6之前,js中是沒(méi)有塊級(jí)作用域的,只有函數(shù)作用域和全局作用域,實(shí)際上,ES3中的try/catch語(yǔ)句中的catch語(yǔ)句就會(huì)創(chuàng)建一個(gè)塊級(jí)作用域
try { throw 2 } catch (a) { console.log(a); //2 } console.log(a);//Reference Error我試著在babel在線編譯器里轉(zhuǎn)換
{ let a = 2; console.log(a); } console.log(a);但是得到的是
"use strict"; { var _a = 2; console.log(_a); } console.log(a);
作用域鏈和閉包
作用域鏈
說(shuō)作用域鏈先理解一下自由變量:當(dāng)前作用域沒(méi)有定義的變量即自由變量。如何訪問(wèn)到自由變量:向父級(jí)作用域?qū)ふ摇H绻讣?jí)作用域沒(méi)有,則一層一層向上尋找,直到找到全局作用域,如果還是沒(méi)找到,則宣布放棄。
這個(gè)一層一層向上的層級(jí)關(guān)系,就是作用域鏈,它是一個(gè)對(duì)象列表。
只用記住一點(diǎn):自由變量將從作用域鏈中去尋找, 依據(jù)的是函數(shù)定義時(shí)的作用域鏈,而不是函數(shù)執(zhí)行時(shí),JS采用的是靜態(tài)作用域,依據(jù)的是函數(shù)定義時(shí)的位置。
function F1() {
var a = 100
return function () {
console.log(a)
}
}
var f1 = F1()
var a = 200
f1() //100
閉包
當(dāng)函數(shù)可以記住并訪問(wèn)函數(shù)體內(nèi)的變量時(shí),就形成了閉包,因此嚴(yán)格來(lái)說(shuō)每個(gè)函數(shù)都是閉包。
注意三點(diǎn):
-
同一個(gè)作用域鏈中的多個(gè)閉包共享私有變量或變量
function countFunc() { var funcs = []; for(var i = 0; i < 10; i++){ funcs[i] = function (){ return i } } return funcs } countFunc()[0]() //10 //這段代碼創(chuàng)建了10個(gè)閉包,但是都是在一個(gè)函數(shù)調(diào)用中定義的,因此它們共享變量i -
每次調(diào)用js函數(shù)的時(shí)候,都會(huì)創(chuàng)建一個(gè)新的作用域鏈
function counter() { var n = 0; return { counter: function() { return n++; }, reset: function() { n = 0; } } } var c = counter(); var d = counter(); c.counter(); //0 d.counter(); //0 互不干擾 c.reset(); //重置c c.counter(); //c的reset和counter共享n變量 this是JS關(guān)鍵字而不是變量,每一個(gè)函數(shù)調(diào)用都包含一個(gè)this值,如果閉包在外部函數(shù)中,是無(wú)法訪問(wèn)外部函數(shù)的this值的,arguments同理,雖然它不是關(guān)鍵字,但是調(diào)用函數(shù)時(shí)會(huì)自動(dòng)聲明它。
執(zhí)行上下文this
記住大佬做的一張圖就夠了(侵刪):
小結(jié)
有點(diǎn)曲折的一章,被條件語(yǔ)句和塊級(jí)作用域中的函數(shù)聲明搞得頭大。
犀牛書(shū)沒(méi)有把這一塊說(shuō)的很清楚,看了《你不知道的JS》相關(guān)章節(jié)作補(bǔ)充
后續(xù)需要補(bǔ)充:js編譯執(zhí)行機(jī)制,this詳解,面試題詳解