摘要:依然持有對(duì)該作用域的引用,而這個(gè)引用就叫作閉包。無論通過何種手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域以外,它都會(huì)持有對(duì)原始定義作用域的引用,無論在何處執(zhí)行這個(gè)函數(shù)都會(huì)使用閉包。
因?yàn)樽罱?xiàng)目比較少,閑來覺得需要學(xué)習(xí)《你不知道的JavaScript》;跟大家分享一下;
什么是作用域需要一套設(shè)計(jì)良好的規(guī)則來存儲(chǔ)變量,并且之后可以方便地找到這些變量。這套規(guī)
則被稱為作用域
1.var a: 編譯器會(huì)詢問作用域是否存在變量a;如果是,編譯器會(huì)忽略該聲明,繼續(xù)進(jìn)行編譯。否則它會(huì)要求作用域在當(dāng)前作用域的集合中聲明一個(gè)新的變量,并命名為a;接下來編譯器會(huì)為引擎生成運(yùn)行時(shí)所需的代碼,這些代碼被用來處理a = 2這個(gè)賦值操作。
2.引擎運(yùn)行時(shí)會(huì)首先詢問作用域,在當(dāng)前的作用域集合中是否存在一個(gè)叫作a的變量。如果否,引擎就會(huì)
使用這個(gè)變量;如果不是,引擎會(huì)繼續(xù)查找該變量如果引擎最終找到了a變量,就會(huì)將2賦值給它。否則引擎就會(huì)舉手示意并拋出一個(gè)異常!
RHS查詢:簡單地查找某個(gè)變量的值
LHS查詢:試圖找到變量的容器本身,從而可以對(duì)其賦值
作用域嵌套在概念上最好將其理解為“賦值操作的目標(biāo)是誰(LHS)”以及“誰是賦值操作的源頭(RHS)”。
LHS:對(duì)哪個(gè) 賦值 就對(duì)哪個(gè)進(jìn)行LHS引用,可以理解為賦值操作的目標(biāo)。
RHS:需要 獲取 哪個(gè)變量的值,就對(duì)哪個(gè)變量的值進(jìn)行RHS引用,理解為賦值操作的源頭。
當(dāng)一個(gè)塊或函數(shù)嵌套在另一個(gè)塊或函數(shù)中時(shí),就發(fā)生了作用域的嵌套。因此,在當(dāng)前作用域中無法
找到某個(gè)變量時(shí),引擎就會(huì)在外層嵌套的作用域中繼續(xù)查找,直到找到該變量,或抵達(dá)最外層的作用域(也就是全局作用域)為止。
遍歷嵌套作用域鏈的規(guī)則很簡單:引擎從當(dāng)前的執(zhí)行作用域開始查找變量,如果找不到,就向上一
級(jí)繼續(xù)查找。當(dāng)?shù)诌_(dá)最外層的全局作用域時(shí),無論找到還是沒找到,查找過程都會(huì)停止。
如果RHS查詢在所有嵌套的作用域中遍尋不到所需的變量,引擎就會(huì)拋出ReferenceError異常
在嚴(yán)格模式中LHS查詢失敗時(shí),并不會(huì)創(chuàng)建并返回一個(gè)全局變量,引擎會(huì)拋出同RHS查詢失敗時(shí)類似的ReferenceError異常。
如果RHS查詢找到了一個(gè)變量,但是你嘗試對(duì)這個(gè)變量的值進(jìn)行不合理的賦值,那么引擎會(huì)
拋出另外一種類型的異常,叫作TypeError。
ReferenceError同作用域判別失敗相關(guān),而TypeError則代表作用域判別成功了,但是對(duì)結(jié)果的操作
是非法或不合理的。
遮蔽效應(yīng)在多層的嵌套作用域中可以定義同名的標(biāo)識(shí)符,這叫作“遮蔽效應(yīng)”(內(nèi)部的標(biāo)識(shí)符“遮蔽”了外部的標(biāo)識(shí)符)。作用域查找始終從運(yùn)行時(shí)所處的最內(nèi)部作用域開始,逐級(jí)向外或者說向上進(jìn)行,直到遇見第一個(gè)匹配的標(biāo)識(shí)符為止。
全局變量會(huì)自動(dòng)成為全局對(duì)象(比如瀏覽器中的window對(duì)象)的屬性,所以如果要逃避遮蔽效應(yīng)
可以通過 window對(duì)象
window.a //得到的是全局定義的a變量;全局命名空間
庫通常會(huì)在全局作用域中聲明一個(gè)名字足夠獨(dú)特的變量,通常是一個(gè)對(duì)象。這個(gè)對(duì)象被用作
庫的命名空間,所有需要暴露給外界的功能都會(huì)成為這個(gè)對(duì)象(命名空間)的屬性,而不是將自己
的標(biāo)識(shí)符暴漏在頂級(jí)的詞法作用域中。
我們已經(jīng)知道,在任意代碼片段外部添加包裝函數(shù),可以將內(nèi)部的變量和函數(shù)定義“隱藏”起來,外
部作用域無法訪問包裝函數(shù)內(nèi)部的任何內(nèi)容。
雖然這種技術(shù)可以解決一些問題,但是它并不理想,因?yàn)闀?huì)導(dǎo)致一些額外的問題。首先,必須聲明
一個(gè)具名函數(shù)foo(),意味著foo這個(gè)名稱本身“污染”了所在作用域(在這個(gè)例子中是全局作用域)。
其次,必須顯式地通過函數(shù)名(foo())調(diào)用這個(gè)函數(shù)才能運(yùn)行其中的代碼。
更加理想的方式
var a = 2; (function foo(){ // <-- 添加這一行 var a = 3; console.log( a ); // 3 })(); // <-- 以及這一行 console.log( a ); // 2
以(function...而不僅是以function...開始。函數(shù)會(huì)被當(dāng)作函數(shù)表達(dá)式而不是一個(gè)標(biāo)準(zhǔn)的函數(shù)聲明 來處理。
函數(shù)聲明和函數(shù)表達(dá)式之間最重要的區(qū)別是它們的名稱標(biāo)識(shí)符將會(huì)綁定在何處。foo被綁定在函數(shù)表達(dá)式自身的函數(shù)中而不是所在作用域中。
換句話說,(function foo(){ .. })作為函數(shù)表達(dá)式意味著foo只能在..所代表的位置中被訪問,外
部作用域則不行。foo變量名被隱藏在自身中意味著不會(huì)非必要地污染外部作用域。
很多人都更喜歡另一個(gè)改進(jìn)的形式:(function(){ .. }())。這兩種形式在功能上是一致的。選擇哪個(gè)全憑個(gè)人喜好.
塊 作用域for (var i=0; i<10; i++) { console.log( i ); }
我們在for循環(huán)的頭部直接定義了變量i,通常是因?yàn)橹幌朐趂or循環(huán)內(nèi)部的上下文中使用i,而忽
略了i會(huì)被綁定在外部作用域(函數(shù)或全局)中的事實(shí)。
JavaScript的ES3規(guī)范中規(guī)定try/catch的catch分句會(huì)創(chuàng)建一個(gè)塊作用域,其中聲明的變量僅在catch內(nèi)部有效。
try { undefined(); // 執(zhí)行一個(gè)非法操作來強(qiáng)制制造一個(gè)異常 }c atch (err) { console.log( err ); // 能夠正常執(zhí)行! } c onsole.log( err ); // ReferenceError: err not found
ES6改變了現(xiàn)狀,引入了新的let關(guān)鍵字,提供了除var以外的另一種變量聲明方式
let關(guān)鍵字可以將變量綁定到所在的任意作用域中(通常是{ .. }內(nèi)部)。只要聲明是有效的,在聲明中的任意位置都可以使用{ .. }括號(hào)來為let創(chuàng)建一個(gè)用于綁定的塊。
為變量顯式聲明塊作用域,并對(duì)變量進(jìn)行本地綁定是非常有用的工具,可以讓引擎清楚地知道沒有必要繼續(xù)保存那些變量(當(dāng)塊的變量沒有被引用時(shí)就銷毀);
const除了let以外,ES6還引入了const,同樣可以用來創(chuàng)建塊作用域變量,但其值是固定的(常量)。之后
任何試圖修改值的操作都會(huì)引起錯(cuò)誤。
解析兩個(gè)輸出
a = 2; var a; console.log( a );//2
console.log( a ); //undefined var a = 2;
編譯器順序
當(dāng)你看到var a = 2;時(shí),可能會(huì)認(rèn)為這是一個(gè)聲明。但JavaScript實(shí)際上會(huì)將其看成兩個(gè)聲 明:var a;和a =
2;。第一個(gè)定義聲明是在編譯階段進(jìn)行的。第二個(gè)賦值聲明會(huì)被留在原地等待執(zhí) 行階段。
我們的第二個(gè)代碼片段實(shí)際是按照以下流程處理的:
var a; console.log( a ); a = 2;
這個(gè)過程就好像變量和函數(shù)聲明從它們在代碼中出現(xiàn)的位置被“移動(dòng)”到了最上面。這個(gè)過程就叫作 提升。
換句話說,先有蛋(聲明)后有雞(賦值)。
函數(shù)聲明會(huì)優(yōu)先于變量聲明
foo(); // 1 var foo; function foo() { console.log( 1 ); } f oo = function() { console.log( 2 ); };
相當(dāng)于
function foo() { console.log( 1 ); } foo(); // 1 foo = function() { console.log( 2 ); };閉包
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之
外執(zhí)行。
詞法作用域就是定義在詞法階段的作用域。換句話說,詞法作用域是由你在寫代碼時(shí)將 變量和塊作用域?qū)懺谀睦飦頉Q定的
function foo() { var a = 2; function bar() { console.log( a );} r eturn bar; } var baz = foo(); baz(); // 2 ———— 朋友, 這就是閉包的效果。
我們將bar()函數(shù)本身當(dāng)作一個(gè)值類型進(jìn)行傳遞。
函數(shù)bar()的詞法作用域能夠訪問foo()的內(nèi)部作用域。
在foo()執(zhí)行后,其返回值(也就是內(nèi)部的bar()函數(shù))賦值給變量baz并調(diào)用baz(),實(shí)際上只是通過不同的標(biāo)識(shí)符引用調(diào)用了內(nèi)部的函數(shù)bar()。
因?yàn)槲覀冎酪嬗欣厥掌饔脕磲尫挪辉偈褂玫膬?nèi)存空間。由于看上去foo()的內(nèi)容不會(huì)再被使用,所以很自然地會(huì)考慮對(duì)其進(jìn)行回收。在foo()執(zhí)行后,通常會(huì)期待foo()的整個(gè)內(nèi)部作用域都被銷毀。
而閉包的“神奇”之處正是可以阻止這件事情的發(fā)生。事實(shí)上內(nèi)部作用域依然存在,因此沒有被回收。
誰在使用這個(gè)內(nèi)部作用域?原來是bar()本身在使用。拜bar()所聲明的位置所賜,它擁有涵蓋foo()內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以供bar()在之后任何時(shí)間進(jìn)行引用。
一個(gè)神奇的例子bar()依然持有對(duì)該作用域的引用,而這個(gè)引用就叫作閉包。
無論通過何種手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域以外,它都會(huì)持有對(duì)原始定義作用域的引
用,無論在何處執(zhí)行這個(gè)函數(shù)都會(huì)使用閉包。本質(zhì)上無論何時(shí)何地,如果將函數(shù)(訪問它們各自的詞法作用域)當(dāng)作第一級(jí)的值類型并到處傳遞,你就會(huì)看到閉包在這些函數(shù)中的應(yīng)用。只要使用了回調(diào)函數(shù),實(shí)際上就是在使用閉包!
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); } //每秒出現(xiàn)一個(gè)6
解析:延遲函數(shù)的回調(diào)會(huì)在循環(huán)結(jié)束時(shí)才執(zhí)行。事實(shí)上,當(dāng)定時(shí)器運(yùn)行時(shí)即使每個(gè)迭代中執(zhí)行的是setTimeout(..,
0),所有的回調(diào)函數(shù)依然是在循環(huán)結(jié)束后才會(huì)被執(zhí)行,因此會(huì)每次輸出一個(gè)6出來。我們試圖假設(shè)循環(huán)中的每個(gè)迭代在運(yùn)行時(shí)都會(huì)給自己“捕獲”一個(gè)i的副本。但是根據(jù)作用域的工作原理,實(shí)際情況是盡管循環(huán)中的五個(gè)函數(shù)是在各個(gè)迭代中分別定義的,但是它們都被封閉在一個(gè)共享的全局作用域中,因此實(shí)際上只有一個(gè)i。
改進(jìn)寫法
for (var i=1; i<=5; i++) { (function(j) { setTimeout( function timer() { console.log( j ); }, j*1000 ); })( i ); } //每秒從1到6依次輸出
解析:在迭代內(nèi)使用IIFE(自執(zhí)行函數(shù))會(huì)為每個(gè)迭代都生成一個(gè)新的作用域,使得延遲函數(shù)的回調(diào)可以將新的作用域封閉在每個(gè)迭代內(nèi)部,每個(gè)迭代中都會(huì)含有一個(gè)具有正確值的變量供我們訪問。
細(xì)說:
for (var i = 0; i <= 5; i++) { console.log(i) }
我們都知道上面的代碼可以輸出1到6;因此可以明白
for (var i=1; i<=5; i++) { (function(j) { })( i ); } //這一層,是會(huì)不斷的獲取到正確的i值;
然后,由于延遲函數(shù)的回調(diào) 使用了閉包;每次閉包都會(huì)保存IIFE 的有正確值的作用域;
閉包的應(yīng)用-- 模塊直接抄代碼看:
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
除了返回一個(gè)對(duì)象,還可以返回一個(gè)函數(shù)很好理解,coolmodule()返回一個(gè)對(duì)象,這個(gè)對(duì)象通過不同的標(biāo)識(shí)符引用調(diào)用了內(nèi)部的函數(shù);這些函數(shù)就是coolmodule的閉包,具有訪問coolmodule作用域的能力;
閉包的應(yīng)用-- 實(shí)現(xiàn)模塊從模塊中返回一個(gè)實(shí)際的對(duì)象并不是必須的,也可以直接返回一個(gè)內(nèi)部函數(shù)。jQuery就是
一個(gè)很好的例子。jQuery和$標(biāo)識(shí)符就是jQuery模塊的公共API,但它們本身都是函數(shù)(由于函數(shù)也是對(duì)象,它們本身也可以擁有屬性)。因此,jq的方法有兩種,通過$.xxx() 運(yùn)行的是jq的屬性方法;通過$() 運(yùn)行的是jq的函數(shù)方法;
模塊模式需要具備兩個(gè)必要條件。
必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的模塊實(shí)例)。
封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態(tài)。
單例模式
var foo = (function CoolModule() { var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log( something ); } f unction doAnother() { console.log( another.join( " ! " ) ); }return { doSomething: doSomething, doAnother: doAnother }; })(); foo.doSomething(); // cool foo.doAnother(); // 1 ! 2 ! 3
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/89648.html
摘要:的變量作用域是基于其特有的作用域鏈的。需要注意的是,用創(chuàng)建的函數(shù),其作用域指向全局作用域。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。 作用域 定義 在編程語言中,作用域控制著變量與參數(shù)的可見性及生命周期,它能減少名稱沖突,而且提供了自動(dòng)內(nèi)存管理 --javascript 語言精粹 我理解的是,一個(gè)變量、函數(shù)或者成員可以在代碼中訪問到的范圍。 js的變量作...
摘要:作用域分為詞法作用域和動(dòng)態(tài)作用域。這樣就形成了一個(gè)鏈?zhǔn)降淖饔糜?。一般情況下,當(dāng)函數(shù)執(zhí)行完畢時(shí),里面的變量會(huì)被自動(dòng)銷毀。而能夠訪問到這個(gè)在的編譯階段就已經(jīng)定型了詞法作用域。 什么是作用域?在當(dāng)前運(yùn)行環(huán)境下,可以訪問的變量或函數(shù)的范圍。作用域分為詞法作用域和動(dòng)態(tài)作用域。詞法作用域是在js代碼編譯階段就確定下來的; 對(duì)應(yīng)的,with和eval語句會(huì)產(chǎn)生動(dòng)態(tài)作用域。 會(huì)產(chǎn)生新的作用域的情況: ...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內(nèi)部,也就是說為其聲明的變量隱式的劫持了所在的塊級(jí)作用域。 作用域與閉包 如何用js創(chuàng)建10個(gè)button標(biāo)簽,點(diǎn)擊每個(gè)按鈕時(shí)打印按鈕對(duì)應(yīng)的序號(hào)? 看到上述問題,如果你能看出來這個(gè)問題實(shí)質(zhì)上是考對(duì)作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對(duì)作用域已經(jīng)理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內(nèi)部,也就是說為其聲明的變量隱式的劫持了所在的塊級(jí)作用域。 作用域與閉包 如何用js創(chuàng)建10個(gè)button標(biāo)簽,點(diǎn)擊每個(gè)按鈕時(shí)打印按鈕對(duì)應(yīng)的序號(hào)? 看到上述問題,如果你能看出來這個(gè)問題實(shí)質(zhì)上是考對(duì)作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對(duì)作用域已經(jīng)理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
摘要:是詞法作用域工作模式。使用可以將變量綁定在所在的任意作用域中通常是內(nèi)部,也就是說為其聲明的變量隱式的劫持了所在的塊級(jí)作用域。 作用域與閉包 如何用js創(chuàng)建10個(gè)button標(biāo)簽,點(diǎn)擊每個(gè)按鈕時(shí)打印按鈕對(duì)應(yīng)的序號(hào)? 看到上述問題,如果你能看出來這個(gè)問題實(shí)質(zhì)上是考對(duì)作用域的理解,那么恭喜你,這篇文章你可以不用看了,說明你對(duì)作用域已經(jīng)理解的很透徹了,但是如果你看不出來這是一道考作用域的題目,...
閱讀 2676·2023-04-25 18:10
閱讀 1617·2019-08-30 15:53
閱讀 2811·2019-08-30 13:10
閱讀 3228·2019-08-29 18:40
閱讀 1134·2019-08-23 18:31
閱讀 1209·2019-08-23 16:49
閱讀 3408·2019-08-23 16:07
閱讀 883·2019-08-23 15:27