javascript
举例详细说明javascript作用域、闭包原理以及性能问题(转)
轉(zhuǎn)自:http://www.cnblogs.com/mrsunny/archive/2011/11/03/2233978.html
這可能是每一個(gè)jser都曾經(jīng)為之頭疼的卻又非常經(jīng)典的問(wèn)題,關(guān)系到內(nèi)存,關(guān)系到閉包,關(guān)系到j(luò)avascript運(yùn)行機(jī)制。關(guān)系到功能,關(guān)系到性能。
文章內(nèi)容主要參考自《High Performance JavaScript》,這本書對(duì)javascript性能方面確實(shí)講的比較深入,大家有空都可以嘗試著閱讀一下,下載地址:中英電子版。
復(fù)習(xí),筆記,更深入的理解。
歡迎拍磚指正。
作用域:
下面我們先搞明白這樣幾個(gè)概念:
- 函數(shù)對(duì)象的[[scope]]屬性、ScopeChain(作用域鏈)
- Execution Context(運(yùn)行期上下文)、Activation Object(激活對(duì)象)
[[scope]]屬性:
javascript中每個(gè)函數(shù)都是一個(gè)函數(shù)對(duì)象(函數(shù)實(shí)例),既然是對(duì)象,就有相關(guān)的屬性和方法。[[scope]]就是每個(gè)函數(shù)對(duì)象都具有的一個(gè)僅供javascript引擎內(nèi)部使用的屬性,該屬性是一個(gè)集合(類似于鏈表結(jié)構(gòu)),集合中保存了該函數(shù)在被創(chuàng)建時(shí)的作用域中的所有對(duì)象,而這個(gè)作用域集合形成的鏈表則被稱為ScopeChain(作用域鏈)。
該作用域鏈中保存的作用域?qū)ο?#xff0c;就是該函數(shù)可以訪問(wèn)的所有數(shù)據(jù)。例如(例子引用自《High Performance JavaScript高性能javascript》):
function add(num1, num2){ var sum = num1 + num2; return sum; } 圖 1當(dāng)add函數(shù)被創(chuàng)建時(shí),函數(shù)所在的全局作用域的全局對(duì)象被放置到add函數(shù)的作用域鏈([[scope]]屬性)中。我們可以從圖1中看到作用域鏈的第一個(gè)對(duì)象保存的是全局對(duì)象,全局對(duì)象中保存了諸如this,window,document以及全局對(duì)象中的add函數(shù),也就是他自己。這也就是我們可以在全局作用域下的函數(shù)中訪問(wèn)window(this),訪問(wèn)全局變量,訪問(wèn)函數(shù)自身的原因。當(dāng)然還有函數(shù)作用域不是全局的情況,等會(huì)兒我們?cè)儆懻摗?/span>
Execution Context(運(yùn)行期上下文)、Activation Object(激活對(duì)象):
(前天看了老羅的演講,老羅說(shuō)過(guò)年的時(shí)候給全公司的人每人發(fā)一臺(tái)電冰箱,要給校舍的所有的廁所門上都安上新鎖,保證童鞋們能有個(gè)真正隱私的地方。)
var total = add(5, 10);
當(dāng)開(kāi)始執(zhí)行此函數(shù)時(shí),就會(huì)創(chuàng)建一個(gè)Execution Context的內(nèi)部對(duì)象,該對(duì)象定義了函數(shù)運(yùn)行時(shí)的作用域環(huán)境(注意這里要和函數(shù)創(chuàng)建時(shí)的作用域鏈對(duì)象[[scope]]區(qū)分,這是兩個(gè)不同的作用域鏈對(duì)象,這樣分開(kāi)我猜測(cè)一是為了保護(hù)[[scope]],二是為了方便根據(jù)不同的運(yùn)行時(shí)環(huán)境控制作用域鏈。函數(shù)每執(zhí)行一次,都會(huì)創(chuàng)建單獨(dú)的Execution Context,也就相當(dāng)于每次執(zhí)行函數(shù)前,都把函數(shù)的作用域鏈復(fù)制了一份到當(dāng)前的Execution Context中)。Execution Context對(duì)象有自己的作用域鏈,在Execution Context創(chuàng)建時(shí)初始化,會(huì)將函數(shù)創(chuàng)建時(shí)的作用域鏈對(duì)象[[scope]]中的全部?jī)?nèi)容按照在[[scope]]作用域鏈中的順序復(fù)制到Execution Context的作用域鏈中。
此時(shí),在Execution Context的作用域鏈的頂部會(huì)插入一個(gè)新的對(duì)象,叫做Activation Object(激活對(duì)象),這個(gè)激活對(duì)象又是干嘛的呢?這個(gè)激活對(duì)象保存了函數(shù)中的所有形參,實(shí)參,局部變量,this指針等函數(shù)執(zhí)行時(shí)函數(shù)內(nèi)部的數(shù)據(jù)情況,這個(gè)Activation Object是一個(gè)可變對(duì)象,里面的數(shù)據(jù)隨著函數(shù)執(zhí)行時(shí)的數(shù)據(jù)的變化而變化,當(dāng)函數(shù)執(zhí)行結(jié)束之后,就會(huì)銷毀Execution Context,也就會(huì)銷毀Execution Context的作用域鏈,當(dāng)然也就會(huì)銷毀Activation Object(但如果存在閉包,Activation Object就會(huì)以另外一種方式存在,這也是閉包產(chǎn)生的真正原因,具體的我們稍后討論。)。具體情況如圖所示:
圖 2
我們從左往右看,第一部分是函數(shù)執(zhí)行時(shí)創(chuàng)建的Execution Context,它有自己的作用域鏈,第二部分是作用域鏈中的對(duì)象,索引為1的對(duì)象是從[[scope]]作用域鏈中復(fù)制過(guò)來(lái)的,索引為0的對(duì)象是在函數(shù)執(zhí)行時(shí)創(chuàng)建的,第三部分是作用域鏈中的對(duì)象的內(nèi)容Activation Object和Global Object。
函數(shù)在運(yùn)行過(guò)程中,沒(méi)遇到一個(gè)變量,都會(huì)去Execution Context的作用域鏈中從上到下依次搜索,如果在第一個(gè)作用域鏈(假如是Activation Object)中找到了,那么就返回這個(gè)變量,如果沒(méi)有找到,那么繼續(xù)向下查找,直到找到為止,這也就是為什么函數(shù)可以訪問(wèn)全局變量,當(dāng)局部變量和全局變量同名時(shí),會(huì)使用局部變量而不使用全局變量,以及javascript中各種看似怪異的、有趣的作用域問(wèn)題的答案(你可以用這種方法來(lái)解釋你以前碰到的所有作用域問(wèn)題,當(dāng)然,如果還是有疑問(wèn)的話,非常希望你能貼出代碼,我們一起討論。)
一般情況下,一個(gè)函數(shù)的作用域鏈?zhǔn)遣粫?huì)在函數(shù)運(yùn)行時(shí)被改變的,但有些運(yùn)算符會(huì)臨時(shí)改變作用域鏈,with和try catch的catch子句。看下面的例子:
function initUI(){ with (document){ //avoid! var bd = body, links = getElementsByTagName("a"), i= 0, len = links.length; while(i < len){ update(links[i++]); } getElementById("go-btn").onclick = function(){ start(); }; bd.className = "active"; }//eOf with }上例中,在onclick事件的事件處理器中引用了外部函數(shù)assignEvents的局部變量id,形成了閉包,下面我們看一下它們的作用域圖示:
圖 4
我們一起來(lái)從作用域的角度分析一下閉包的形成過(guò)程:
圖 5
這也就是閉包為何能“記得”在它周圍到底發(fā)生了什么,為何閉包能訪問(wèn)外層函數(shù)的局部數(shù)據(jù),為何閉包能保持這些局部數(shù)據(jù)而不在外層函數(shù)執(zhí)行完畢銷毀時(shí)一起銷毀等等的原因。
前些天一個(gè)前輩(Darrel文叔)告訴我一句話,一針見(jiàn)血:沒(méi)有內(nèi)存,就沒(méi)有閉包。
性能問(wèn)題:
在作用域鏈和閉包中的性能問(wèn)題主要表現(xiàn)在數(shù)據(jù)讀寫的速度上。
由于作用域鏈的原因,我們?cè)L問(wèn)全局作用域的數(shù)據(jù)(這里為什么不說(shuō)變量呢?因?yàn)椴粌H包括變量,還有函數(shù),對(duì)象等其他內(nèi)容)時(shí),效率是最低的,而訪問(wèn)局部數(shù)據(jù)時(shí)的效率是最高的。
所以一個(gè)非常經(jīng)典的解決數(shù)據(jù)訪問(wèn)性能問(wèn)題的方案出現(xiàn)了:將需要訪問(wèn)的數(shù)據(jù)盡量的以局部數(shù)據(jù)的方式緩存起來(lái)。這樣當(dāng)標(biāo)識(shí)符解析程序在作用域鏈中尋找數(shù)據(jù)時(shí),直接就可以在作用域鏈的最上層找到想要的數(shù)據(jù),效率自然就提升了。
這句話可以解決很多性能問(wèn)題:設(shè)置緩存,將數(shù)據(jù)保存在局部變量中。
轉(zhuǎn)載請(qǐng)注明出處:
參考:
- 《高性能javascript》——Zakas(劉新 譯)
- 李松峰《理解javascript閉包》
- 阮一峰《學(xué)習(xí)javascript閉包》
- 網(wǎng)絡(luò)各種雜文
轉(zhuǎn)載于:https://www.cnblogs.com/jintianfan/p/3475470.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的举例详细说明javascript作用域、闭包原理以及性能问题(转)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 简单的对象监听器 观察者设计模式
- 下一篇: [译] 第五天: GruntJS - 为