做用域鏈與閉包

  讀了這篇博文地址後,對做用域和閉包終於有一了些瞭解。以前看各類文章,讓我覺得閉包是由於內部引用變量,致使變量沒法在外部訪問,而經過內部函數能夠被外部訪問致使被引用的變量能夠間接訪問。如今看來,這隻能說是閉包的一種外在表現,跟閉包自己沒有任何關係的(在You don't know JS中理解與本文不一致,書中認爲閉包其實是做用域,因爲內部函數引用致使外部函數在棧中移出後其做用域仍可被訪問,這個做用域被稱之爲閉包。這樣理解的話本文查看的應該是chrome的一種實現,即無論有沒有閉包,都會生成一個叫closure的變量,這個變量就是做用域,只是chrome進行了優化,便於垃圾回收,移除了部分非使用的變量。)。html

  下面總結一下做用域鏈和閉包。git

1. 執行至閉包以前的整個過程

  更爲詳細的可參見那篇博文,這裏只是簡單總結下。在瀏覽器執行解析代碼時,分爲編譯和代碼執行兩個部分。編譯階段負責對語法解析,做用域鏈分析,語法優化。代碼執行階段中,先爲函數建立上下文,而後執行代碼。建立函數上下文時,要進行:建立變量對象、建立做用域鏈、肯定this的指向。github

  變量對象(VO)建立過程:創建arguments對象,並將數據掛載到到arguments上 -> 針對函數內部聲明的函數、變量,先進行掛載聲明的函數,而後掛載聲明的變量(若是名稱衝突則不覆蓋) 面試

       變量對象在函數執行以前是不可訪問的,執行代碼時,變量對象轉變爲活動對象,全部變量獲取均從活動對象中取得,若是沒有,則會拋異常。chrome

2. 做用域鏈建立與閉包

       做用域鏈的生成規則是在編譯階段就肯定了的。當函數建立上下文時,除建立變量對象外,還會產生一個閉包(closure)。這個閉包會在子函數中被加入到做用域鏈中。某個函數的做用域鏈是這個樣子的:瀏覽器

       當前函數上下文的活動變量 - 上層閉包 - 上上層閉包 .....閉包

 

       所以閉包的概念應該是這樣的:在某個函數內聲明瞭另一個函數(子函數),子函數(或子函數的子函數)引用了當前函數聲明的一些變量或函數,這些變量或函數因爲在子函數(或子函數的子函數)的做用域中會被訪問到,所以這些變量或函數的訪問地址被保存到了一個對象中,這個對象就是一個閉包。函數

  閉包的實際做用應該爲子函數的函數做用域鏈提供一個可訪問的對象,即便當前的函數已經從棧中移出,因爲子函數中已經將該閉包加到了做用域鏈中(子函數的子函數可繼承使用子函數的全部閉包),子函數全部的變量或函數都是從做用域鏈中獲取的,所以子函數從表現上看仍舊能夠訪問在父函數或父父函數中聲明的變量(實際上訪問的是做用域鏈上的變量)。優化

  這樣,函數的做用域鏈的規則應該是這樣的:this

       當前函數上下文的活動變量 - 上層函數的閉包 - ...上層函數擁有的閉包

這樣一看就清晰明瞭了吧。

 

3. 理論驗證

  爲驗證這個推論,創建一個帶有四層函數的腳本。其中第一層中聲明變量a,被second、four引用, cuse變量僅被four引用,unuse未被任何子函數引用。

(function () { var a = 1
  var cuse = 2
  var unuse = 10 function second () { var b = a function third() { var d = 'xx' function four () { var c = b c += a c += cuse console.log(c) } four() console.log(d) } third() } second() var final = unuse + 1
  return final })()

 

  1. 執行到second時,看到匿名函數的closure中只有a和cuse,但second中未引用匿名函數聲明的任何一個變量,說明這個closure應該是在編譯階段就肯定了哪些變量會被加入到closure中。

 

  2. 執行到third函數時,closure中出現了匿名函數的closure和second的closure。

  3. 執行到four時,出現了匿名函數的closure和second的closure。因爲third中聲明的變量沒有被子函數引用,它未產生closure,在four中也就沒有出現closure。

 

  從上面的結果看出:編譯階段已經確認了哪些變量會被加入到closure中,這個closure會被加入到全部子函數的做用域鏈中。換種理解就是子函數的使用域鏈中closure構成是由上層函數生成的closure以及它所擁有的全部closure組成的。

 

  當使用eval函數時,會有更明顯的對比(見下圖):使用eval後,父函數聲明的全部變量和函數都被加入到closure中。這是由於eval執行具備不肯定性,爲保證變量能夠訪問,只能將全部變量加入closure中,避免代碼執行過程當中找不到變量的問題(arguments之因此被加入到closure中,是由於新的箭頭函數致使子函數會使用父函數的arguments對象)。

 

4. 總結

  閉包的概念仍是比較難以理解的,要從瀏覽器解釋執行代碼的角度理解纔會更深入(貌似這不是寫代碼的考慮的,不知道爲啥面試全會問)。感謝·這波能反殺·博主的詳細講解,一對比文章質量,我寫的估計只有理解的人能快速看明白,有點像寫論文,但寫的匆忙質量差,仍是要多練習。繼續加油吧~~

 

附: 博文地址 https://yangbo5207.github.io/wutongluo/ji-chu-jin-jie-xi-lie/si-3001-zuo-yong-yu-lian-yu-bi-bao.html

相關文章
相關標籤/搜索