摘要:所以,當在函數中使用全局變量的時候,所產生的代價是最大的,因為全局對象一直處于作用域鏈的最末位置,讀取局部變量是最快的。
什么是作用域
靜態作用域在編程語言中,作用域控制著變量與參數的可見性及生命周期,它能減少名稱沖突,而且提供了自動內存管理(javascript 語言精粹)
再者,js不像其他的編程語言一樣,擁有著塊級作用域,就像下面一段代碼。
function afunction(){ var a = "sf"; console.log(b); console.log(c); var b = function(){ console.log("這是b中的內容"); } function c(){ console.log("這是c中的內容"); } (function d(){ console.log("這是d中的內容"); })() }
實用var聲明的變量和函數聲明將會進行聲明提前在afunction函數的執行環境中,故上述代碼相當于以下的代碼,在一個變量聲明提前的時候,其值為undefined,而函數聲明則是將函數體作為值。
function afunction(){ var a; var b; function c(){ console.log("這是c中的內容"); } a = "sf"; console.log(b); console.log(c); b = function(){ console.log("這是b中的內容"); } (function d(){ console.log("這是d中的內容"); })() }全局作用域與局部作用域
將上述的代碼稍作改動如下
var outer = "outer"; function afunction(){ function c(){ console.log("這是c中的內容"); } a = "sf"; console.log(outer); }
我們在afunction函數的外部定義了outer變量,假設這段代碼運行在瀏覽器上,那么變量提前的過程中outer變量被聲明在了window作用域上,也就是瀏覽器中的全局作用域上,而函數中的變量則在函數運行時被聲明在了afunction作用域上,這個就是局部作用域,在這個局部作用域中,outer變量被訪問到了,這種跨作用域的讀取變量的形式就是根據作用域鏈來實現的。
什么是作用域鏈在js中,函數也是對象,函數與其他的對象一樣,擁有可以訪問的屬性,[[Scope]]就是其中的一個屬性,它指明了哪些東西可以被函數訪問。
考慮下面的函數
function add(a,b){ var sum = a + b; return sum; }
當函數add創建時候,add的[[Scope]]屬性會指向作用域鏈對象,該對象的初始位置指向全局對象,如下圖所示。
var t = add(1,2);
上述語句執行了add函數,對于函數的每一次執行,瀏覽器會創建一個執行環境的內部對象,一個執行環境定義了一個函數執行時的環境。函數的每次執行時對應的執行環境都說唯一的。每一個執行環境都有自己的作用域鏈,此對象的局部變量,this, arguments等組成活動對象,插入在作用域鏈對象的最前端,也就是圖中所示的0號位置,當運行結束后,執行環境和活動對象都將銷毀。
函數的執行過程中,每遇到一個變量,都會從作用域鏈的頂部,也就是0號位置查找該變量,如果查找成功則返回,查找失敗則按照作用域鏈查找下一個位置的對象,該例子中也就是1號位置的全局對象。
如上面所討論的那樣,每一次遇到讀取變量的時候,都意味著一次搜索作用域鏈的過程,如果搜索的作用域鏈的層次越多的話,將嚴重影響性能。
所以,當在函數中使用全局變量的時候,所產生的代價是最大的,因為全局對象一直處于作用域鏈的最末位置,讀取局部變量是最快的。
所以,一個提高效率的規則是盡可能的使用局部變量。如下面的代碼所示。
function demo(){ var d = document, bd = d.body, div = d.getElementsByTagName("div"); d.getElementById("id1").innerHTML = "aaa"; //(許多使用document,body和div的操作) }
上面的代碼首先將全局的document對象保存在了局部變量d中,這樣當下次頻繁的使用document對象時,僅僅需要從局部變量中即可獲得。
動態作用域js中實用的是靜態作用域,作用域鏈一般不可改變,但是with和try-catch可以改變作用域鏈,發生在函數的執行時候
with語句function withTest(){ var foo = "sf"; var obj = {foo:"abc"}; with(obj){ function f(){ alert(foo); } (function(){ alert(foo); })(); f(); } } withTest();
在函數聲明的時候,作用域鏈沒有考慮with的情況,當函數執行的時候,動態生成with的對象,推入在作用域鏈的首位,這就意味著函數的局部變量存在作用域鏈的第二個位置,訪問的代價提高了,雖然訪問with對象的代價降低了,完全可以將with對象保存在局部變量中,故with語句不推薦使用。
try-catch語句try{ anErrorFunction(); }catch(e){ errorHandler(e); }
由于catch語句中只有一條語句,將error傳遞給errorHandler函數,所以運行時作用域鏈的改變不會影響性能。
什么是閉包閉包是允許函數訪問局部作用域之外的數據。即使外部函數已經退出,外部函數的變量仍可以被內部函數訪問到。
因此閉包的實現需要三個條件:
內部函數實用了外部函數的變量
外部函數已經退出
內部函數可以訪問
function a(){ var x = 0; return function(y){ x = x + y; return x; } } var b = a(); b(1);
上述代碼在執行的時候,b得到的是閉包對象的引用,雖然a執行完畢后,但是a的活動對象由于閉包的存在并沒有被銷毀,在執行b(1)的時候,仍然訪問到了x變量,并將其加1,若在此執行b(1),則x是2,因為閉包的引用b并沒有消除。
一個經典的閉包的實例//ul下面有3個li,實現點擊每個li,彈出li的序號 for(var i = 0,len = lis.length;i < len; i++){ lis[i].onclick = function(i){ return function(){ alert(i); } }(i); }
在這里,沒有把閉包直接給onclick事件,而是先定義了一個自執行函數,該函數中包含著閉包的函數,i的值被保存在自執行的函數中,當閉包函數執行后,會從自執行函數中查找i,達到“保存”變量的目的。
注:匿名函數中的this指向的是window,故在匿名閉包函數使用父函數的this指針時,需要將其存儲下來,如 var that = this;
閉包的作用模塊化代碼
私有成員
避免全局變量的污染
希望一個變量長期駐扎在內存中
使用閉包所造成的性能問題如上面的描述,當執行閉包函數后,父函數所保留下來的活動對象并不是在閉包函數的作用域鏈的首位(首位存放的是閉包的活動對象),當頻繁的訪問跨作用域的標識符時候,每次都會造成性能的損失,我們仍然可以將常用的跨作用域變量存儲在局部變量中,直接訪問該局部變量
實用閉包所造成的內存泄露問題(IE9以下)IE9及以下的版本使用的是引用計數的內存回收機制,當引用計數為0的時候將會回收,但有一種循環引用的情況
window.onload = function(){ var el = document.getElementById("id"); el.onclick = function(){ alert(el.id); } }
這段代碼執行時,將匿名函數對象賦值給el的onclick屬性;然后匿名函數內部又引用了el對象,存在循環引用,所以不能被回收;
(javascript 高級程序設計(第三版))
解決方法:
window.onload = function(){ var el = document.getElementById("id"); var id = el.id; //解除了循環引用 el.onclick = function(){ alert(id); //并沒有出現循環引用 } el = null; // 將閉包引用的外部活動對象清除 }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86108.html
摘要:本期推薦文章從作用域鏈談閉包,由于微信不能訪問外鏈,點擊閱讀原文就可以啦。推薦理由這是一篇譯文,深入淺出圖解作用域鏈,一步步深入介紹閉包。作用域鏈的頂端是全局對象,在全局環境中定義的變量就會綁定到全局對象中。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周開始前端進階的第二期,本周的主題是作用域閉包,今天是第6天。 本...
摘要:前言這段時間一直在消化作用域鏈和閉包的相關知識。而作用域鏈則是這套規則這套規則的具體運行。是變量對象的縮寫那這樣放有什么好處呢我們知道作用域鏈保證了當前執行環境對符合訪問權限的變量和函數的有序訪問。 前言:這段時間一直在消化作用域鏈和閉包的相關知識。之前看《JS高程》和一些技術博客,對于這些概念的論述多多少少不太清楚或者不太完整,包括一些大神的技術文章。這也給我的學習上造成了一些困惑,...
摘要:在此例中,在匿名函數被返回后,它的作用域鏈初始化為包含函數的活動對象和全局變量對象。函數在執行完畢后,其活動對象也不會被銷毀,因為匿名函數的作用域鏈仍然在引用這個活動對象,結果就是只是的執行環境的作用域鏈會被銷毀,其活動對象會留在內存中。 寫在前面 注:這個系列是本人對js知識的一些梳理,其中不少內容來自書籍:Javascript高級程序設計第三版和JavaScript權威指南第六版,...
摘要:變量對象也是有父作用域的。作用域鏈的頂端是全局對象。當函數被調用的時候,作用域鏈就會包含多個作用域對象。當函數要訪問時,沒有找到,于是沿著作用域鏈向上查找,在的作用域找到了對應的標示符,就會修改的值。 一、概要 對于閉包的定義(紅寶書P178):閉包就是指有權訪問另外一個函數的作用域中的變量的函數。 關鍵點: 1、閉包是一個函數 2、能夠訪問另外一個函數作用域中的變量 二、閉包特性 對...
閱讀 4078·2021-10-08 10:04
閱讀 3061·2021-08-11 11:20
閱讀 2731·2021-07-25 21:37
閱讀 2681·2019-08-30 12:44
閱讀 2306·2019-08-30 11:12
閱讀 1314·2019-08-26 13:45
閱讀 2338·2019-08-26 11:53
閱讀 3057·2019-08-26 11:32