摘要:而閉包的神奇之處正是可以阻止這件事情的發(fā)生。事實(shí)上內(nèi)部作用域依然存在,因此沒有被回收頻繁使用閉包可能導(dǎo)致內(nèi)存泄漏。拜所聲明的位置所賜,它擁有涵蓋內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以供在之后任何時(shí)間進(jìn)行引用。
作用域
作用域(scope),程序設(shè)計(jì)概念,通常來說,一段程序代碼中所用到的變量并不總是有效/可用的,而限定這個(gè)變量的可用性的代碼范圍就是這個(gè)變量的作用域。通俗一點(diǎn)就是我要把我的變量分成一坨一坨保管起來,有些地方只能用這幾個(gè)變量,有些地方只能用另外幾個(gè)變量,而這個(gè)分開的一坨一坨的區(qū)域就是作用域~
那這個(gè)作用域什么時(shí)候用到的呢?
沒錯(cuò)就是編譯的時(shí)候~
讓我們來看看編譯的大概流程
詞法分析(這個(gè)過程會(huì)將由字符組成的字符串分解成(對(duì)編程語言來說)有意義的代碼塊)
語法分析(這個(gè)過程是將詞法單元流(數(shù)組)轉(zhuǎn)換成一個(gè)由元素逐級(jí)嵌套所組成的代表了程序語法結(jié)構(gòu)的樹。這個(gè)樹被稱為“抽象語法樹”)
代碼生成(將這棵“樹” 轉(zhuǎn)換為可執(zhí)行代碼,將我們寫的代碼變成機(jī)器指令并執(zhí)行)
比起上面這些編譯過程只有三個(gè)步驟的語言的編譯器,JavaScript 引擎要復(fù)雜得多。例如,在語法分析和代碼生成階段有特定的步驟來對(duì)運(yùn)行性能進(jìn)行優(yōu)化,包括對(duì)冗余元素進(jìn)行優(yōu)化等,但是大體上也是差不多的流程~
那我們要編譯var a = 2的話,是‘誰’來執(zhí)行編譯的過程呢?
當(dāng)當(dāng)當(dāng)當(dāng)~
引擎:負(fù)責(zé)整個(gè)編譯運(yùn)行的全部過程。
編譯器:負(fù)責(zé)詞法分析以及代碼生成。
作用域:負(fù)責(zé)收集維護(hù)所有聲明的標(biāo)識(shí)符,確定當(dāng)前執(zhí)行代碼對(duì)標(biāo)識(shí)符的訪問權(quán)限。
當(dāng)我們看到var a = 2的時(shí)候,我們認(rèn)為是一條聲明,但是對(duì)于引擎來說,這是兩個(gè)完全不一樣的聲明,分為下面兩部分
1.遇到 var a,編譯器會(huì)詢問作用域是否已經(jīng)有一個(gè)該名稱的變量存在于同一個(gè)作用域的集合中。如果是,編譯器會(huì)忽略該聲明,繼續(xù)進(jìn)行編譯;否則它會(huì)要求作用域在當(dāng)前作用域的集合中聲明一個(gè)新的變量,并命名為a(嚴(yán)格模式下報(bào)錯(cuò))。
2.接下來編譯器會(huì)為引擎生成運(yùn)行時(shí)所需的代碼,這些代碼被用來處理 a = 2這個(gè)賦值操作。引擎運(yùn)行時(shí)會(huì)首先詢問作用域,在當(dāng)前的作用域集合中是否存在一個(gè)叫作 a的變量。如果是,引擎就會(huì)使用這個(gè)變量;如果否,引擎會(huì)繼續(xù)查找該變量。
可以看到,編譯的時(shí)候,編譯器和引擎需要詢問作用域,所求變量是否存在,然后根據(jù)查詢結(jié)果來進(jìn)行不同的操作
作用域嵌套上面我們展示了只有一個(gè)作用域,變量的聲明和賦值過程。
實(shí)際情況中,我們通常需要同時(shí)顧及幾個(gè)作用域。當(dāng)一個(gè)塊或函數(shù)嵌套在另一個(gè)塊或函數(shù)中時(shí),就發(fā)生了作用域的嵌套。因此,在當(dāng)前作用域中無法找到某個(gè)變量時(shí),引擎就會(huì)在外層嵌套的作用域中繼續(xù)查找,直到找到該變量,或抵達(dá)最外層的作用域(也就是全局作用域)為止;但是反過來,外層的作用域無法訪問內(nèi)層作用域的變量,如果可以的話那不就全都是全局變量了嗎嘿嘿嘿
function foo(a) { console.log( a + b ); } var b = 2; foo( 2 ); // 4
當(dāng)引擎需要變量b的時(shí)候,首先在foo的作用域中查找,發(fā)現(xiàn)沒有b的蹤影,于是就跑出來,往上面一層作用域走一走,發(fā)現(xiàn)了這個(gè)b原來在全局作用域里待著,那可不得一頓引用!如果全局作用域也沒有b的話,那就得報(bào)錯(cuò)了,告訴寫代碼的傻子“你豬呢?一天到晚凈會(huì)寫bug!”。
第一層樓代表當(dāng)前的執(zhí)行作用域,也就是你所處的位置。建筑的頂層代表全局作用域。
變量引用都會(huì)在當(dāng)前樓層進(jìn)行查找,如果沒有找到,就會(huì)坐電梯前往上一層樓,如果還是沒有找到就繼續(xù)向上,以此類推。一旦抵達(dá)頂層(全局作用域),可能找到了你所需的變量,也可能沒找到,但無論如何查找過程都將停止
可以看到我們在上面生成兩層作用域(一層foo一層全局)的時(shí)候用了函數(shù)。因?yàn)镴avaScript的函數(shù)可以產(chǎn)生一層函數(shù)作用域。
上代碼!
function foo(a) { var b = a * 2; function bar(c) { console.log( a, b, c ); } bar( b * 3 ); } foo( 2 ); // 2, 4, 12
我們來分析一下上面幾行代碼。這個(gè)例子里面包含了三層逐級(jí)嵌套的作用域,其中兩個(gè)函數(shù)生成了兩層嵌套作用域。
1.包含著整個(gè)全局作用域,其中只有一個(gè)標(biāo)識(shí)符: foo 。 2.包含著 foo 所創(chuàng)建的作用域,其中有三個(gè)標(biāo)識(shí)符: a 、 bar 和 b 。 3.包含著 bar 所創(chuàng)建的作用域,其中只有一個(gè)標(biāo)識(shí)符: c 。
由于bar是最內(nèi)層的作用域,如果在它作用域內(nèi)的查詢不到它需要的值,它會(huì)逐級(jí)往外查詢外層作用域的同名變量。如果查詢到了則取用~
塊級(jí)作用域盡管函數(shù)作用域是最常見的作用域單元,當(dāng)然也是現(xiàn)行大多數(shù) JavaScript 中最普遍的設(shè)計(jì)方法,但其他類型的作用域單元也是存在的,并且通過使用其他類型的作用域單元甚至可以實(shí)現(xiàn)維護(hù)起來更加優(yōu)秀、簡潔的代碼。(如果你會(huì)其他一些語言你就會(huì)發(fā)現(xiàn)一個(gè)花括號(hào)不就一個(gè)塊級(jí)作用域了嗎)
我們來看看JavaScript中的花括號(hào)~
for(var i=0;i<5;i++){console.log(window.i)} //0 1 2 3 4
你驚奇的發(fā)現(xiàn),媽耶,我這個(gè)var不等于白var嘛,反正都是全局變量(如果你沒在函數(shù)內(nèi)使用的話)。
是的JavaScript就是這么的高端兼靈性~(滑稽)
if的花括號(hào)和for是一樣的,不做贅述。
那我們怎樣整一個(gè)獨(dú)立作用域?然后我又不想一直聲明函數(shù)
JavaScript有四種方式可以產(chǎn)生塊級(jí)作用域。
with
try/catch
let
const
讓我們來介紹一下這四種東西吧~
1.首先是with,算了,垃圾,不講。好處不多,壞處倒是挺多,有興趣百度用法~不建議使用 2.然后是try/catch, ES3 規(guī)范中規(guī)定 try / catch 的 catch 分句會(huì)創(chuàng)建一個(gè)塊作用域,其中聲明的變量僅在 catch 內(nèi)部有效
try{throw 2}catch(a){console.log(a)}; console.log(a);//Uncaught ReferenceError
3.let,這個(gè)是es6引入的新關(guān)鍵字,非常香~看下面可以和上面的var i的循環(huán)做對(duì)比
for(let j=0;j<5;j++)(console.log(window.j));//undefined *5
4.這個(gè)跟let差不多,但是是用來定義常量的。const a = 5;a = 6;//報(bào)錯(cuò)
ok~這個(gè)很敢單~讓我們來學(xué)習(xí)下一部分
提升在最開始之前,我們先來學(xué)習(xí)一下兩種報(bào)錯(cuò)。
ReferenceError 異常
TypeError
第一種的出現(xiàn)是因?yàn)楸闅v了所有的作用域都查找不到變量,第二種是找到了這個(gè)變量,但是對(duì)這個(gè)變量的值進(jìn)行了錯(cuò)誤的操作,比如試圖對(duì)一個(gè)非函數(shù)類型的值進(jìn)行函數(shù)調(diào)用
我們先來看看下面的代碼會(huì)輸出什么
a = 2; var a; console.log( a );
你可能會(huì)以為,我先給a賦值了2,然后var a又給a賦值了undefined,所以會(huì)輸出undefined。但是這個(gè)輸出了2。
我們再來看一題
console.log( a ); var a = 2;
這個(gè)時(shí)候你可能認(rèn)為會(huì)報(bào)ReferenceError異常,因?yàn)槭褂迷谇?,使用的時(shí)候a還沒有定義,作用域肯定也找不到a,但是這個(gè)卻輸出了undefined。
Why?
為了搞明白這個(gè)問題,我們需要回顧一下前面關(guān)于編譯器的內(nèi)容。回憶一下,引擎會(huì)在解釋 JavaScript 代碼之前首先對(duì)其進(jìn)行編譯。編譯階段中的一部分工作就是找到所有的聲明,并用合適的作用域?qū)⑺鼈冴P(guān)聯(lián)起來。
因此,正確的思考思路是,包括變量和函數(shù)在內(nèi)的所有聲明都會(huì)在任何代碼被執(zhí)行前首先被處理。當(dāng)你看到 var a = 2; 時(shí),可能會(huì)認(rèn)為這是一個(gè)聲明。但 JavaScript 實(shí)際上會(huì)將其看成兩個(gè)聲明: var a; 和 a = 2; 。第一個(gè)定義聲明是在編譯階段進(jìn)行的。第二個(gè)賦值聲明會(huì)被留在原地等待執(zhí)行階段。
上面的第一段代碼就可以看做
var a; a = 2; console.log(a)
第二段代碼則可以看成
var a; console.log(a);//此時(shí)a還沒賦值,所以是undefined a = 2;
打個(gè)比方,這個(gè)過程就好像變量從它們在代碼中出現(xiàn)的位置被“移動(dòng)”到了最上面(變量所在作用域)。這個(gè)過程就叫作提升。
我們從上面可以看到變量聲明的提升,那么對(duì)于函數(shù)聲明呢?當(dāng)然是no趴笨啦~
foo(); function foo() { console.log( a ); // undefined var a = 2; }
但是,需要注意的是,函數(shù)聲明會(huì)被提升,但是函數(shù)表達(dá)式卻不會(huì)。
foo(); // 不是 ReferenceError, 而是 TypeError! var foo = function bar() { // ... };
這個(gè)就相當(dāng)于
var foo; foo(); // 此時(shí)foo肯定是undefined啦,undefined()? 對(duì)undefined值進(jìn)行函數(shù)調(diào)用顯然是錯(cuò)誤操作!TypeError! foo = function bar() { // ... };
既然函數(shù)聲明和變量聲明都會(huì)被提升,那它們兩個(gè)哪個(gè)提升到更前面呢?
是函數(shù)?。『瘮?shù)作為JavaScript的一名大將,確實(shí)是有一些牌面。
foo(); // 1 var foo; function foo() { console.log( 1 ); } foo = function() { console.log( 2 ); };
我們可以將上面看成
function foo() { console.log( 1 ); } var foo;//重復(fù)聲明,可以去掉 foo(); // 1 foo = function() { console.log( 2 ); };
注意:后面的聲明會(huì)覆蓋前面的聲明。
foo(); // 3 function foo() { console.log( 1 ); } var foo = function() { console.log( 2 ); }; foo();//2 function foo() { console.log( 3 ); }
相當(dāng)于
function foo() { console.log( 1 ); } function foo() { console.log( 3 ); } var foo; foo(); // 3 foo = function() { console.log( 2 ); }; foo();//2閉包
我們剛剛講那么多,相信大家都已經(jīng)知道并且深信,作用域只能一層一層往外查詢,不能往里走,那我如果要找一個(gè)函數(shù)里的變量值呢?那可咋整啊?
很簡單,我們不能往里走,但是我們可以再給這個(gè)函數(shù)里面整一層作用域,這樣函數(shù)里面的子作用域不就可以訪問它的變量了嗎?
perfect~
function foo() { var a = 2; function bar() { console.log( a ); // 2 } return bar; } var baz = foo();執(zhí)行了foo()就返回了一個(gè)bar;現(xiàn)在相當(dāng)于baz=bar; baz();//2
這里我們需要獲取a的值,我們就在里面寫一個(gè)函數(shù)bar,顯然這個(gè)bar是有權(quán)利訪問a的,那我們返回這個(gè)有權(quán)利訪問a的函數(shù)不就頂呱呱了嗎?
在 foo() 執(zhí)行后,通常會(huì)期待 foo() 的整個(gè)內(nèi)部作用域都被銷毀,因?yàn)槲覀冎酪嬗欣厥掌饔脕磲尫挪辉偈褂玫膬?nèi)存空間。由于看上去 foo() 的內(nèi)容不會(huì)再被使用,所以很自然地會(huì)考慮對(duì)其進(jìn)行回收。
而閉包的“神奇”之處正是可以阻止這件事情的發(fā)生。事實(shí)上內(nèi)部作用域依然存在,因此沒有被回收(頻繁使用閉包可能導(dǎo)致內(nèi)存泄漏)。誰在使用這個(gè)內(nèi)部作用域?原來是 bar() 本身在使用。拜 bar() 所聲明的位置所賜,它擁有涵蓋 foo() 內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以供 bar() 在之后任何時(shí)間進(jìn)行引用。
第一題 var tt = "aa"; function test(){ alert(tt); var tt = "dd"; alert(tt); } test();
第二題 var a = 100; function test(){ console.log(a); a = 10; console.log(a); } test(); console.log(a);
第三題 var a=10; function aaa(){ alert(a); }; function bbb(){ var a=20; aaa(); } bbb();
答案:
undefined dd
100 10 10
10
參考文獻(xiàn)《你不知道的JavaScript》
最后有什么錯(cuò)誤或者建議可以在評(píng)論區(qū)告訴我~謝謝
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/97981.html
摘要:變量作用域詳解作用域規(guī)則大部分情況下沒有塊級(jí)作用域除非你使用當(dāng)你使用的情況下僅僅支持函數(shù)作用域不使用聲明的變量為全局變量不用情況下中局部變量只能通過和函數(shù)參數(shù)聲明大部分情況下沒有塊級(jí)作用域除非你使用與很多語言不同在之前并沒有塊級(jí)作用域一個(gè)作 Javascript變量作用域詳解 JS作用域規(guī)則 JS大部分情況下沒有塊級(jí)作用域, 除非你使用let 當(dāng)你使用var的情況下, JS僅僅支持函...
摘要:再看一段代碼這樣就清晰地展示了閉包的詞法作用域能訪問的作用域?qū)?dāng)做一個(gè)值返回執(zhí)行后,將的引用賦值給執(zhí)行,輸出了變量我們知道通過引用的關(guān)系,就是函數(shù)本身。 在正式學(xué)習(xí)閉包之前,請(qǐng)各位同學(xué)一定要確保自己對(duì)詞法作用域已經(jīng)非常的熟悉了,如果對(duì)詞法作用域還不夠熟悉的話,可以先看: 深入理解閉包之前置知識(shí)---作用域與詞法作用域 前言 現(xiàn)在去面試前端開發(fā)的崗位,如果你的面試官也是個(gè)前端,并且不是太...
摘要:不同的是函數(shù)體并不會(huì)再被提升至函數(shù)作用域頭部,而僅會(huì)被提升到塊級(jí)作用域頭部避免全局變量在計(jì)算機(jī)編程中,全局變量指的是在所有作用域中都能訪問的變量。 ES6 變量作用域與提升:變量的生命周期詳解從屬于筆者的現(xiàn)代 JavaScript 開發(fā):語法基礎(chǔ)與實(shí)踐技巧系列文章。本文詳細(xì)討論了 JavaScript 中作用域、執(zhí)行上下文、不同作用域下變量提升與函數(shù)提升的表現(xiàn)、頂層對(duì)象以及如何避免創(chuàng)建...
摘要:不是引用類型,無法輸出簡而言之,堆內(nèi)存存放引用值,棧內(nèi)存存放固定類型值。變量的查詢在變量的查詢中,訪問局部變量要比全局變量來得快,因此不需要向上搜索作用域鏈。 贊助我以寫出更好的文章,give me a cup of coffee? 2017最新最全前端面試題 基本類型值有:undefined,NUll,Boolean,Number和String,這些類型分別在內(nèi)存中占有固定的大小空...
摘要:文章部分實(shí)例和內(nèi)容來自鳥哥的作用域原理首先應(yīng)該注意幾個(gè)點(diǎn)函數(shù)也是對(duì)象因?yàn)槭窃谌肿饔糜虍?dāng)中當(dāng)執(zhí)行到時(shí)即會(huì)產(chǎn)生在當(dāng)中,函數(shù)的運(yùn)行是在它被定義的作用域當(dāng)中,而非執(zhí)行的作用域當(dāng)中。 文章部分實(shí)例和內(nèi)容來自鳥哥的blogJavascript作用域原理 首先應(yīng)該注意幾個(gè)點(diǎn): 函數(shù)也是對(duì)象 variable object(VO) A variable object is a container...
閱讀 1882·2021-11-11 16:55
閱讀 2064·2021-10-08 10:13
閱讀 739·2019-08-30 11:01
閱讀 2155·2019-08-29 13:19
閱讀 3277·2019-08-28 18:18
閱讀 2620·2019-08-26 13:26
閱讀 579·2019-08-26 11:40
閱讀 1864·2019-08-23 17:17