亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

深入理解Javascript中的作用域鏈和閉包

2019-11-19 16:44:32
字體:
來源:轉載
供稿:網友

首先我們回顧下之前一篇關于介紹數組遍歷的文章:

請先看上一篇中提到的for循環代碼:

var array = [];array.length = 10000000;//(一千萬)for(var i=0,length=array.length;i<length;i++){ array[i] = 'hi';}var t1 = +new Date();for(var i=0,length=array.length;i<length;i++){}var t2 = +new Date();console.log(t2-t1);//以下是連續5次的運行時間//168+158+170+159+165 = 820(ms)

我們再看下面一段代碼, 測試環境為 chrome 52.0.2743.116 (64-bit):

var t1 = +new Date();(function(){//閉包 for(var i=0,length=array.length;i<length;i++){ //array.push(i); }})();var t2 = +new Date();console.log(t2-t1);//以下是連續5次的運行時間://8+6+8+7+6 = 35(ms)

計算一下: 820/35 = 23 效率提升大致20倍. 實際上, 在 Firefox 及 Safari 對 for有做底層優化的情況下, 仍然有4~6倍的性能提升. 這是為什么呢?

我們注意到兩段代碼最大的區別就是, 第二段代碼使用了匿名函數包裹for循環. 我們將在后面講到, 請耐心閱讀.

作用域

所謂作用域, 指的是, 變量在聲明它們的函數體以及這個函數體嵌套的任意函數體內都是有定義的.

js中只有函數作用域

眾所周知, JS中并沒有塊作用域, 只有函數作用域. 如下:

for(var i=0;i<10;i++){ ;}console.log(i);//10function f(){ var a = 123;}f();console.log(a);//a is not defined

因此 js 中只有一種局部作用域, 即函數作用域.

使用 var 聲明變量

通常我們知道, js 作為一種弱類型語言, 聲明一個變量只需要var保留字, 如果在函數中不使用 var 聲明變量, 該變量將提升為全局變量, 進而脫離函數作用域, 如下:

function f(){ b = 123;}f();console.log(b);//123

此時相對于前面使用var聲明的 a 變量, b 變量被提升為全局變量, 在函數作用域外依然可以訪問.

既然在函數作用域內不使用 var 聲明變量, 會將變量提升為全局變量, 那么在全局下, 不使用var, 會怎么樣呢?

//全局下不使用var聲明,該變量依然是全局變量c = "hello scope";console.log(c);//hello scopeconsole.log(window.c);//hello scope//查看c變量的屬性console.log(Object.getOwnPropertyDescriptor(window, 'c'));//Object {value: "hello scope", writable: true, enumerable: true, configurable: true} ,此時c變量可賦值,可列舉,可配置//試著刪除c變量delete c;//true 表示c變量被成功刪除console.log(c);//c is not definedconsole.log(window.c);//undefined//使用var聲明后再刪除d變量var d = 1;console.log(Object.getOwnPropertyDescriptor(window, 'd'));//Object {value: 1, writable: true, enumerable: true, configurable: false} ,此時d變量可賦值,可列舉,但不可配置delete d;//false 表示d變量刪除失敗console.log(d);//1 console.log(window.d);//1

綜上, 有如下規律:

  • 不使用var保留字聲明變量, 變量提升為全局變量, 而不論變量處于哪種作用域;
  • 如果不使用var聲明, 該變量便可配置, 即可被 delete 保留字刪除, 刪除后該變量便不可訪問; 如果使用var聲明, 該變量便不可配置, 即不能被 delete 保留字刪除;
  • 只要是全局變量都可以直接訪問, 也可使用 “window.變量名” 來訪問, 不管該變量是不是通過var來聲明的;

JS中的作用域鏈

函數對象和其它對象一樣,擁有可以通過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內部屬性。其中一個內部屬性是[[Scope]],由ECMA-262標準第三版定義,該內部屬性包含了函數被創建的作用域中對象的集合,這個集合被稱為函數的作用域鏈,它決定了哪些數據能被函數訪問。

我們先看一個栗子:

var e = "hello";function f(){ e = "scope chain"; var g = = "good";}

以上作用域鏈的圖如下所示:

函數執行時, 在函數 f 內部會生成一個 active object 和 scope chain. JavaScript引擎內部對象會放入 active object中, 外部的 e 變量處于scope chain的第二層, index=1, 而內部的g變量處于scope chain的頂層, index=0, 因此訪問g變量總比訪問e變量來的快些.

閉包

聊到作用域, 就不得不說閉包, 那么, 什么是閉包?

“官方”的解釋是:閉包是一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。

這是什么意思呢, 簡單來說就是:

  • 函數執行時返回內部私有函數, 或者通過其他方式將內部私有函數保留在外(比如說通過將其內部私有函數的引用賦值外部變量), 從而阻止該函數內部作用域等被執行引擎回收.
  • 在函數外部通過訪問暴露在外的函數內部私有函數, 從而具有訪問函數內部私有作用域的效果, 就是閉包.

ES6之前, 通常我們實現的模塊就是利用了閉包. 閉包依賴的結構有個鮮明的特點, 即: 一個函數在詞法作用域之外執行. 如下, f2是閉包的關鍵, 它的詞法作用域便是函數f的內部私有作用域, 且它在f的作用域外部執行.

var h = 1;function f(){ var i = 2; return function f2(){ var j = 3 + i + h; console.log(j); }}var ff = f();ff();//6

由于定義時 f2 處于 f 的內部, 因此 f2 內可以訪問到 f 的內部私有作用域, 這樣通過返回 f2 就能保證在 f 函數外部也能訪問到 i 變量.

當f2執行時, 變量 j 處于scope chain的 index0的位置上, 變量 i 和變量 h 分別處于 scope chain 的 index1 index2 的位置上. 因此 j 的賦值過程其實就是沿著 scope chain 第二層 第三層 依次找到 i 和 h 的值, 然后將它們和3一起求和, 最終賦值給 j .

瀏覽器沿著 scope chain 尋找變量總是需要耗費CPU時間, 越是 scope chain 的 外層(或者離f2越遠的變量), 瀏覽器查找起來越是需要時間, 因為 scope chain 需要歷經更多次遍歷. 因此全局變量(window)總是需要最多的訪問時間.

閉包內的微觀世界

  如果要更加深入的了解閉包以及函數 f 和嵌套函數 f2 的關系,我們需要引入另外幾個概念:函數的執行環境(excution context)、活動對象(call object)、作用域(scope)、作用域鏈(scope chain)。以函數a從定義到執行的過程為例闡述這幾個概念。

  • 當定義函數 f 的時候, js解釋器會將函數a的作用域鏈(scope chain)設置為定義 f 時 a 所在的”環境”, 如果 f 是一個全局函數,則scope chain中只有window對象。
  • 當執行函數 f 的時候, f 會進入相應的執行環境(excution context).
  • 在創建執行環境的過程中, 首先會為 f 添加一個scope屬性, 即a的作用域, 其值就為第1步中的scope chain. 即a.scope=f 的作用域鏈.
  • 然后執行環境會創建一個活動對象(call object). 活動對象也是一個擁有屬性的對象, 但它不具有原型而且不能通過JavaScript代碼直接訪問. 創建完活動對象后, 把活動對象添加到 f 的作用域鏈的最頂端. 此時a的作用域鏈包含了兩個對象: f 的活動對象和window對象.
  • 下一步是在活動對象上添加一個arguments屬性, 它保存著調用函數 f 時所傳遞的參數.
  • 最后把所有函數 f 的形參和內部的函數 f2 的引用也添加到 f 的活動對象上. 在這一步中, 完成了函數 f2 的定義, 因此如同第3步, 函數 f2 的作用域鏈被設置為 f2 所被定義的環境, 即 f 的作用域.

到此, 整個函數 f 從定義到執行的步驟就完成了. 此時 f 返回函數 f2 的引用給 ff, 又函數 f2 的作用域鏈包含了對函數 f 的活動對象的引用, 也就是說 f2 可以訪問到 f 中定義的所有變量和函數. 函數 f2 被 ff 引用, 函數 f2又依賴函數 f , 因此函數 f 在返回后不會被GC回收.

當函數 f2 執行的時候亦會像以上步驟一樣. 因此, 執行時 f2 的作用域鏈包含了3個對象: f2 的活動對象、f 的活動對象和window對象, 如下圖所示:

如圖所示, 當在函數 f2 中訪問一個變量的時候, 搜索順序是:

  • 先搜索自身的活動對象, 如果存在則返回, 如果不存在將繼續搜索函數 f 的活動對象, 依次查找, 直到找到為止.
  • 如果函數 f2 存在prototype原型對象, 則在查找完自身的活動對象后先查找自身的原型對象, 再繼續查找. 這就是Javascript中的變量查找機制.
  • 如果整個作用域鏈上都無法找到, 則返回undefined.

小結, 本段中提到了兩個重要的詞語: 函數的定義與執行. 文中提到函數的作用域是在定義函數時候就已經確定, 而不是在執行的時候確定(參看步驟1和3).用一段代碼來說明這個問題:

function f(x) {  var g = function () { return x; } return g;}var h = f(1);alert(h());

這段代碼中變量h指向了f中的那個匿名函數(由g返回).

  • 假設函數h的作用域是在執行alert(h())確定的, 那么此時h的作用域鏈是: h的活動對象->alert的活動對象->window對象.
  • 假設函數h的作用域是在定義時確定的, 就是說h指向的那個匿名函數在定義的時候就已經確定了作用域. 那么在執行的時候, h的作用域鏈為: h的活動對象->f的活動對象->window對象.

如果第一種假設成立, 那輸出值就是undefined; 如果第二種假設成立, 輸出值則為1。

運行結果證明了第2個假設是正確的,說明函數的作用域確實是在定義這個函數的時候就已經確定了.

閉包有可能導致IE瀏覽器內存泄漏

先看一個栗子:

function f(){ var div = document.createElement("div");  div.onclick = function(){ return false; }}

上述div的click事件就是一個閉包, 由于該閉包的存在使得 f 函數內部的 div 變量對DOM元素的引用將一直存在.

而早期IE瀏覽器( IE9之前 ) js 對象和 DOM 對象使用不同的垃圾收集方法, DOM對象使用計數垃圾回收機制, 只要匿名函數( 比如說onclick事件 )存在, DOM對象的引用便至少為1,因此它所占用的內存就永遠不會被銷毀.

有趣的是,不同的IE版本將導致不同的現象:

  • 如果是IE 6, 內存泄漏,直到關閉IE進程為止;
  • 如果是IE 7,內存泄漏, 直到離開當前頁面為止;
  • 如果是IE 8, GC回收器回收他們的內存,無論當前是不是compatibility模式.

總結一下, 閉包的優點: 共享函數作用域, 便于開放一些接口或變量供外部使用;

注意事項: 由于閉包可能會使得函數中變量被長期保存在內存中, 從而大量消耗內存, 影響頁面性能, 因此不能濫用, 并且在IE瀏覽中可能導致內存泄露. 解決方法是,在退出函數之前,將不使用的局部變量全部刪除.

for循環問題分析

我們再來看看開篇的for循環問題, 增加匿名函數后, for循環內部的變量便處于匿名函數的局部作用域下, 此時訪問 length 屬性, 或者訪問 i 屬性, 都只需要在匿名函數作用域內查找即可, 因此查詢效率大大提升(測試數據發現提升有兩百多倍).

使用匿名函數后, 不止是作用域查詢更快, 作用域內的變量還與外部隔離, 避免了像 i , length 這樣的變量對后續代碼產生影響. 可謂一舉兩得.

踩個作用域的坑

下面我們來踩一個作用域經典的坑.

var div = document.getElementsByTagName("div");for(var i=0,len=div.length;i<len;i++){ div[i].onclick = function(){ console.log(i); }}

上述代碼的本意是每次點擊div, 打印div的索引, 實際上打印的卻是 len 的值. 我們來分析下原因.

點擊div時, 將會執行 console.log(i) 語句, 顯然 i 變量不在 click 事件的局部作用域內, 瀏覽器將沿著 scope chain 尋找 i 變量, 在 index1 的地方, 即 for循環開始的地方, 此處定義了一個 i 變量, 又 js 沒有塊作用域, 故 i 變量并不會在 for循環塊執行完成后被銷毀,又 i的最后一次自加使得 i = len, 于是瀏覽器在scope chain index=1索引的地方停下來了, 返回了i的值, 即len的值.

為了解決這個問題, 我們將根據癥結, 對癥下藥, 從作用域入手, 改變click事件的局部作用域, 如下:

var div = document.getElementsByTagName("div");for(var i=0,len=div.length;i<len;i++){ (function(n){ div[n].onclick = function(){  console.log(n); } })(i);}

由于 click 事件被閉包包裹, 并且閉包自執行, 因此閉包內 n 變量的值每次都不一樣, 點擊div時, 瀏覽器將沿著 scope chain 尋找 n 變量, 最終會找到閉包內的 n 變量, 并且打印出div 的索引.

this作用域

前面我們學習了作用域鏈, 閉包等基礎知識, 下面我們來聊聊神秘莫測的this作用域.

熟悉OOP的開發人員都知道, this是對象實例的引用, 始終指向對象實例. 然而 js 的世界里, this隨著它的執行環境改變而改變, 并且它總是指向它所在方法的對象. 如下,

function f(){ alert(this);}var o = {};o.func = f;f();//[object Window]o.func();//[object Object]console.log(f===window.f);//true

當f單獨執行時, 其內部this指向window對象, 但是當f成為o對象的屬性func時, this指向的是o對象, 又f === window.f, 故它們實際上指向的都是this所在方法的對象.

下面我們來應用下

Array.prototype.slice.call([1,2,3],1);//[2,3],正確用法Array.prototype.slice([1,2,3],1);//[], 錯誤用法,此時slice內部this仍然指向Array.prototypevar slice = Array.prototype.slice;slice([1,2,3],1);//Uncaught TypeError: Array.prototype.slice called on null or undefined//此時slice內部this指向的是window對象,離開了原來的Array.prototype對象作用域,故報錯~~

總結下, this的使用只需要注意一點:

this 總是指向它所在方法的對象.

with語句

聊到作用域鏈就不得不說with語句了, with語句可以用來臨時改變作用域, 將語句中的對象添加到作用域的頂部.

語法: with (expression){statement}

例如:

var k = {name:"daicy"};with(k){ console.log(name);//daicy}console.log(name);//undefined

with 語句用于對象 k, 作用域第一層為 k 對象內部作用域, 故能直接打印出 name 的值, 在with之外的語句不受此影響.
再看一個栗子:

var l = [1,2,3];with(l) { console.log(map(function(i){ return i*i; }));//[1,4,9]}

在這個例子中,with 語句用于數組,所以在調用 map() 方法時,解釋程序將檢查該方法是否是本地函數。如果不是,它將檢查偽對象 l,看它是否為該對象的方法, 又map是Array對象的方法, 數組l繼承了該方法, 故能正確執行.

注意: with語句容易引起歧義, 由于需要強制改變作用域鏈, 它將帶來更多的cpu消耗, 建議慎用 with 語句.

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對武林網的支持。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲国产中文字幕在线观看| 国产在线高清精品| 两个人的视频www国产精品| 成人两性免费视频| 欧美激情乱人伦| 亚洲精品自拍第一页| 精品久久久国产精品999| 国产成人一区二区三区| 97精品欧美一区二区三区| 亚洲欧美国产va在线影院| 国产精品网站视频| 欧美孕妇性xx| 在线观看日韩www视频免费| 国产精品一区二区av影院萌芽| 亚洲成人动漫在线播放| 韩国国内大量揄拍精品视频| 久久亚洲春色中文字幕| 午夜精品久久久久久久99黑人| 日韩一二三在线视频播| 亚洲最新中文字幕| 日韩国产欧美精品一区二区三区| 日韩中文字幕国产精品| 在线观看国产成人av片| 在线精品播放av| 亚洲精品国产精品自产a区红杏吧| 久久综合久久八八| 国产97在线视频| 伊人精品在线观看| 欧美中文字幕在线| 久久久精品久久| 91美女福利视频高清| 青青青国产精品一区二区| 亚洲国产精品久久91精品| 欧美国产亚洲视频| 欧美成人精品xxx| 国产精品一二三视频| 色无极亚洲影院| 最新中文字幕亚洲| 在线观看欧美成人| 69av在线播放| 精品久久久久久国产| 久久国产精品久久久久久| 中文字幕国产精品久久| 日韩精品高清在线观看| 国产精彩精品视频| 亚洲成人黄色在线观看| 日韩精品免费在线视频观看| 亚洲一品av免费观看| 久久综合伊人77777| 精品国产91久久久| 国产成人免费91av在线| 国产精品成人久久久久| 亚洲欧洲免费视频| 日韩激情视频在线| 国产日韩在线精品av| 国产精品视频最多的网站| 国产精品电影一区| 亚洲第一网站免费视频| 亚洲国产美女精品久久久久∴| 国产一区二区三区久久精品| 国产午夜精品一区二区三区| 精品色蜜蜜精品视频在线观看| 美女黄色丝袜一区| 亚洲一区二区三区视频| 91在线观看欧美日韩| 午夜精品久久久久久99热| 中文字幕久热精品在线视频| 日韩精品欧美激情| 国产亚洲精品美女久久久久| 精品日本高清在线播放| 欧美天天综合色影久久精品| 成人a视频在线观看| 亚洲视频一区二区三区| 亚洲午夜激情免费视频| 最近2019中文字幕第三页视频| 中文字幕欧美日韩在线| 日韩一区在线视频| 欧美孕妇与黑人孕交| 欧美激情久久久| 亚洲国产精品一区二区久| 久久久精品视频成人| 国产视频精品免费播放| 中文字幕亚洲欧美一区二区三区| 欧美日韩一区二区在线播放| 欧美一级淫片播放口| 日韩在线免费av| 久精品免费视频| 亚洲一区美女视频在线观看免费| 中文字幕视频在线免费欧美日韩综合在线看| 日韩精品在线免费播放| 国内揄拍国内精品| 黑人巨大精品欧美一区免费视频| 日韩精品福利在线| 韩国v欧美v日本v亚洲| 日韩欧美在线国产| 亚洲已满18点击进入在线看片| 欧美日韩xxx| 欧美壮男野外gaytube| 国产精品av在线播放| 亚洲第一区中文字幕| 国产精品久久久久久av下载红粉| 成人精品视频在线| 国产精品视频大全| 精品日本美女福利在线观看| 中文字幕v亚洲ⅴv天堂| 国产精品久久久久久久久免费| 高清亚洲成在人网站天堂| 亚洲成在人线av| 欧美激情中文字幕在线| 国产精品久久一区主播| 操日韩av在线电影| 亚洲娇小xxxx欧美娇小| 亚洲第一福利网站| 亚洲综合在线播放| 国产91露脸中文字幕在线| 国产精品久久久久久久久久免费| 亚洲黄色在线看| 91极品女神在线| 91久久精品久久国产性色也91| 欧美大肥婆大肥bbbbb| 久久韩国免费视频| 亚洲综合中文字幕68页| 亚洲a一级视频| 欧美精品videos另类日本| 免费不卡在线观看av| 亚洲精品电影网在线观看| 91亚洲精品久久久| 日韩男女性生活视频| 精品一区电影国产| 欧美日韩亚洲精品一区二区三区| 亚州精品天堂中文字幕| 91亚洲精品一区二区| 国产偷国产偷亚洲清高网站| 精品亚洲男同gayvideo网站| 成人久久18免费网站图片| 97视频在线观看网址| 欧美激情精品久久久久久黑人| 久久九九免费视频| 亚洲精品久久久久久久久久久久久| 国产免费一区二区三区香蕉精| 亚洲一区二区三区成人在线视频精品| 91高潮在线观看| 国产精品电影久久久久电影网| 欧美肥老太性生活视频| 一道本无吗dⅴd在线播放一区| 亚洲欧洲午夜一线一品| 国内精品久久久久| 国产v综合v亚洲欧美久久| 在线激情影院一区| 亚洲大胆美女视频| 久久免费国产视频| 欧美激情精品久久久久久变态| 成人网在线观看| 欧美性xxxx| 精品久久久在线观看| 亚洲自拍在线观看| 午夜精品三级视频福利| 国产丝袜一区二区| 欧美国产极速在线| 欧美日韩国产一区二区| 北条麻妃一区二区三区中文字幕| 久久精视频免费在线久久完整在线看| 国产91在线播放精品91| 啊v视频在线一区二区三区|