<address id="ttjl9"></address>

      <noframes id="ttjl9"><address id="ttjl9"><nobr id="ttjl9"></nobr></address>
      <form id="ttjl9"></form>
        <em id="ttjl9"><span id="ttjl9"></span></em>
        <address id="ttjl9"></address>

          <noframes id="ttjl9"><form id="ttjl9"></form>

          JavaScript作用域和閉包

          2021-9-14    前端達人


          前言

          深入了解閉包和作用域鏈就需先了解函數預編譯的過程


          一、預編譯

          JavaScript:運行三部曲:
          語法分析–預編譯–解釋執行
          預編譯:
          發生在函數執行的前一刻。
          函數聲明整體提升,變量只聲明提升。
          1.函數預編譯的過程:
          1.創建AO對象Activation Object(執行期上下文,其作用就是我們理解的作用域,函數產生的執行空間庫)
          2.找形參和變量聲明,將變量和形參名作為AO屬性名,值為undefined
          3.將實參值與形參統一
          4.找到函數聲明,將函數名作為屬性名,值為函數體。
          例:

          function test (a, b){ console.log(a); c = 0; var c; a = 3; b = 2; console.log(b); function b (){}; function d (){}; console.log(b); } test(1); /*答案:1,2,2
          答題過程:找形參和變量聲明,將變量和形參名作為 AO 屬性名,值為 undefined, AO{
           a : 1,
           b : undefined,
           c : undefined
          }
          函數聲明 function b(){}和 function d(){},AO{
           a : 1,
           b : function b(){},
           c : undefined,
           d : function d(){}
          }
          執行 console.log(a);答案是 1
          執行 c = 0;變 AO{
           a : 1,
           b : function b(){},
           c : 0,
           d : function d(){}
          }
          var c 不用管,因為 c 已經在 AO 里面了
          執行 a = 3;改 AO{
           a : 3,
           b : function b(){},
           c : 0,
           d : function d(){}
          }
          執行 b = 2;改 AO{
           a : 3,
           b : 2,
           c : 0,
           d : function d(){}
          }
          執行 console.log(b);答案是 2
          function b () {}和 function d(){}已經提過了,不用管
          執行 console.log(b);答案是 2*/ 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43
          • 44
          • 45
          • 46
          • 47

          2.全局預編譯它和函數預編譯步驟一樣,但它創造的是GO(全局對象):
          1.生成了一個 GO 的對象 Global Object(window 就是 GO
          2.找變量聲明…
          3.找函數聲明…

          任何全局變量都是 window 上的屬性
          變量沒有聲明就賦值了,歸 window 所有,就是在 GO 里面預編譯。
          例 :

          function test(){ var a = b =123; console.log(window.b); } test(); 答案 a 是 undefined,b 是 123 先生成 GO{ b : 123 } 再有 AO{ a : undefined } 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12

          想執行全局,先生成 GO,在執行 test 的前一刻生成 AO
          函數里找變量,因為GO和AO有幾層嵌套關系,近的優先,從近的到遠的, AO里有就看 AO,AO 沒有才看 GO。所以函數局部變量和全局變量同名,函數內只會用局部。

          二、作用域精講

          作用域定義:變量(變量作用于又稱上下文)和函數生效(能被訪問)的區域
          全局、局部變量
          作用域的訪問順序:函數外面不能用函數里面的。里面的可以訪問外面的,外面的不能訪問里面的,彼此獨立的區間不能相互訪問。

          1.[[scope]]: 每個 javascript 函數都是一個對象,對象中有些屬性我們可以訪問,但有些不可以,這些屬性僅供 javascript 引擎存取,[[scope]]就是其中一個。[[scope]]指的就是我們所說的作用域,其中存儲了運行期上下文的集合。

          2.執行期上下文: 當函數在執行的前一刻,會創建一個稱為執行期上下文的內部對象(AO)。
          一個執行期上下文定義了一個函數執行時的環境,函數每次執行時對應的執行上下文都是獨一無二的,所以多次調用一個函數會導致創建多個執行上下文,當函數執行完畢,執行上下文被銷毀。

          3.作用域鏈:[[scope]]中所存儲的執行期上下文對象的集合(GO和AO),這個集合呈鏈式鏈接,我們把這種鏈式鏈接叫做作用域鏈。

          4.查找變量: 在哪個函數里面查找變量,就從哪個函數作用域鏈的頂端依次向下查找(先查自己的AO,再查父級的AO,一直到最后的GO)。
          函數類對象,我們能訪問 test.name
          test.[[scope]]隱式屬性——作用域

          作用域鏈圖解:

          function a (){ function b (){ var bb = 234; aa = 0; } var aa = 123; b(); console.log(aa) } var glob = 100; a(); 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12

          0 是最頂端,1 是次頂端,查找順序是從最頂端往下查
          在這里插入圖片描述
          在全局預編譯中函數a定義時,它的[[scope]]屬性中有GO對象。
          在這里插入圖片描述
          在函數a執行前先函數預編譯,創建自己的AO對象,并存儲在[[scope]]屬性上,與之前存儲的GO成鏈式。同時函數b被創建定義。
          在這里插入圖片描述
          在b被創建時,它生成的[[scope]]屬性直接存儲了父級的[[scope]],它有了父級的AO和GO。
          在這里插入圖片描述
          b函數執行前預編譯,生成自己的AO,存儲在[[scope]]屬性中。

          詳解過程: 注意[[scope]]它是數組,存儲的都是引用值。
          b 中 a 的 AO 與 a 的 AO,就是同一個 AO,b 只是引用了 a 的 AO,GO 也都是同一個。
          function b(){}執行完,干掉的是 b 自己的 AO(銷毀執行期上下文)(去掉連接線),下次 function b 被執行時,產生的是新的 b 的 AO。b 執行完只會銷毀自己的 AO,不會銷毀 a 的 AO。會退回到b被定義時(仍有父級的AO和GO)。
          function a(){}執行完,會把 a 自己的 AO 銷毀【也會把 function b的[[scope]]也銷毀】,只剩 GO(回歸到 a 被定義的時候),等下次 function a再次被執行時,會產生一個全新的 AO,里面有一個新的 b 函數。。。。。。周而復始。

          思考一個問題:如果 function a 不被執行,下面的 function b 和 function c 都是看不到的(也不會被執行,被折疊)。只有 function a 被執行,才能執行 function a 里面的內容a();不執行,根本看不到 function a (){}里面的內容,但我們想在a函數外面調用b函數怎么辦呢,于是閉包出現了。

          三、閉包

          閉包的定義

          當內部函數被保存到外部時,將會生成閉包。但凡是內部的函數被保存到外部,一定生成閉包。
          閉包的問題:閉包會導致原有作用域鏈不釋放,作用域中的局部變量一直被使用著,導致該作用域釋放不掉,造成內存泄露(就是占有過多內存,導致內存越來越少,就像泄露了一樣)
          例:

          function a(){ function b(){ var b=456; console.log(a); console.log(b); } var a=123; return b; } var glob = a(); glob(); 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11

          答案 123,456。
          function a(){ }是在 return b 之后才執行完,才銷毀。而return b 把 b(包括 a 的 AO)保存到外部了(放在全局)當 a 執行完砍掉自己的 AO 時(砍掉對AO存儲地址的指針),因為b還保存著對a的AO的引用,所以內存清除機制不會清除掉a的AO, b 依然可以訪問到 a 的 AO。

          閉包的作用:

          1.實現共有變量

          function test(){ var num=100; function a(){ num++; } function b(){ num--; } return [a,b]; } var myArr=test(); myArr[0](); myArr[1](); 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13

          答案 101 和 100。
          思考過程:說明兩個用的是一個 AO。
          myArr[0]是數組第一位的意思,即 a,myArr0;就是執行函數 a 的意思;
          myArr[1]是數組第二位的意思,即 b,myArr1; 就是執行函數 b 的意思。
          test doing test[[scope]] 0:testAO
          1:GO
          a defined a.[[scope]] 0 : testAO
          1 : GO
          b defined b.[[scope]] 0 : testAO
          1 : GO
          return[a, b]將 a 和 b 同時被定義的狀態被保存出來了
          當執行 myArr0;時
          a doing a.[[scope]] 0 : aAO
          1 : testAO
          2 : GO
          當執行 myArr1;時
          b doing b.[[scope]] 0 : bAO
          1 : a 運行后的 testAO
          2 : GO
          a 運行后的 testAO, 與 a doing 里面的 testAO 一模一樣
          a 和 b 連線的都是 test 環境,對應的一個閉包

          2.可以做緩存(存儲結構)

          function eater(){ var food=""; var obj={ eat : function (myFood){ console.log("i am eating"+food); food =""; }, push : function (myFood){ food = myFood; } } return obj; } var eater1 = eater(); eater1.push("banana"); eater1.eat(); 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17

          答案 i am eating banana,eat 和 push 操作的是同一個 food
          在 function eater(){里面的 food}就相當于一個隱式存儲的機構
          obj 對象里面是可以有 function 方法的,也可以有屬性,方法就是函數的表現形式

          3.可以實現封裝,屬性私有化
          只能調用函數方法,不能修改函數的屬性。

          4.模塊化開發,防止污染全局變量


























          藍藍設計建立了UI設計分享群,每天會分享國內外的一些優秀設計,如果有興趣的話,可以進入一起成長學習,請掃碼藍小助,報下信息,藍小助會請您入群。歡迎您加入噢~~希望得到建議咨詢、商務合作,也請與我們聯系。

          分享此文一切功德,皆悉回向給文章原作者及眾讀者.

          轉自:csdn
          免責聲明:藍藍設計尊重原作者,文章的版權歸原作者。如涉及版權問題,請及時與我們取得聯系,我們立即更正或刪除。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          日歷

          鏈接

          個人資料

          藍藍設計的小編 http://www.syprn.cn

          存檔

          亚洲va欧美va天堂v国产综合