JavaScript閉包和作用域
文章目錄
- JavaScript閉包和作用域
- 前言
- 一、預編譯
- 二、作用域精講
- 三、閉包
- 閉包的定義
- 閉包的作用:
前言
深入了解閉包和作用域鏈就需先了解函式預編譯的程序
一、預編譯
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*/
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
}
想執行全域,先生成 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();
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();
答案 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]();
答案 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();
答案 i am eating banana,eat 和 push 操作的是同一個 food
在 function eater(){里面的 food}就相當于一個隱式存盤的機構
obj 物件里面是可以有 function 方法的,也可以有屬性,方法就是函式的表現形式
3.可以實作封裝,屬性私有化
只能呼叫函式方法,不能修改函式的屬性,
4.模塊化開發,防止污染全域變數
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/300263.html
標籤:其他
