您好,登錄后才能下訂單哦!
這篇文章主要介紹Javascript之作用域、作用域鏈、閉包的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
什么是作用域?
作用域是一種規(guī)則,在代碼編譯階段就確定了,規(guī)定了變量與函數(shù)的可被訪問的范圍。全局變量擁有全局作用域,局部變量則擁有局部作用域。 js是一種沒有塊級(jí)作用域的語言(包括if、for等語句的花括號(hào)代碼塊或者單獨(dú)的花括號(hào)代碼塊都不能形成一個(gè)局部作用域),所以js的局部作用域的形成有且只有函數(shù)的花括號(hào)內(nèi)定義的代碼塊形成的,既函數(shù)作用域。
什么是作用域鏈?
作用域鏈?zhǔn)亲饔糜蛞?guī)則的實(shí)現(xiàn),通過作用域鏈的實(shí)現(xiàn),變量在它的作用域內(nèi)可被訪問,函數(shù)在它的作用域內(nèi)可被調(diào)用。
作用域鏈?zhǔn)且粋€(gè)只能單向訪問的鏈表,這個(gè)鏈表上的每個(gè)節(jié)點(diǎn)就是執(zhí)行上下文的變量對(duì)象(代碼執(zhí)行時(shí)就是活動(dòng)對(duì)象),單向鏈表的頭部(可被第一個(gè)訪問的節(jié)點(diǎn))始終都是當(dāng)前正在被調(diào)用執(zhí)行的函數(shù)的變量對(duì)象(活動(dòng)對(duì)象),尾部始終是全局活動(dòng)對(duì)象。
作用域鏈的形成?
我們從一段代碼的執(zhí)行來看作用域鏈的形成過程。
function fun01 () { console.log('i am fun01...'); fun02(); } function fun02 () { console.log('i am fun02...'); } fun01();
數(shù)據(jù)訪問流程
如上圖,當(dāng)程序訪問一個(gè)變量時(shí),按照作用域鏈的單向訪問特性,首先在頭節(jié)點(diǎn)的AO中查找,沒有則到下一節(jié)點(diǎn)的AO查找,最多查找到尾節(jié)點(diǎn)(global AO)。在這個(gè)過程中找到了就找到了,沒找到就報(bào)錯(cuò)undefined。
延長(zhǎng)作用域鏈
從上面作用域鏈的形成可以看出鏈上的每個(gè)節(jié)點(diǎn)是在函數(shù)被調(diào)用執(zhí)行是向鏈頭unshift進(jìn)當(dāng)前函數(shù)的AO,而節(jié)點(diǎn)的形成還有一種方式就是“延長(zhǎng)作用域鏈”,既在作用域鏈的頭部插入一個(gè)我們想要的對(duì)象作用域。延長(zhǎng)作用域鏈有兩種方式:
1.with語句
function fun01 () { with (document) { console.log('I am fun01 and I am in document scope...') } } fun01();
2.try-catch語句的catch塊
function fun01 () { try { console.log('Some exceptions will happen...') } catch (e) { console.log(e) } } fun01();
ps:個(gè)人感覺with語句使用需求不多,try-catch的使用也是看需求的。個(gè)人對(duì)這兩種使用不多,但是在進(jìn)行這部分整理過程中萌發(fā)了一點(diǎn)點(diǎn)在作用域鏈層面的不成熟的性能優(yōu)化小建議。
由作用域鏈引發(fā)的關(guān)于性能優(yōu)化的一點(diǎn)不成熟的小建議
1.減少變量的作用域鏈的訪問節(jié)點(diǎn)
這里我們自定義一個(gè)名次叫做“查找距離”,表示程序訪問到一個(gè)非undefined變量在作用域鏈中經(jīng)過的節(jié)點(diǎn)數(shù)。因?yàn)槿绻诋?dāng)前節(jié)點(diǎn)沒有找到變量,就跳到下一個(gè)節(jié)點(diǎn)查找,還要進(jìn)行判斷下一個(gè)節(jié)點(diǎn)中是否存在被查找變量。“查找距離”越長(zhǎng),要做的“跳”動(dòng)作和“判斷”動(dòng)作也就越多,資源開銷就越大,從而影響性能。這種性能帶來的差距可能少數(shù)的幾次變量查找操作不會(huì)帶來太多性能問題,但如果是多次進(jìn)行變量查找,性能對(duì)比則比較明顯了。
(function(){ console.time() var find = 1 //這個(gè)find變量需要在4個(gè)作用域鏈節(jié)點(diǎn)進(jìn)行查找 function fun () { function funn () { var funnv = 1; var funnvv = 2; function funnn () { var i = 0 while(i <= 100000000){ if(find){ i++ } } } funnn() } funn() } fun() console.timeEnd() })()
(function(){ console.time() function fun () { function funn () { var funnv = 1; var funnvv = 2; function funnn () { var i = 0 var find = 1 //這個(gè)find變量只在當(dāng)前節(jié)點(diǎn)進(jìn)行查找 while(i <= 100000000){ if(find){ i++ } } } funnn() } funn() } fun() console.timeEnd() })()
在mac pro的chrome瀏覽器下做實(shí)驗(yàn),進(jìn)行1億次查找運(yùn)算。
實(shí)驗(yàn)結(jié)果:前者運(yùn)行5次平均耗時(shí)85.599ms,后者運(yùn)行5次平均耗時(shí)63.127ms。
2.避免作用域鏈內(nèi)節(jié)點(diǎn)AO上過多的變量定義
過多的變量定義造成性能問題的原因主要是查找變量過程中的“判斷”操作開銷較大。我們使用with來進(jìn)行性能對(duì)比。
(function(){ console.time() function fun () { function funn () { var funnv = 1; var funnvv = 2; function funnn () { var i = 0 var find = 10 with (document) { while(i <= 1000000){ if(find){ i++ } } } } funnn() } funn() } fun() console.timeEnd() })()
在mac pro的chrome瀏覽器下做實(shí)驗(yàn),進(jìn)行100萬次查找運(yùn)算,借助with使用document進(jìn)行的延長(zhǎng)作用域鏈,因?yàn)閐ocument下的變量屬性比較多,可以測(cè)試在多變量作用域鏈節(jié)點(diǎn)下進(jìn)行查找的性能差異。
實(shí)驗(yàn)結(jié)果:5次平均耗時(shí)558.802ms,而如果刪掉with和document,5次平均耗時(shí)0.956ms。
當(dāng)然,這兩個(gè)實(shí)驗(yàn)是在我們假設(shè)的極端環(huán)境下進(jìn)行的,結(jié)果僅供參考!
關(guān)于閉包
1.什么是閉包?
函數(shù)對(duì)象可以通過作用域鏈相互關(guān)聯(lián)起來,函數(shù)體內(nèi)的數(shù)據(jù)(變量和函數(shù)聲明)都可以保存在函數(shù)作用域內(nèi),這種特性在計(jì)算機(jī)科學(xué)文獻(xiàn)中被稱為“閉包”。既函數(shù)體內(nèi)的數(shù)據(jù)被隱藏于作用于鏈內(nèi),看起來像是函數(shù)將數(shù)據(jù)“包裹”了起來。從技術(shù)角度來說,js的函數(shù)都是閉包:函數(shù)都是對(duì)象,都關(guān)聯(lián)到作用域鏈,函數(shù)內(nèi)數(shù)據(jù)都被保存在函數(shù)作用域內(nèi)。
2.閉包的幾種實(shí)現(xiàn)方式
實(shí)現(xiàn)方式就是函數(shù)A在函數(shù)B的內(nèi)部進(jìn)行定義了,并且當(dāng)函數(shù)A在執(zhí)行時(shí),訪問了函數(shù)B內(nèi)部的變量對(duì)象,那么B就是一個(gè)閉包。如下:
如上兩圖所示,是在chrome瀏覽器下查看閉包的方法。兩種方式的共同點(diǎn)是都有一個(gè)外部函數(shù)outerFun(),都在外部函數(shù)內(nèi)定義了內(nèi)部函數(shù)innerFun(),內(nèi)部函數(shù)都訪問了外部函數(shù)的數(shù)據(jù)。不同的是,第一種方式的innerFun()是在outerFun()內(nèi)被調(diào)用的,既聲明和被調(diào)用均在同一個(gè)執(zhí)行上下文內(nèi)。而第二種方式的innerFun()則是在outerFun()外被調(diào)用的,既聲明和被調(diào)用不在同一個(gè)執(zhí)行上下文。第二種方式恰好是js使用閉包常用的特性所在:通過閉包的這種特性,可以在其他執(zhí)行上下文內(nèi)訪問函數(shù)內(nèi)部數(shù)據(jù)。
我們更常用的一種方式則是這樣的:
//閉包實(shí)例 function outerFun () { var outerV1 = 10 function outerF1 () { console.log('I am outerF1...') } function innerFun () { var innerV1 = outerV1 outerF1() } return innerFun //return回innerFun()內(nèi)部函數(shù) } var fn = outerFun() //接到return回的innerFun()函數(shù) fn() //執(zhí)行接到的內(nèi)部函數(shù)innerFun()
此時(shí)它的作用域鏈?zhǔn)沁@樣的:
3.閉包的好處及使用場(chǎng)景
js的垃圾回收機(jī)制可以粗略的概括為:如果當(dāng)前執(zhí)行上下文執(zhí)行完畢,且上下文內(nèi)的數(shù)據(jù)沒有其他引用,則執(zhí)行上下文pop出call stack,其內(nèi)數(shù)據(jù)等待被垃圾回收。而當(dāng)我們?cè)谄渌麍?zhí)行上下文通過閉包對(duì)執(zhí)行完的上下文內(nèi)數(shù)據(jù)仍然進(jìn)行引用時(shí),那么被引用的數(shù)據(jù)則不會(huì)被垃圾回收。就像上面代碼中的outerV1,放我們?cè)谌稚舷挛耐ㄟ^調(diào)用innerFun()仍然訪問引用outerV1時(shí),那么outerFun執(zhí)行完畢后,outerV1也不會(huì)被垃圾回收,而是保存在內(nèi)存中。另外,outerV1看起來像不像一個(gè)outerFun的私有內(nèi)部變量呢?除了innerFun()外,我們無法隨意訪問outerV1。所以,綜上所述,這樣閉包的使用情景可以總結(jié)為:
(1)進(jìn)行變量持久化。
(2)使函數(shù)對(duì)象內(nèi)有更好的封裝性,內(nèi)部數(shù)據(jù)私有化。
進(jìn)行變量持久化方面舉個(gè)栗子:
我們假設(shè)一個(gè)需求時(shí)寫一個(gè)函數(shù)進(jìn)行類似id自增或者計(jì)算函數(shù)被調(diào)用的功能,普通青年這樣寫:
var count = 0 function countFun () { return count++ }
這樣寫固然實(shí)現(xiàn)了功能,但是count被暴露在外,可能被其他代碼篡改。這個(gè)時(shí)候閉包青年就會(huì)這樣寫:
function countFun () { var count = 0 return function(){ return count++ } } var a = countFun() a()
這樣count就不會(huì)被不小心篡改了,函數(shù)調(diào)用一次就count加一次1。而如果結(jié)合“函數(shù)每次被調(diào)用都會(huì)創(chuàng)建一個(gè)新的執(zhí)行上下文”,這種count的安全性還有如下體現(xiàn):
function countFun () { var count = 0 return { count: function () { count++ }, reset: function () { count = 0 }, printCount: function () { console.log(count) } } } var a = countFun() var b = countFun() a.count() a.count() b.count() b.reset() a.printCount() //打印:2 因?yàn)閍.count()被調(diào)用了兩次 b.printCount() //打印出:0 因?yàn)檎{(diào)用了b.reset()
以上便是閉包提供的變量持久化和封裝性的體現(xiàn)。
4.閉包的注意事項(xiàng)
由于閉包中的變量不會(huì)像其他正常變量那種被垃圾回收,而是一直存在內(nèi)存中,所以大量使用閉包可能會(huì)造成性能問題。
以上是“Javascript之作用域、作用域鏈、閉包的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。