【學習】你不知道的JavaScript + 忍者祕籍 -- 作用域 +閉包 小結

枕月娘娘萬壽無疆發表於2020-10-11

相關知識點

  1. 編譯語言,動態語言

  2. 編譯原理:分詞/詞法分析(Tokenizing/Lexing),解析/語法分析(Parsing),程式碼生成。

  3. LHS 和 RHS:左查詢和右查詢
    LHS:找到變數的容器本身;RHS:找到變數的值

  4. 引擎:負責整個JavaScript程式的編譯及執行過程
    編譯器:負責語法分析及程式碼生成
    作用域:負責收集並維護由所有宣告的識別符號(變數)所組成的一系列的查詢,並實施一套非常嚴格的規則,確定當前執行的程式碼對這些識別符號的訪問許可權。

  5. 詞法作用域 && 動態作用域:JavaScript都是詞法作用域
    詞法作用域:定義在詞法階段的作用域,詞法作用域是在寫程式碼時將比那輛和塊作用域寫在哪裡來決定的,因此當詞法分析器處理程式碼時會保持作用域不變(大部分情況下,例如with可改變作用域)。

  6. 無論函式在哪裡被呼叫,也無論它如何被呼叫,它的詞法作用域都只由函式被宣告時所處的位置決定。

  7. eval()和with()會在執行時修改或建立新的作用域,影響JavaScript引擎在編譯階段對作用域查詢進行優化。

  8. 塊級作用域:從ES3開始,try/catch結構在catch分局中具有塊級作用域;ES6之後引入了if-else for等使用{}包起來的塊級作用域

  9. 閉包: 通過閉包可以訪問建立閉包時所處環境中的全部變數。閉包為函式建立時所處的作用域中的函式和變數船艦“安全氣泡”,通過這種方式,即使建立函式時所處的作用域已經消失,但是函式仍然能夠獲得執行時所需的全部內容。

  10. 在JavaScript中,我們可以定義全域性級別,函式級別,塊級別的變數。

  11. 可以使用關鍵字var,let,const定義變數:

  • var定義距離最近的函式級變數或者全域性變數;
  • let和const定義距離最近級別(函式或者塊)的變數
  • const定義只能賦值一次的變數。
  1. JavaScript程式碼的執行分成兩個階段進行:一旦建立了新的詞法環境,就會執行第一階段。在第一階段,沒有執行程式碼,但是JavaScript引擎會訪問並註冊在當前詞法環境中所宣告的變數和函式。JavaScript在第一階段完成之後開始執行第二階段,具體如何執行取決於變數的型別(let,var,const和函式宣告)以及環境型別(全域性環境,函式環境,塊級作用域)
    具體過程如下:
  • 如果時傳經一個函式環境,那麼建立形參及函式引數的預設值。如果是非函式環境,將跳過此步驟。
  • 如果是建立全域性或函式環境,就掃描當前程式碼進行函式宣告(不會掃描其他函式的函式體),但是不會掃描函式表示式和箭頭函式。對於找到的函式宣告,將建立函式,並繫結到當前環境與函式名相同的識別符號上。若該識別符號已經存在,那麼該識別符號的值將被重寫。如果是塊級作用域,將跳過此步驟。
  • 掃描當前程式碼進行變數宣告,在函式或者全域性環境中,找到所有當前函式以及其他函式之外通過var宣告的變數,並找到所有在其他函式或者程式碼塊之外通過let或const定義的變數。在塊級作用域中,僅查詢當前塊中通過let和const定義的變數。對於所查詢的變數,若該識別符號不存在,進行註冊並將其初始化為undefined。若該識別符號已經存在,將保留其值。
//舉例說明
console.log(typeof fun === 'function') //true: 函式宣告提前第二步,第三步變數宣告提前的時候因為發現識別符號已經存在,則保留原來的值,所以依舊為function
var fun = 3;
console.log(typeof fun === 'number');//true: fun被重新賦值為3
function fun(){};
cosole.log(typeof fun === 'number') //true 在程式實際執行過程中,跳過函式宣告部分。所以依舊為number

進入正題 - 認識閉包

閉包允許函式方位並操作函式外部的變數。只要變數或函式存在於宣告函式時的作用域內,閉包即可使函式能夠訪問這些變數或函式。

//閉包例子
var outerValue = "sss";
var later;
function outerFunc(){
  var innerValue = "eee";
  function innerFunc(){
    console.log(outerValue === "sss") 
    console.log(innerValue === "eee") 
  }
  later = innerFunc
}
outerFunc(); 
later();  // true true

說明: 當在外部函式中宣告內部函式時,不僅定義了函式的宣告,而且還建立了一個閉包,該閉包中不僅包含了函式的宣告,還包含了在函式宣告時該作用域中的左右變數。當最終執行內部函式時,儘管宣告時的作用域已經消失了,但是通過閉包,仍然能夠訪問到原始作用域。

注意:雖然閉包時非常有用的,但是不能過度使用。使用閉包時,所有的資訊都會儲存在記憶體中,知道JavaScript引擎確保這些資訊不再使用(可以安全的進行垃圾回收),或頁面解除安裝時,才會清理這些資訊

使用閉包

  1. 封裝私有變數
function A(){
  var value = 55;
  this.getValue = function(){
    return value
  };
  this.add = function(){
    value = value + 10;
  }
}

var a1 = new A();
a1.add();
//a1.value = undefined;
//a1.getValue() = 65

var a2 = new A();
//a2.getValue() = 55;

//注意:
var obj = {};
obj.getValue = a1.getValue ;
//obj.getValue() = 65

注意: 將a1的getValue方法賦給新的obj物件,我們發現可以通過obj.getValue()獲得a1的value值。表明了在JavaScript中沒有真正的私有物件屬性

  1. 回撥函式
function move(){
  var step = 10;
  var sum = 0;
  var timer = setInterval(function(){
    sum = sum + step;
  },1000)
}

相關文章