通過(guò)JS運(yùn)行機(jī)制的角度說(shuō)說(shuō)作用域
前言
任何程序設(shè)計(jì)語(yǔ)言都有作用域的概念,簡(jiǎn)單的說(shuō),作用域就是變量與函數(shù)的可訪(fǎng)問(wèn)范圍。JS中的作用域、閉包、this機(jī)制和原型往往是最難理解的概念之一。筆者將通過(guò)幾篇文章和大家談?wù)勛约旱睦斫猓M麑?duì)大家的學(xué)習(xí)有一些幫助。如果有什么理解偏差的地方,希望大家可以評(píng)論指出,相互學(xué)習(xí)。
有過(guò)一定編程經(jīng)驗(yàn)的同學(xué),一定不會(huì)對(duì)作用域感到陌生,在C/C++/Java中等語(yǔ)言中,作用域從來(lái)沒(méi)有JavaScript中的作用域那樣令人困惑以致于成為一個(gè)大多數(shù)JS開(kāi)發(fā)者都難以跨過(guò)的門(mén)檻。
作用域形成機(jī)制
JS中存在的三種作用域類(lèi)型:全局作用域,函數(shù)作用域和ES6中新加入的塊級(jí)作用域。
var a = 1; function foo() { var b = 2; console.log(a); // 1 console.log(b); // 2 console.log(c); // ReferenceError } function foo1() { var c = 3; console.log(a); // 1 console.log(b); // ReferenceError console.log(c); // 3 } console.log(a); // 1 console.log(b); // ReferenceError console.log(c); // ReferenceError foo(); foo1();
從上面的例子可以看到,每個(gè)函數(shù)內(nèi)部形成了屬于自己的作用域,函數(shù)內(nèi)部聲明的變量?jī)H僅在定義的函數(shù)內(nèi)部才可以訪(fǎng)問(wèn)。全局作用域中可以訪(fǎng)問(wèn)到的有a,foo,foo作用域中可以訪(fǎng)問(wèn)到的有b,foo,a,foo1的作用域中可以訪(fǎng)問(wèn)到的有c,foo,a。因?yàn)閒oo的作用域嵌套在全局作用域之中,當(dāng)console.log(a);執(zhí)行的時(shí)候,JS在foo的作用域查找不到a,就會(huì)到它的上層(這里是foo的上層直接就是全局作用域)查找,發(fā)現(xiàn)這里聲明了一個(gè)a,將它的值打印了出來(lái)。這種從里到外的查找就是根據(jù)作用域鏈查找。foo1和foo的作用域沒(méi)有嵌套關(guān)系,所以相互隔離。
如果函數(shù)中使用了未聲明的變量怎么辦?
function foo() { a = 2; } foo(); console.log(a); // 2
JS引擎在foo中查找不到a的聲明,便會(huì)到它的上層(這里是全局作用域中)查找,這個(gè)時(shí)候還是沒(méi)有查找到a的聲明,在非嚴(yán)格模式下,JS引擎會(huì)在全局中自動(dòng)聲明一個(gè)a,這個(gè)時(shí)候,未經(jīng)聲明的變量a實(shí)際上泄漏到了全局作用域中。
只有使用未聲明的變量才會(huì)出現(xiàn)變量泄漏的問(wèn)題么,其實(shí),不僅僅這種寫(xiě)法會(huì)出現(xiàn),更常見(jiàn)的也會(huì)出現(xiàn)在for循環(huán)和if代碼塊中也會(huì)出現(xiàn)。
for(var i=1;i<10;i++) { console.log(i); } console.log(i); // 10, 這里的i泄漏到了全局作用域中 if(true) { var a = 2; } console.log(a); // 2, 這里的a也泄漏到了全局變量之中
如果你學(xué)習(xí)過(guò)C語(yǔ)言系列的語(yǔ)法,往往很容易感到困惑,if和for居然沒(méi)有作用域,這真是太奇怪了。這一切的問(wèn)題的根源,都是由于ES6之前沒(méi)有塊級(jí)作用域?qū)е碌?。所以可想而知,if包裹的代碼塊,同樣里面的聲明也是暴露出來(lái)的~
一切問(wèn)題的解決直到ES6中引入了let和const得以完美的解決。使用let和const,將可以使用塊級(jí)作用域,使得聲明變量泄漏的問(wèn)題得以解決。
for(let i=1;i<10;i++) { console.log(i); } console.log(i); // ReferenceError if(true) { let a = 2; } console.log(a); // ReferenceError
聲明提升機(jī)制
對(duì)于在JS中聲明的不論是變量還是函數(shù),基本上都會(huì)存在著變量聲明提升的行為,將變量的聲明提升到所在作用域的頂端。ES6中的let和const不會(huì),在未聲明之前都不可以使用。
看看下面的代碼
console.log(a); // undefined console.log(b); // undefined console.log(foo); // Function console.log(foo2); // ReferenceError function foo () { console.log('聲明提升了哈'); } var a = 1; var b = function foo2() { console.log('不同的函數(shù)聲明方式提升的結(jié)果也不一樣哦'); };
JS 引擎解釋這段代碼之前首先對(duì)代碼中所有的變量進(jìn)行了聲明的提升,函數(shù)聲明的提升的優(yōu)先級(jí)是高于普通變量的,函數(shù)聲明會(huì)整個(gè)提升到所在作用域的頂端(但是以函數(shù)表達(dá)式方式聲明的函數(shù)不會(huì)),代碼實(shí)際上是下面這個(gè)樣子:
function foo () { console.log('聲明提升了哈'); } var a; var b; var foo2; console.log(a); console.log(b); console.log(foo); console.log(foo2); b = function foo2 () { console.log('不同的函數(shù)聲明方式提升的結(jié)果也不一樣哦'); }
靜態(tài)作用域機(jī)制(詞法作用域)
關(guān)于JS中的作用域,需要明確的一點(diǎn)就是,JS中只存在靜態(tài)作用域(詞法作用域)。靜態(tài)作用域是什么意思呢?意思就是它的作用域在你寫(xiě)下代碼的時(shí)候就已經(jīng)確定了,和函數(shù)的調(diào)用順序無(wú)關(guān),了解這一點(diǎn)。就可以對(duì)一些常見(jiàn)的現(xiàn)象進(jìn)行解釋。
var a = 2; function foo() { console.log(a); } var obj = { a: 3, foo: foo } obj.foo(); // 2
foo中的a在代碼寫(xiě)完時(shí)就確認(rèn)了,指向了全局作用域中的a,一旦確定就無(wú)法更改了。同理,下面的代碼
function foo() { console.log(b); // ReferenceError } function foo1 () { var b = 1; foo(); } foo1();
這里,JS引擎在全局作用域中查找不到b,所以會(huì)拋出一個(gè)異常。所以可以明確的道理是,foo的作用域和foo1的作用域仍然是相互獨(dú)立的,不會(huì)因?yàn)檎{(diào)用時(shí)候的順序而更改作用域的嵌套順序,靜態(tài)作用域在代碼書(shū)寫(xiě)時(shí)就已經(jīng)確定無(wú)法更改了,明白這一點(diǎn)在分析JS代碼的時(shí)候尤為重要。
坑外話(huà)
變量的遮蔽效應(yīng)
在函數(shù)中定義的變量會(huì)遮蔽上層作用域中同名的變量,兩個(gè)變量互不影響。
var a = 1; function foo() { var a = 2; console.log(a); // 2 } console.log(a); // 1
Try-Catch 中的塊級(jí)作用域
try-catch的catch中會(huì)創(chuàng)建一個(gè)塊級(jí)作用域,該作用域內(nèi)變量的表現(xiàn)同樣遵守變量的聲明提升規(guī)則。
try { throw undefined; }catch(e) { a = 1; console.log(e); // undefined } console.log(a); // 1, 變量提升規(guī)則 console.log(e); // ReferenceError,catch的塊作用域中定義的變量
隱式聲明
以參數(shù)形式傳入的變量在函數(shù)內(nèi)部實(shí)際上存在的隱式的聲明,使用時(shí)不算作未聲明的變量。
function foo(a) { a = 1; console.log(a); } foo(); // 1 console.log(a); // ReferenceError
本來(lái)想一篇文章寫(xiě)完作用域和閉包的,想例子實(shí)在是累,就拆作兩篇吧,逃~
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
js實(shí)現(xiàn)DIV的一些簡(jiǎn)單控制
js實(shí)現(xiàn)DIV的一些簡(jiǎn)單控制...2007-06-06windows系統(tǒng)下簡(jiǎn)單nodejs安裝及環(huán)境配置
相信對(duì)于很多關(guān)注javascript發(fā)展的同學(xué)來(lái)說(shuō),nodejs已經(jīng)不是一個(gè)陌生的詞眼,這里不想談太多的nodejs的相關(guān)信息。只說(shuō)一下,windows系統(tǒng)下簡(jiǎn)單nodejs環(huán)境配置2013-01-01js實(shí)現(xiàn)(全選)多選按鈕的方法【附實(shí)例】
下面小編就為大家?guī)?lái)一篇js實(shí)現(xiàn)(全選)多選按鈕的方法【附實(shí)例】。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-03-03js中setTimeout()與clearTimeout()用法實(shí)例淺析
這篇文章主要介紹了js中setTimeout()與clearTimeout()用法,以實(shí)例形式分析了setTimeout()與clearTimeout()的功能與使用技巧,需要的朋友可以參考下2015-05-05兼容IE/Firefox/Opera/Safari的檢測(cè)頁(yè)面裝載完畢的腳本Ext.onReady的實(shí)現(xiàn)
其中對(duì)于IE的檢測(cè)很有意思。 以上代碼,整理自Extjs的腳本,完全可以代替 Ext.onReady使用。2009-07-07低門(mén)檻開(kāi)發(fā)iOS、Android、小程序應(yīng)用的前端框架詳解
結(jié)合AVM官網(wǎng)的介紹和我自己的一些實(shí)踐經(jīng)驗(yàn),我總結(jié)了一系列AVM的特性,我想這些內(nèi)容足以讓你主動(dòng)去學(xué)習(xí)AVM框架了2021-10-10