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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

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

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

轉自:http://www.cnblogs.com/mrsunny/archive/2011/11/03/2233978.html

這可能是每一個jser都曾經為之頭疼的卻又非常經典的問題,關系到內存,關系到閉包,關系到javascript運行機制。關系到功能,關系到性能。

文章內容主要參考自《High Performance JavaScript》,這本書對javascript性能方面確實講的比較深入,大家有空都可以嘗試著閱讀一下,下載地址:中英電子版

復習,筆記,更深入的理解。

歡迎拍磚指正。

作用域:

下面我們先搞明白這樣幾個概念:

  • 函數對象的[[scope]]屬性、ScopeChain(作用域鏈)
  • Execution Context(運行期上下文)、Activation Object(激活對象)

[[scope]]屬性:

javascript中每個函數都是一個函數對象(函數實例),既然是對象,就有相關的屬性和方法。[[scope]]就是每個函數對象都具有的一個僅供javascript引擎內部使用的屬性,該屬性是一個集合(類似于鏈表結構),集合中保存了該函數在被創建時的作用域中的所有對象,而這個作用域集合形成的鏈表則被稱為ScopeChain(作用域鏈)。

該作用域鏈中保存的作用域對象,就是該函數可以訪問的所有數據。例如(例子引用自《High Performance JavaScript高性能javascript》):

function add(num1, num2){ var sum = num1 + num2; return sum; } 圖 1

當add函數被創建時,函數所在的全局作用域的全局對象被放置到add函數的作用域鏈([[scope]]屬性)中。我們可以從圖1中看到作用域鏈的第一個對象保存的是全局對象,全局對象中保存了諸如this,window,document以及全局對象中的add函數,也就是他自己。這也就是我們可以在全局作用域下的函數中訪問window(this),訪問全局變量,訪問函數自身的原因。當然還有函數作用域不是全局的情況,等會兒我們再討論。

Execution Context(運行期上下文)、Activation Object(激活對象):

(前天看了老羅的演講,老羅說過年的時候給全公司的人每人發一臺電冰箱,要給校舍的所有的廁所門上都安上新鎖,保證童鞋們能有個真正隱私的地方。)

var total = add(5, 10);

當開始執行此函數時,就會創建一個Execution Context的內部對象,該對象定義了函數運行時的作用域環境(注意這里要和函數創建時的作用域鏈對象[[scope]]區分,這是兩個不同的作用域鏈對象,這樣分開我猜測一是為了保護[[scope]],二是為了方便根據不同的運行時環境控制作用域鏈。函數每執行一次,都會創建單獨的Execution Context,也就相當于每次執行函數前,都把函數的作用域鏈復制了一份到當前的Execution Context中)。Execution Context對象有自己的作用域鏈,在Execution Context創建時初始化,會將函數創建時的作用域鏈對象[[scope]]中的全部內容按照在[[scope]]作用域鏈中的順序復制到Execution Context的作用域鏈中。

此時,在Execution Context的作用域鏈的頂部會插入一個新的對象,叫做Activation Object(激活對象),這個激活對象又是干嘛的呢?這個激活對象保存了函數中的所有形參,實參,局部變量,this指針等函數執行時函數內部的數據情況,這個Activation Object是一個可變對象,里面的數據隨著函數執行時的數據的變化而變化,當函數執行結束之后,就會銷毀Execution Context,也就會銷毀Execution Context的作用域鏈,當然也就會銷毀Activation Object(但如果存在閉包,Activation Object就會以另外一種方式存在,這也是閉包產生的真正原因,具體的我們稍后討論。)。具體情況如圖所示:

圖 2

我們從左往右看,第一部分是函數執行時創建的Execution Context,它有自己的作用域鏈,第二部分是作用域鏈中的對象,索引為1的對象是從[[scope]]作用域鏈中復制過來的,索引為0的對象是在函數執行時創建的,第三部分是作用域鏈中的對象的內容Activation Object和Global Object。

函數在運行過程中,沒遇到一個變量,都會去Execution Context的作用域鏈中從上到下依次搜索,如果在第一個作用域鏈(假如是Activation Object)中找到了,那么就返回這個變量,如果沒有找到,那么繼續向下查找,直到找到為止,這也就是為什么函數可以訪問全局變量,當局部變量和全局變量同名時,會使用局部變量而不使用全局變量,以及javascript中各種看似怪異的、有趣的作用域問題的答案(你可以用這種方法來解釋你以前碰到的所有作用域問題,當然,如果還是有疑問的話,非常希望你能貼出代碼,我們一起討論。)

一般情況下,一個函數的作用域鏈是不會在函數運行時被改變的,但有些運算符會臨時改變作用域鏈,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事件的事件處理器中引用了外部函數assignEvents的局部變量id,形成了閉包,下面我們看一下它們的作用域圖示:

圖 4

