摘要:當被創建時,它的作用域鏈初始化為當前運行函數的屬性中的對象,這些值按照他們出現在函數中的順序,被復制到執行環境的作用域鏈中。然后這個對象被推入作用域鏈最前端。
Javascript中四種基本的數據存儲位置在計算機科學中,數據存儲的位置關系到代碼執行過程中數據的檢索速度,有一個經典的問題即為:通過改變數據的存儲位置來獲得最佳的讀寫性能。
字面量
字面量只代表自身,不存儲在特定的位置。JavaScript中的字面量有:字符串,數字,布爾值,對象,數組,函數,正則表達式,以及null&undefined。
字面量是用于表達源代碼中一個固定值的表示法,例如:string str="hello world";
hello world為字面量
本地變量
開發人員使用關鍵字var定義的數據存儲單元
數組元素
存儲在JavaScript數組對象內部,以數字作為索引,這里注意和本地變量的區別,
var arr = new Array();
arr為本地變量,arr[0]為一個數組元素
對象成員
儲存在JavaScript對象內部,以字符串作為索引
每一種數據存儲的位置都有不同的讀寫消耗,一般而言:
從一個字面量中存取數據的性能約等于局部變量
數組元素和對象成員成本較高,高出多少由瀏覽器決定
管理作用域作用域概念是理解JavaScript的關鍵所在,不僅僅從性能,還包括從功能的角度。作用域對JavaScript有很多影響,從確定哪些變量可以被函數訪問,到確定this的賦值。
作用域鏈
function可以理解為一個“制造機器的機器”,那么我們可以這樣理解:每一個JavaScript函數都是一個function對象的實例。
那么function對象和其他對象一樣,擁有可以編程訪問的屬性和一系列不同通過代碼訪問而僅供JavaScript引擎存取的內部屬性。
內部屬性之Scope
先放一個Scope的有趣解釋;
Scope屬性包含了一個函數被創建時的作用域中的對象的集合,這個集合被稱為作用域鏈,它決定哪些數據能被函數訪問。
函數作用域中的每個對象被稱為一個可變對象,每個可變對象都以“鍵值對”的形式存在。
當一個函數創建后,它的作用域鏈會被創建此函數的作用域中可訪問的數據對象所填充。
我說下自己的理解:作用域、作用域鏈、內置屬性(Scope)其實可以類比權限、管理組、全局管理員,作用域中的對象以鍵值對的形式存在,成為可變對象,作用域鏈用來連接作用域和Scope,而Scope就好像一種專門管理全局對象的全局管理員;
舉一個例子:
我們先創建一個函數:
function add(num1,num2){ var sun = num1 + num2; return sum; }
這里我們創建了一個add()函數,當他被創建的時候,在這個函數的內置屬性Scope所包含的作用域鏈中插入一個對象變量,這個全局對象代表著所有在全局范圍內定義的變量。 改全局對象包含像window,navigator,document等;
當我們來執行上面的函數又會發生什么呢?
假如執行如下代碼:
var total = add(5,10);
此時函數會創建一個稱為 執行環境 或者叫 執行上下文 (execution context)的內部對象。
一個execution context定義了一個函數執行時的環境。
函數每次執行時對應的execution context都是獨一無二的,所以多次調用同一個函數就會創建多個不一樣的execution context
當函數執行完畢,execution context就會被銷毀
每個execution context都有自己的作用域鏈,用于解析標識符。
當execution context被創建時,它的作用域鏈初始化為當前運行函數的Scope屬性中的對象,這些值按照他們出現在函數中的順序,被復制到執行環境的作用域鏈中。
前面這個過程完成之后,一個“活動對象”也為execution context創建好了,該對象作為函數運行時的變量對象,包含了所有的局部變量,參數集合以及this。
然后這個對象被推入作用域鏈最前端。
當execution context被銷毀,活動對象也隨之銷毀。
我們在執行過程中是怎樣使用作用域鏈的呢?
在函數執行過程中,沒遇到一個變量,都會經歷一次標識符解析過程以決定從哪里獲取或存儲數據。標識符解析是性能開銷的,即有代價的!解析標識符實際上就是搜索execution context的作用域鏈,來匹配同名的標識符。
搜索過程從作用域鏈頭部開始,即作用域鏈中的數字越小越優先,這意味著,一個標識符所在的位置越深,它的讀寫速度越慢
函數中讀寫局部變量總是最快的,而讀寫全局變量總是最慢的
在查找過程中,如果找到,就使用這個標識符對應的變量,若沒找到,則繼續查找下一個對象,若整個搜索過程都沒有找到匹配的對象,那么這個標識符將被視為未定義的
正是這個搜索過程影響了性能
在沒有優化JavaScript引擎的瀏覽器中,盡可能使用局部變量,一個好的經驗法則就是:如果某個跨作用域的值在函數中被引用一次以上,那么就把它存儲在局部變量里。
我們來看一個例子:
function initUI(){ var bd = document.body, links = document.getElementsByTagName("a"), i = 0, len = links.length; while(i我們看到上面這個函數用了三次document對象,而很不巧,他又是個全局變量,搜索document需要遍歷整個作用域鏈,那么現在有一種解決方案來減少對性能的影響:先將全局變量的引用存儲在一個局部變量里面,然后用這個局部變量來替代全局變量;
那么上述代碼可以重寫為:function initUI(){ var doc = document, bd = doc.body, links = doc.getElementsByTagName("a"), i = 0, len = links.length; while(i我們將訪問document的次數由三次變成了一次,如果這個訪問次數足夠大的話,那么我們的性能將得到極大的改善!
改變作用域鏈
學到這里,我認為改善標識符的解析性能可以從提高解析速度和減少使用次數兩方面入手,前者通過優化JavaScript引擎來進行,后者我們在編程過程中可以進行實踐,兩者的前提都是搜索能夠正確進行!一般來說,一個execution context的作用域鏈是不會被改變的,但是在JavaScript中有兩個語句是可以在執行時臨時改變作用域鏈的,為動態作用域。
NO.1 With語句
With語句用來在作用域鏈中新創建一個變量對象,這個可變對象包含了參數指定的對象的所有屬性。先看看With在編程中怎么使用:function initUI(){ with (document){ var bd = body, links = getElementsByTagName("a"), i = 0, len = links.length; while(i從代碼中可以很直觀看到,它也只在全局對象中執行一次搜索,從而避免了多次書寫document,但是這樣會更加高效嗎?
我們來看看執行with語句時,作用域鏈中發生了什么:當執行with語句時,它的execution context被臨時改變了,一個新的對量對象被創建,它包含了參數指定的對象的所有屬性,并且這個對象被推入了作用域鏈的首位;
在上面的例子中,通過把document對象傳遞給with語句,一個包含了document對象所有屬性的新的可變變量被置于作用域的頭部,這樣就出現了一個問題:我訪問document對象的屬性非常快,但是當我想訪問活動對象(也就是局部變量)或者全局對象的屬性時,我的解析標識符速度反而降低了,所以,在減少全局對象屬性這方面的性能優化,將document儲存在一個局部變量中比用with語句改變作用域鏈更加可靠!NO.2 try-catch語句
try-catch語句中的catch字句也具有臨時改變作用域鏈的效果。
當try代碼塊中發生錯誤,執行過程會自動跳轉到catch字句,然后將異常對象推入一個變量對象并置于作用域的首位,也就是說,在catch代碼塊內部,函數所有的局部變量都會放在第二個作用域鏈對象中,但是,一旦catch代碼塊執行完畢,作用域鏈就會返回到之前的狀態。try-catch 語句不應該被用來解決JavaScript錯誤,如果某個錯誤重現率很高,最好是盡快修復。其實作用域鏈的改變是發生在catch代碼塊執行的過程中,那么我們如果在catch代碼塊內沒有對局部變量和全局變量的訪問,就可以使catch字句對性能的影響最小化!
閉包的作用域
這種思想的一種實現方法就是將錯誤委托給一個函數來處理!閉包是JavaScript最強大的特性之一,它允許函數訪問局部作用域之外的數據,但是閉包在使用過程種可能會導致性能問題。
我們先來看一個閉包的例子:
function assignEvents(){ var id = "xdi9592"; document.getElementById("btn").onclick = function(){ saveDocument(id); }; }assignEvents()函數給一個DOM元素設置事件處理函數,這個事件處理函數就是一個閉包,它在assignEvents()執行時創建,并且可以訪問所屬作用域的id變量。
如圖所示,當assignEvents()函數執行時,一個包含了變量id以及其他數據的活動對象被創建,這個活動對象成為execution context作用域鏈中的第一個對象,緊接著就是全局對象,然后閉包被創建,并且它的Scope屬性被初始化為這些對象。
至此,出現了第一個問題:內存問題。
一般來說,一個函數執行完了之后,函數作用域鏈中的活動對象會隨著execution context一起被銷毀,但是引入了閉包之后,由于引用仍然存在于閉包的Scope屬性中,所以此時活動對象沒法被銷毀,這意味著腳本中閉包與非閉包函數相比,需要更多的內存開銷。然后在閉包代碼執行時,又會創建一個閉包的execution context,它的作用域鏈與自身Scope中所引用的兩個相同的作用域鏈對象一起被初始化,然后創建一個閉包的活動對象,并且放在首位;
可以看到閉包內代碼所用的id & savaDocument,他們的位置分列2,3,就里就是我們在使用閉包過程中所需要關注的性能點:在頻繁地訪問跨作用域的標識符的時候,每次訪問都會帶來性能損失。
最后劃一下重點:將常用的跨作用域變量存儲到局部變量中,然后直接通過局部變量來訪問,是一個可行的方法。
--END--
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/87997.html
摘要:函數式編程術語大全函數式編程有許多優點,它也越來越流行了。然而,每個編程范式都有自己獨特的術語,函數式編程也不例外。作用域有兩種類似全局作用域和局部作用域。目前最重要的應用場景之一,就是在的握手階段,客戶端服務端利用算法交換對稱密鑰。 1、JavaScript 函數式編程術語大全 函數式編程(FP)有許多優點,它也越來越流行了。然而,每個編程范式都有自己獨特的術語,函數式編程也不例外。...
摘要:函數式編程術語大全函數式編程有許多優點,它也越來越流行了。然而,每個編程范式都有自己獨特的術語,函數式編程也不例外。作用域有兩種類似全局作用域和局部作用域。目前最重要的應用場景之一,就是在的握手階段,客戶端服務端利用算法交換對稱密鑰。 1、JavaScript 函數式編程術語大全 函數式編程(FP)有許多優點,它也越來越流行了。然而,每個編程范式都有自己獨特的術語,函數式編程也不例外。...
摘要:這是因為我們訪問了數組中不存在的數組元素它超過了最后一個實際分配到內存的數組元素字節,并且有可能會讀取或者覆寫的位。包含個元素的新數組由和數組元素所組成中的內存使用中使用分配的內存主要指的是內存讀寫。 原文請查閱這里,本文有進行刪減,文后增了些經驗總結。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第三章。 我們將會討論日常使用中另一個被開發...
摘要:查詢是在作用域鏈中,一級級的往上查找該變量的引用。作用域和作用域鏈作用域的概念,應該兩張圖幾句話就能解釋吧。這個建筑代表程序中的嵌套作用域鏈。一層嵌一層的作用域形成了作用域鏈,變量在作用域鏈中的函數內得到了自己的定義。 javascript作用域和閉包之我見 看了《你不知道的JavaScript(上卷)》的第一部分——作用域和閉包,感受頗深,遂寫一篇讀書筆記加深印象。路過的大牛歡迎指點...
閱讀 3768·2021-08-30 09:47
閱讀 3690·2019-08-30 15:56
閱讀 677·2019-08-30 14:18
閱讀 698·2019-08-29 16:17
閱讀 2065·2019-08-29 11:07
閱讀 642·2019-08-26 13:53
閱讀 3443·2019-08-26 10:26
閱讀 2491·2019-08-23 18:30