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