摘要:的分句會(huì)創(chuàng)建一個(gè)塊作用域,其聲明的變量?jī)H在中有效。而閉包的神奇作用是阻止此事發(fā)生。依然持有對(duì)該作用域的引用,而這個(gè)引用就叫做閉包。當(dāng)然,無(wú)論使用何種方式對(duì)函數(shù)類型的值進(jìn)行傳遞,當(dāng)函數(shù)在別處被調(diào)用時(shí)都可以觀察到閉包。
LHS:賦值操作的目標(biāo)是誰(shuí)?
比如:
a = 2;
RHS:誰(shuí)是賦值操作的源頭?
比如:
console.log(2);
作用域嵌套:遍歷嵌套作用域鏈的規(guī)則:引擎從當(dāng)前的執(zhí)行作用域開(kāi)始查找變量,如果找不到,就向上一級(jí)繼續(xù)查找。當(dāng)?shù)诌_(dá)最外層的全局作用域時(shí),無(wú)論是否找到都會(huì)停止。
異常:為什么區(qū)分LHS和RHS是一件重要的事情?
如果RHS查詢?cè)谒星短椎淖饔糜蛑斜閷げ坏剿璧淖兞浚婢蜁?huì)拋出ReferenceError異常。
當(dāng)引擎在執(zhí)行LHS查詢時(shí),如果在頂層作用域也無(wú)法找到目標(biāo)變量,全局作用域就會(huì)創(chuàng)建一個(gè)具有該名稱的變量,并將其返回給引擎。(非嚴(yán)格模式下)
如果RHS查詢找到了一個(gè)變量,但你嘗試對(duì)這個(gè)變量的值進(jìn)行不合理的操作,比如試圖對(duì)一個(gè)非函數(shù)類型的值進(jìn)行函數(shù)調(diào)用,或者引用null或undefined類型的值中的屬性,引擎會(huì)拋出TypeError.
ReferenceError同作用域判別失敗相關(guān),TypeError則代表作用域判別成功但對(duì)結(jié)果的操作是非法或不合理的。
詞法作用域
詞法作用域就是定義在詞法階段的作用域。詞法作用域是由你在寫(xiě)代碼時(shí)將變量和塊作用域?qū)懺谀睦飦?lái)決定的,因此當(dāng)詞法分析器處理代碼時(shí)會(huì)保持作用域不變。
作用域查找會(huì)在找到第一個(gè)匹配的標(biāo)識(shí)符時(shí)停止:遮蔽效應(yīng)。(全局變量可以使用window.a來(lái)訪問(wèn))
欺騙詞法
eval():可以對(duì)一段包含一個(gè)或多個(gè)聲明的代碼字符串進(jìn)行演算,并借此來(lái)修改已經(jīng)存在的詞法作用域(在運(yùn)行時(shí))
function foo(str, a){ eval( str ); console.log(a,b); } var b = 2 foo("var b = 3;",1); //1,3
with關(guān)鍵字:本質(zhì)上是用過(guò)講一個(gè)對(duì)象的引用當(dāng)作作用域來(lái)處理,將對(duì)象的屬性當(dāng)作作用域中的標(biāo)識(shí)符來(lái)處理,從而創(chuàng)建了一個(gè)新的詞法作用域。
function foo(obj) { with (obj) { a = 2; } } var o1 = { a:3 }; var o2 = { b:3 }; foo(o1); console.log( o1.a ); //2 foo(o2); console.log( o2.a ); // undefined console.log(a); //2---不好,a被泄露到全局作用域上了。第三章 函數(shù)作用域和塊作用域
函數(shù)作用域的含義指,屬于這個(gè)函數(shù)的全部變量都可以在整個(gè)函數(shù)的范圍內(nèi)使用及復(fù)用。
規(guī)避沖突:
function foo() { function bar(a) { i = 3; //不小心懂了for循環(huán)所屬作用域中的i console.log( a + i ); } for (var i=0; i<10; i++) { bar( i*2 ); //進(jìn)入死循環(huán)。 } } foo();
全局命名空間:當(dāng)程序加載了多個(gè)第三方庫(kù)時(shí),如果他們沒(méi)有妥善的將內(nèi)部私有的函數(shù)或變量隱藏起來(lái),就很容易產(chǎn)生沖突。
模塊管理
為了不污染作用域,可以使用包裝函數(shù)來(lái)解決這個(gè)問(wèn)題。包裝函數(shù)的聲明以(function.. 開(kāi)始。包裝函數(shù)會(huì)自動(dòng)運(yùn)行,是一個(gè)表達(dá)式。
IIFE:立即執(zhí)行函數(shù)表達(dá)式(Immediately Invoked Function Expression)
var a = 2; (function foo(){ var a = 3; console.log(a); //3 })(); //防止了foo這個(gè)名稱污染了作用域 console.log(a); //2
匿名函數(shù)表達(dá)式的利弊
setTimeout( function() { console.log("+1s,WTF!") },100);
行內(nèi)函數(shù)表達(dá)式
setTimeout( function haveName() { console.log("+1s,WTF!") },100);
塊作用域:幾乎形同虛設(shè),只能靠開(kāi)發(fā)者自覺(jué)了。在塊作用域內(nèi)聲明的變量都會(huì)屬于外部作用域。表面上看如此,但如果深入探究。
用with從對(duì)象中創(chuàng)建出的作用域僅在with聲明中而非外部作用域中有效。
try/catch的catch分句會(huì)創(chuàng)建一個(gè)塊作用域,其聲明的變量?jī)H在catch中有效。
let關(guān)鍵字可以將變量綁定到所在的任意作用域中。let聲明附屬于一個(gè)新的作用域而不是當(dāng)前的函數(shù)作用域(也不屬于全局作用域)。
var foo = true; if (foo) { let bar = foo * 2; bar = something(bar); console.log(bar); } console.log(bar); //ReferenceError第四章 提升
先有雞還是先有蛋的問(wèn)題:
Demo1:
a = 2; var a; console.log(a); //2
Demo2:
console.log(a); //undefined var a = 2;
事實(shí)是先有蛋(聲明)后有雞(賦值)。實(shí)際處理如下:
demo1實(shí)際:
var a; a = 2; console.log(a);
demo2實(shí)際:
var a; console.log(a); a = 2;
只有聲明本身會(huì)被提升,而賦值或者其他運(yùn)行邏輯會(huì)留在本地。
foo(); //TypeError var foo = function bar() { // ... };
demo3:
foo(); // TypeError bar(); //ReferenceError var foo = function bar(){ // ... }
上述代碼提升后實(shí)際理解形式:
var foo; foo(); bar(); foo = function() { var bar = ..self.. //... }
提升過(guò)程函數(shù)優(yōu)先,然后才是變量:
foo(); //1 var foo; function foo() { console.log(1); } foo = function() { console.log(2); }
上述代碼會(huì)被理解成以下形式:
function foo() { console.log(1); } foo(); foo = function() { console.log(2); };
盡管var foo出現(xiàn)在function foo()之前,但它是重復(fù)的聲明,因此被忽略。因?yàn)楹瘮?shù)聲明會(huì)被提升到普通變量之前。
聲明本身會(huì)被提升,但包括函數(shù)表達(dá)式的賦值在內(nèi)的賦值操作并不會(huì)提升。
當(dāng)函數(shù)可以記住并訪問(wèn)所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外進(jìn)行。
function foo() { var a = 2; function bar() { console.log(a); } return bar; } var baz = foo(); baz(); //2 這就是閉包的效果
函數(shù)bar()詞法作用域能夠訪問(wèn)foo()的內(nèi)部作用域。然后我們將bar()函數(shù)本身當(dāng)作一個(gè)值類型進(jìn)行傳遞。我們將bar所引用的函數(shù)對(duì)象本身當(dāng)作返回值。
在foo()執(zhí)行后,其返回值賦值給變量baz并調(diào)用baz(),實(shí)際上是通過(guò)不同的標(biāo)識(shí)符引用調(diào)用了內(nèi)部的函數(shù)bar()。
bar()顯然可以被正常執(zhí)行。但在這個(gè)例子中,它在自己定義的詞法作用域以外的地方執(zhí)行。
在foo()執(zhí)行后,通常會(huì)期待foo()的整個(gè)內(nèi)部作用域都被銷毀,因?yàn)橐嬗欣厥掌饔脕?lái)釋放不再使用的內(nèi)存空間。由于foo()的內(nèi)容不會(huì)再被使用,所以會(huì)被回收。
而閉包的神奇作用是阻止此事發(fā)生。事實(shí)上內(nèi)部作用域依舊存在,因?yàn)閎ar()本身在使用。
拜bar()所聲明的位置所賜,它擁有涵蓋foo()內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以供bar()在之后任何時(shí)間進(jìn)行引用。
bar()依然持有對(duì)該作用域的引用,而這個(gè)引用就叫做閉包。
當(dāng)然,無(wú)論使用何種方式對(duì)函數(shù)類型的值進(jìn)行傳遞,當(dāng)函數(shù)在別處被調(diào)用時(shí)都可以觀察到閉包。
var fn; function foo() { var a = 2; function baz() { console.log(a); } fn = baz; //將baz分配給全局變量 } function bar() { fn(); } foo(); bar(); //2
無(wú)論通過(guò)何種手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域外,它都會(huì)持有對(duì)原始定義作用域的引用,無(wú)論在何處執(zhí)行這個(gè)函數(shù)都會(huì)使用閉包。
本質(zhì)上無(wú)論何時(shí)何地。如果將函數(shù)(訪問(wèn)它們各自的詞法作用域)當(dāng)作第一級(jí)的值類型并到處傳遞,你就會(huì)看到閉包在這類函數(shù)中的應(yīng)用。(比如使用了回調(diào)函數(shù))
for (var i=1; i<=5; i++) { setTimeout(function timer() { console.log(i); }, i*1000); }
我們預(yù)期上述代碼依次輸出1,2,3,4,5。實(shí)際會(huì)輸出五次6。因?yàn)檩敵鲲@示的是循環(huán)結(jié)束時(shí)i的值。
因?yàn)檠舆t函數(shù)的回調(diào)會(huì)在循環(huán)結(jié)束后才執(zhí)行。根據(jù)作用域的工作原理,實(shí)際情況是盡管循環(huán)中的五個(gè)函數(shù)是在各個(gè)迭代中分別定義的,但是它們都被封閉在一個(gè)共享的全局作用域中,因此實(shí)際上只有一個(gè)i.
修改如下:
for (var i=1; i<=5; i++) { (function(j) { setTimeout( function timer() { console.log(j); }, j*1000); })(i); }
再迭代中使用IIFE會(huì)為每個(gè)迭代都生成一個(gè)新的作用域,使得延遲函數(shù)的回調(diào)可以將新的作用域封閉在每個(gè)迭代內(nèi)部,每個(gè)迭代中都會(huì)含有一個(gè)具有正確值的變量供我們?cè)L問(wèn)。
將塊作用域和閉包聯(lián)手后:
for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log(i); }, i*1000); }
模塊也是利用閉包的一個(gè)好方法:
function CoolModule() { var something = "cool"; var another = [1,2,3]; function doSomething() { console.log( something ); } function doAnother() { console.log( another.join("!")); } return { doSomething: doSomething, doAnother: doAnother }; } var foo = CoolModule(); foo.doSomething(); //cool foo.doAnother(); //1!2!3
這就是JavaScript中最常用的模塊,doSomething()和doAnother()函數(shù)具有涵蓋模塊實(shí)例內(nèi)部作用域的閉包。
總結(jié)一下,模塊模式需要兩個(gè)必要條件:
1.必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的模塊實(shí)例)。
2.封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問(wèn)或者修改私有的狀態(tài)。
也可以用單例模式來(lái)實(shí)現(xiàn),這種情況適用于只需要一個(gè)實(shí)例的情景:
var foo = (function CoolModule() { var something = "cool"; var another = [1,2,3]; function doSomething() { console.log( something ); } function doAnother() { console.log( another.join("!")); } return { doSomething: doSomething, doAnother: doAnother }; })(); foo.doSomething(); foo.doAnother();
模塊模式也可以接受參數(shù),不再贅述。
最后總結(jié)一下閉包:
當(dāng)函數(shù)可以記住并訪問(wèn)所在的詞法作用域,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行,這是就產(chǎn)生了閉包。
JavaScript并不具有動(dòng)態(tài)作用域,它只有詞法作用域。
function foo() { console.log(a); } function bar() { var a = 3; foo(); } var a = 2; bar();
實(shí)際上上述代碼輸出2,因?yàn)樵~法作用域讓foo()中的a通過(guò)RHS引用到了全局作用域中的a,因此會(huì)輸出2.如果JavaScript有動(dòng)態(tài)作用域,那么會(huì)輸出3,但是JavaScript并沒(méi)有動(dòng)態(tài)作用域。
第一部分完
感謝作者Kyle Simpson和譯者趙望野,感謝自由和開(kāi)源世界
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/88127.html
摘要:比如程序會(huì)被分解為解析語(yǔ)法分析將詞法單元流轉(zhuǎn)換成一個(gè)由元素逐級(jí)嵌套所組成的代表了程序語(yǔ)法接口的書(shū),又稱抽象語(yǔ)法樹(shù)。代碼生成將抽象語(yǔ)法樹(shù)轉(zhuǎn)換為機(jī)器能夠識(shí)別的指令。 showImg(https://segmentfault.com/img/remote/1460000009682106?w=640&h=280); 本文首發(fā)在我的個(gè)人博客:http://muyunyun.cn/ 《你不知道的...
摘要:建筑的頂層代表全局作用域。實(shí)際的塊級(jí)作用域遠(yuǎn)不止如此塊級(jí)作用域函數(shù)作用域早期盛行的立即執(zhí)行函數(shù)就是為了形成塊級(jí)作用域,不污染全局。這便是閉包的特點(diǎn)吧經(jīng)典面試題下面的代碼輸出內(nèi)容答案?jìng)€(gè)如何處理能夠輸出閉包方式方式下一篇你不知道的筆記 下一篇:《你不知道的javascript》筆記_this 寫(xiě)在前面 這一系列的筆記是在《javascript高級(jí)程序設(shè)計(jì)》讀書(shū)筆記系列的升華版本,旨在將零碎...
摘要:而閉包的神奇之處正是可以阻止事情的發(fā)生。拜所聲明的位置所賜,它擁有涵蓋內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以供在之后任何時(shí)間進(jìn)行引用。依然持有對(duì)該作用域的引用,而這個(gè)引用就叫閉包。 引子 先看一個(gè)問(wèn)題,下面兩個(gè)代碼片段會(huì)輸出什么? // Snippet 1 a = 2; var a; console.log(a); // Snippet 2 console.log(a); v...
摘要:最近剛剛看完了你不知道的上卷,對(duì)有了更進(jìn)一步的了解。你不知道的上卷由兩部分組成,第一部分是作用域和閉包,第二部分是和對(duì)象原型。附錄詞法這一章并沒(méi)有說(shuō)明機(jī)制,只是介紹了中的箭頭函數(shù)引入的行為詞法。第章混合對(duì)象類類理論類的機(jī)制類的繼承混入。 最近剛剛看完了《你不知道的 JavaScript》上卷,對(duì) JavaScript 有了更進(jìn)一步的了解。 《你不知道的 JavaScript》上卷由兩部...
摘要:一作用域域表示的就是范圍,即作用域,就是一個(gè)名字在什么地方可以使用,什么時(shí)候不能使用。概括的說(shuō)作用域就是一套設(shè)計(jì)良好的規(guī)則來(lái)存儲(chǔ)變量,并且之后可以方便地找到這些變量。 一、作用域 域表示的就是范圍,即作用域,就是一個(gè)名字在什么地方可以使用,什么時(shí)候不能使用。想了解更多關(guān)于作用域的問(wèn)題推薦閱讀《你不知道的JavaScript上卷》第一章(或第一部分),從編譯原理的角度說(shuō)明什么是作用域。概...
閱讀 1428·2021-10-08 10:05
閱讀 3079·2021-09-26 10:10
閱讀 890·2019-08-30 15:55
閱讀 515·2019-08-26 11:51
閱讀 451·2019-08-23 18:10
閱讀 3870·2019-08-23 15:39
閱讀 672·2019-08-23 14:50
閱讀 777·2019-08-23 14:46