我們一起來從作用域的角度分析一下閉包的形成過程:

  • assignEvents函數創建,詞法解析后,函數對象assignEvents的[[scope]]屬性被初始化,作用域鏈形成,作用域鏈中包含了全局對象的所有屬性和方法(注意,此時因為assignEvents函數并未執行,所以閉包函數并沒有被解析)。
  • assignEvents函數執行,在開始執行時,創建Execution Context(我們將圖4按照從左到右,從上到下的順序劃分為6部分,第一部分就是運行期上下文),在運行期上下文的作用域鏈中創建Activation Object(第二、三部分),并將Activation Object放置與作用域鏈頂點,在其中保存了函數執行時所有可訪問函數內部的數據。
  • 當執行到閉包時,javascript引擎發現了閉包函數的存在,按照通常的手法,將閉包函數解析,為閉包函數對象創建[[scope]]屬性,初始化作用域鏈(此時閉包函數對象的作用域鏈中有兩個對象,一個是assignEvents函數執行時的Activation Object,還有一個是全局對象,圖4的4、5、6部分。)。我們看到圖中閉包函數對象的作用域鏈和assignEvents函數的執行上下文作用域鏈相同?為什么相同呢?我們來分析一下,閉包函數是在assignEvents函數執行的過程中被發現并且解析的,而函數執行時的作用域是Activation Object,那么結果就很明顯了,閉包函數被解析的時候它的作用域正是assignEvents作用域鏈中的第一個作用域對象Activation Object,當然,由于作用域鏈的關系,全局對象作用域也被引入到閉包函數的作用域鏈中。 那么我們現在考慮另一個問題,閉包作用域鏈中的Activation Object,是引用了assignEvents函數的Activation Object,還是拷貝了一個副本到閉包的作用域鏈中了?我們可以做一個小的測試,在有多個閉包同時引用外層函數局部變量(i)的情況下,如果其中一個閉包改變了i的內容,而其他閉包中的i的內容沒有發生改變,則說明產生了拷貝,反之,則引用了同一個Activation Object。
  • function fn(){ var i = 0; (function(){++i;console.log(i)})(); (function(){++i;console.log(i)})(); } fn(); //1 //2
  • 我們發現變量i從1變為了2,說明兩個閉包引用的是同一個變量i,也就說明他們引用的fn的Activation Object是同一個,其實完全可以換一種非常簡單的方式來解釋:全局對象肯定是同一個吧?
  • 下面討論當閉包函數執行時的情況,因為在詞法分析的時候閉包函數就已經在作用域鏈中保存了對assignEvents函數的Activation Object的引用,所以當assignEvents函數執行完畢之后,閉包函數雖然還沒有開始執行,但依然可以訪問assignEvents的局部數據(并不是因為閉包函數要訪問assignEvents的局部變量id,所以當assignEvents函數執行完畢之后依然保持了對局部變量id的引用。而是不管是否存在變量引用,都會保存對assignEvents的Activation Object作用域對象的引用。因為在詞法分析時,閉包函數沒有執行,函數內部根本就不知道是否要對assignEvents的局部變量進行訪問和操作,所以只能先把assignEvents的Activation Object作用域對象保存起來,當閉包函數執行時,如果需要訪問assignEvents的局部變量,那么再去作用域鏈中搜索)。
  • 閉包函數執行時創建了自己的Execution Context和Activation Object,在運行期上下文的作用域鏈中保存了自己的Activation Object,外層函數assignEvents的Execution Context的Activation Object,以及Global Object,如圖:
  • 圖 5

    這也就是閉包為何能“記得”在它周圍到底發生了什么,為何閉包能訪問外層函數的局部數據,為何閉包能保持這些局部數據而不在外層函數執行完畢銷毀時一起銷毀等等的原因。

    前些天一個前輩(Darrel文叔)告訴我一句話,一針見血:沒有內存,就沒有閉包。

    性能問題:

    在作用域鏈和閉包中的性能問題主要表現在數據讀寫的速度上。

    由于作用域鏈的原因,我們訪問全局作用域的數據(這里為什么不說變量呢?因為不僅包括變量,還有函數,對象等其他內容)時,效率是最低的,而訪問局部數據時的效率是最高的。

    所以一個非常經典的解決數據訪問性能問題的方案出現了:將需要訪問的數據盡量的以局部數據的方式緩存起來。這樣當標識符解析程序在作用域鏈中尋找數據時,直接就可以在作用域鏈的最上層找到想要的數據,效率自然就提升了。

    這句話可以解決很多性能問題:設置緩存,將數據保存在局部變量中。

    轉載請注明出處:

    參考:

    • 《高性能javascript》——Zakas(劉新 譯)
    • 李松峰《理解javascript閉包
    • 阮一峰《學習javascript閉包
    • 網絡各種雜文

    轉載于:https://www.cnblogs.com/jintianfan/p/3475470.html

    《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

    總結

    以上是生活随笔為你收集整理的举例详细说明javascript作用域、闭包原理以及性能问题(转)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。