日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

举例详细说明javascript作用域、闭包原理以及性能问题(转)

發(fā)布時(shí)間:2025/4/9 javascript 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 举例详细说明javascript作用域、闭包原理以及性能问题(转) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

轉(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ò)程:

  • assignEvents函數(shù)創(chuàng)建,詞法解析后,函數(shù)對(duì)象assignEvents的[[scope]]屬性被初始化,作用域鏈形成,作用域鏈中包含了全局對(duì)象的所有屬性和方法(注意,此時(shí)因?yàn)閍ssignEvents函數(shù)并未執(zhí)行,所以閉包函數(shù)并沒(méi)有被解析)。
  • assignEvents函數(shù)執(zhí)行,在開(kāi)始執(zhí)行時(shí),創(chuàng)建Execution Context(我們將圖4按照從左到右,從上到下的順序劃分為6部分,第一部分就是運(yùn)行期上下文),在運(yùn)行期上下文的作用域鏈中創(chuàng)建Activation Object(第二、三部分),并將Activation Object放置與作用域鏈頂點(diǎn),在其中保存了函數(shù)執(zhí)行時(shí)所有可訪問(wèn)函數(shù)內(nèi)部的數(shù)據(jù)。
  • 當(dāng)執(zhí)行到閉包時(shí),javascript引擎發(fā)現(xiàn)了閉包函數(shù)的存在,按照通常的手法,將閉包函數(shù)解析,為閉包函數(shù)對(duì)象創(chuàng)建[[scope]]屬性,初始化作用域鏈(此時(shí)閉包函數(shù)對(duì)象的作用域鏈中有兩個(gè)對(duì)象,一個(gè)是assignEvents函數(shù)執(zhí)行時(shí)的Activation Object,還有一個(gè)是全局對(duì)象,圖4的4、5、6部分。)。我們看到圖中閉包函數(shù)對(duì)象的作用域鏈和assignEvents函數(shù)的執(zhí)行上下文作用域鏈相同?為什么相同呢?我們來(lái)分析一下,閉包函數(shù)是在assignEvents函數(shù)執(zhí)行的過(guò)程中被發(fā)現(xiàn)并且解析的,而函數(shù)執(zhí)行時(shí)的作用域是Activation Object,那么結(jié)果就很明顯了,閉包函數(shù)被解析的時(shí)候它的作用域正是assignEvents作用域鏈中的第一個(gè)作用域?qū)ο驛ctivation Object,當(dāng)然,由于作用域鏈的關(guān)系,全局對(duì)象作用域也被引入到閉包函數(shù)的作用域鏈中。 那么我們現(xiàn)在考慮另一個(gè)問(wèn)題,閉包作用域鏈中的Activation Object,是引用了assignEvents函數(shù)的Activation Object,還是拷貝了一個(gè)副本到閉包的作用域鏈中了?我們可以做一個(gè)小的測(cè)試,在有多個(gè)閉包同時(shí)引用外層函數(shù)局部變量(i)的情況下,如果其中一個(gè)閉包改變了i的內(nèi)容,而其他閉包中的i的內(nèi)容沒(méi)有發(fā)生改變,則說(shuō)明產(chǎn)生了拷貝,反之,則引用了同一個(gè)Activation Object。
  • function fn(){ var i = 0; (function(){++i;console.log(i)})(); (function(){++i;console.log(i)})(); } fn(); //1 //2
  • 我們發(fā)現(xiàn)變量i從1變?yōu)榱?,說(shuō)明兩個(gè)閉包引用的是同一個(gè)變量i,也就說(shuō)明他們引用的fn的Activation Object是同一個(gè),其實(shí)完全可以換一種非常簡(jiǎn)單的方式來(lái)解釋:全局對(duì)象肯定是同一個(gè)吧?
  • 下面討論當(dāng)閉包函數(shù)執(zhí)行時(shí)的情況,因?yàn)樵谠~法分析的時(shí)候閉包函數(shù)就已經(jīng)在作用域鏈中保存了對(duì)assignEvents函數(shù)的Activation Object的引用,所以當(dāng)assignEvents函數(shù)執(zhí)行完畢之后,閉包函數(shù)雖然還沒(méi)有開(kāi)始執(zhí)行,但依然可以訪問(wèn)assignEvents的局部數(shù)據(jù)(并不是因?yàn)殚]包函數(shù)要訪問(wèn)assignEvents的局部變量id,所以當(dāng)assignEvents函數(shù)執(zhí)行完畢之后依然保持了對(duì)局部變量id的引用。而是不管是否存在變量引用,都會(huì)保存對(duì)assignEvents的Activation Object作用域?qū)ο蟮囊谩R驗(yàn)樵谠~法分析時(shí),閉包函數(shù)沒(méi)有執(zhí)行,函數(shù)內(nèi)部根本就不知道是否要對(duì)assignEvents的局部變量進(jìn)行訪問(wèn)和操作,所以只能先把a(bǔ)ssignEvents的Activation Object作用域?qū)ο蟊4嫫饋?lái),當(dāng)閉包函數(shù)執(zhí)行時(shí),如果需要訪問(wèn)assignEvents的局部變量,那么再去作用域鏈中搜索)。
  • 閉包函數(shù)執(zhí)行時(shí)創(chuàng)建了自己的Execution Context和Activation Object,在運(yùn)行期上下文的作用域鏈中保存了自己的Activation Object,外層函數(shù)assignEvents的Execution Context的Activation Object,以及Global Object,如圖:
  • 圖 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)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。