我不知道的js(一)做用域與閉包

做用域與閉包


做用域

  • 什麼是做用域
    做用域就是一套規則,它負責解決(1)將變量存在哪兒?(2)如何找到變量?的問題ajax

  • 做用域工做的前提
    • 誰賦予了做用域的權利?——js引擎
    • 傳統編譯語言編譯的過程
      • 分詞/詞法分析:字符串 =》 詞法單元
      var a=2; => var a = 2 ;(共5個單元)
      • 解析/語法分析:詞法單元流 =》 抽象語法樹(Abstract syntax tree,AST)
      • 代碼的生成: AST =》 可執行代碼(機器指令)
    • js引擎編譯的特色:
      • 代碼在執行前進行編譯(須要用到JIT(just in time)進行延遲編譯甚至實施重編譯來保證性能最佳)
      • 針對 ** var a = 2; **例子的編譯流程
        • 分詞/詞法分析:將var a=2; => var a = 2 ; 分紅5個詞法單元
        • 解析/語法分析:解析成抽象語法樹
        • 代碼生成:** 重點來了 **
          • var a (聲明操做)=》此時編譯器會 詢問做用域是否有叫 a 的變量存在 ?忽略該聲明,繼續編譯 : 在當前做用域下聲明一個新變量,命名爲a
          • a=2 (賦值操做) =》此時編譯器會 詢問做用域是否有叫 a 的變量存在 ?使用該變量 : 引擎根據做用域鏈繼續向上查找該變量。
            是否能找到叫 a 的變量 ?直接賦值 : 引擎拋出異常。
        • 引擎在查找變量a是否被聲明的過程當中,是如何進行的查找?
          • 做用域的協助
          • 查找的目的是賦值=》LHS(left hand side)查詢 :(1) 當變量出如今賦值操做的左側時,使用 LHS 查詢。相似賦值 a=2; (2)若在頂層做用域內沒有找到,嚴格模式下:Reference異常;非嚴格模式下:自動隱式建立全局變量
          • 查找的目的是取值=》RHS(right hand side)查詢 :(1) 當變量出如今賦值操做的右側時,使用 RHS 查詢。相似取值 a; (2) 若在頂層做用域內沒有找到,直接拋出Reference異常
  • 做用域是如何工做的?詞法做用域,動態做用域。
    • 詞法做用域
      • 什麼是詞法做用域
        • 詞法做用域就是定義在詞法分析階段的做用域。詞法做用域就是在寫代碼時將變量和塊做用域寫在哪裏決定的。通常是不會變的。
      • 詞法做用域的查找規則(做用域鏈
        • 做用域查找從運行時所處的最內部的做用域開始,逐級向上或向外查找,直到碰見第一個匹配的標識符(變量、函數)爲止。
      • 改變詞法做用域(二般狀況出現:欺騙詞法
        • 2種方法
          • eval();接受一個字符串爲參數,即動態插入程序代碼,假裝成詞法期就存在的代碼。
          function foo(str, a){
              eval(str);
              console.log(a, b);
          }
          var b=2;
          foo('var b=3;',1); //1,3
          • with;一般被當作重複引用同一個對象的多個屬性的快捷方式,能夠不須要重複引用對象自己。
          var obj = {
              a:1,
              b:2,
              c:3
          };
          //使用with
          with(obj){
              a=3;
              b=4;
              c=5;
          }
        • 後果
          • 引擎沒法在編譯時對做用域對的查找進行優化
          • 在嚴格模式下,with被徹底禁止,eval(...)也被禁止
  • 常見的做用域單元跨域

    • 函數做用域
      • 什麼是函數做用域
        • 屬於這個函數的所有變量均可以在整個函數的範圍內使用及複用
      • 函數的好處:隱藏內部實現,規避同名標誌符之間的衝突(有如下兩個方法)
        • 聲明全局命名空間(一個對象,用來存儲全局做用域種的變量)
      • 函數相關常識
        • 函數聲明 vs 函數表達式
        function是聲明中的第一個詞 ? 函數聲明 : 函數表達式。
        • 具名 vs 匿名 :
        有無名字的區別
        函數表達式能夠沒有名字,可是函數聲明必須有名字
        鼓勵全部的函數都有名字
        • 當即執行函數表達式
        兩種寫法: 
        (function(){}());
        (function(){})();//經常使用,第一個括號( )將函數變成表達式,第二個( )執行這個函數
    • 塊做用域(es5以前並無該概念)
      • 什麼是塊做用域
      變量的聲明離使用的地方越近越好,並最大程度的本地化。
      • 塊做用域的例子
        • with關鍵字 : with從對象中建立出的做用域僅在with聲明中而非外部做用域有效
        • try/catch : catch分句建立做用域,且聲明的變量只能在catch中使用
        • let : let爲其聲明的變量提供塊做用域{...},且let進行的聲明在塊中不會變量提高
        • const : const聲明常量。
  • 提高
    • 聲明提高
      • 變量聲明提高
      • 函數聲明提高
      • 函數表達式聲明不會被提高
      foo();//Uncaught TypeError: foo is not a function//foo()對於undefined值進行函數調用而致使非法操做
      var foo= function bar(){
          console.log('1');
      };
      foo();//1
      foo;//function bar(){}
      bar;//ReferenceError: bar is not defined
      bar();//ReferenceError: bar is not defined
    • 提高的優先級
      • 函數優先,其次纔是變量
      • 避免在塊內聲明函數

  • 練習題(1)找出全部的 LHS 查詢和 RHS 查詢
function foo(a) {
    var b=a;
     return a+b;
}
var c= foo(2);

答案:
LHS(3處) c=..., a=2(隱式變量分配),b=...,
RHS(4處) foo(2..., =a, a.., ..b閉包


做用域閉包(晦澀難懂,經常搞錯的地方)

  • 什麼是閉包?
    • 函數能夠記住並訪問所在的詞法做用域時,就產生了閉包。即便是在當前詞法做用域以外的地方執行。
    閉包     = 那些可以訪問自由變量的函數 = 函數 + 函數能訪問的自由變量
    自由變量  = 在函數中使用,但既不是函數參數也不是函數的局部變量
  • 閉包做用
    • 能夠讀取函數內部的變量
    • 讓這些變量的值始終保持在內存中。
  • 閉包例子
    • 回調函數(定時器,ajax跨域,異步等)
    • for循環
    for(var i=1;i<=5;i++){
        setTimeout( function timer(){
            console.log(i);
        },i*1000);
    }  //以每秒一次的頻率輸出5個6
    
    for(var i=1;i<=5;i++){
        (function(j){
            setTimeout( function timer(){
                console.log(j);
            },j*1000);
        })(i);
    } //當即執行函數爲每一次迭代生成一個新的做用域,以每秒一次依次輸出1-5
    
    for(let i=1;i<=5;i++){
        setTimeout(function timer(){
            console.log(i)
        },i*1000);
    }

動態做用域

  • 什麼是動態做用域 動態做用域是在函數運行時肯定的,相似this。 詞法做用域關注在何處聲明,動態做用域關注在何處調用。 ***
相關文章
相關標籤/搜索