1. 程式人生 > 實用技巧 >JS的變數提升與閉包

JS的變數提升與閉包

【JS渲染機制堆疊記憶體】

當瀏覽器去載入介面載入js時,首先會建立提供window全域性作用域,然後,程式碼開始自上而下執行,

程式碼:var a = 7;

①宣告變數a,預設值為undefined

②在當前作用域中開闢一個位置儲存7這個值

③讓變數a和值12關聯在一起(賦值)

基本型別和引用型別的區別就是,儲存方式的不同。

基本型別直接在作用域中,引用型別因為相對複雜,所以需要單獨開闢的儲存空間。

var ary1 =  [12,23];

var ary2 = ary1;

ary2.push(100);

console.log(ary1); //[12,23,100]

所有作用域的兩個作用:提供程式碼的執行環境

儲存基本型別值。

IE靠的是計數器,引用加一,反之減一。Chrom則是,固定時間檢視一下,是否還被引用。

IE計數器,記混的時候,就發生了記憶體洩漏。

想讓堆記憶體銷燬,直接賦值為 null ,通過空物件指標 null 可以讓 原始變數或者其它 指向空,那麼原有被佔用的堆記憶體就沒有被佔用了,瀏覽器就會在空閒的時候銷燬它。

【變數提升機制】

變數提升:當棧記憶體(作用域)形成,JS程式碼自上而下執行之前,瀏覽器受限會把所有帶"VAR"/"FUNCTION"關鍵詞的進行提前"宣告"或者"定義",這種預先處理機制,叫做"變數提升"。

=》 宣告(declare): var a (預設值undefined)

=》 定義(defined): a = 12 (定義其實就是賦值操作)

[變數提升階段]

=》帶"VAR"的只宣告未定義

=》帶"FUNCTION"的宣告和賦值都完成了

=》變數提升只發生在當前作用域。(例如:開始載入頁面的時候只對全域性作用域下的進行提升,因為此時函式中儲存的都是字串而已)

=》在全域性作用域宣告的函式或者邊阿玲是"全域性變數",同理,在私有作用域下宣告的變數是"私有變數" [帶VAR/FUNCTION的才是宣告]

自從學了預解釋,從此節操是路人。

=》瀏覽器很懶,從不會做相同的事情。當代碼執行遇到建立函式這部分程式碼後,會直接跳過(因為在提升階段就已經完成了函式的賦值操作了)

【重名問題】

【暫時性死區】

在ES6中Let和const等方式建立變數或者函式,不存在變數提升機制。

=》切斷了全域性變數和window屬性的對映機制。

=》在相同作用域,基於Let不能宣告相同名字的變數。(不管是是什麼方式只要聲明瞭,在用let重複宣告就會報錯,var也一樣)

雖然,沒有變數提升機制,但是在當前作用域程式碼自上而下執行之前,瀏覽器會做一個重複性檢測:自上而下查詢當前作用域下所有變數,一旦發現重複的,直接丟擲異常,程式碼停止,不會繼續執行。(雖然沒有把變數提升,但是瀏覽器已經記住了,當前作用域下有哪些變數。)

  

a,b變為私有的,與全域性無關,c為全域性。

【私有變數練習】

【上級作用域查詢】

=》arguments:實參集合

=》arguments.callee:函式本身

=》arguments.callee.caller:當前函式在哪執行的,CALLER就是誰(記錄的是執行它的宿主環境),在全域性下執行的結果是null。(嚴格模式程式設計禁止使用這兩個屬性。)

=》當函式執行時,形成一個私有作用域A,A的上級作用域是誰,和他在哪執行沒有關係,和它在哪建立定義有關係,在哪建立的,它的上級作用域就是誰。

【堆疊記憶體銷燬機制】

=》JS分為堆記憶體和棧記憶體

=》堆記憶體(儲存引用型別值),和程式碼分開(物件:鍵值對 函式:程式碼字串)

=》棧記憶體,提供JS程式碼的執行環境儲存基本型別值

[堆記憶體釋放] :讓所有引用堆記憶體空間地址的變數賦值為 NULL 即可(沒有變數佔用這個堆記憶體,瀏覽器在空閒的時候就會將其釋放)

[棧記憶體釋放] :一般情況下,當函式執行完成後,所形成的私有作用域(棧記憶體)都會自動釋放掉,(在棧記憶體佔用儲存的值也會釋放掉),但是也存在特殊情況:

①函式執行完成,當前形成的棧記憶體中,某些內容被棧記憶體以外的變數佔用。

②全域性棧記憶體,只有當頁面關閉後才會被釋放掉。

...

如果當前棧記憶體沒有被釋放掉,那麼之前在棧記憶體中儲存的基本值也不會被釋放掉,能夠一直儲存下來。

i++ :自身累加1 ,和別人運算的時候 ,先拿原有值和其其它進行運算,運算結束後,本身累加1 。

++i :自身累加1 ,先自身累加1,再和別人運算。

6 12 16 8

【帶不帶var的區別】

=>在全域性作用域下宣告一個變數,也相當於給Window全域性物件設定了一個屬性,變數的值就是屬性值(私有作用域中宣告的私有變數和Window無關)

用 in 操作符 可以檢測某個屬性名是否隸屬於這個物件。 'AZUKI' in BoZai ,rue;

=》全域性變數和window中的屬性存在"對映機制" , 雙方互相同步。

=》不加var本質是win的屬性,建立變數必需加var,養成良好的程式設計習慣。

【閉包】

函式執行形成一個私有的作用域,保護裡面的私有變數不受外界干擾,這種保護機制稱之為“閉包”。

函式執行形成一個不銷燬的私有作用域(私有棧記憶體)才是閉包。

//=>閉包:柯理化函式 fn執行返回一個堆記憶體被f佔用,所以fn作用域不銷燬
function fn(){
  return function (){

  }
}

var f=fn();
//=>閉包:惰性函式 自執行匿名函式形成的私有作用域被utils佔用
var utils=(function(){
  return{
  
  }
})();

小紙條:()內表示宣告一個函式,正常宣告函式是不能直接在後面加括號呼叫的。

閉包應用舉例:
真實專案為了保證JS效能(堆疊記憶體的效能變化),應該儘可能減少閉包的使用(不銷燬的堆疊記憶體是消耗效能的)。 1.閉包的保護作用:保護私有變數不受外界干擾 真實專案開發中,尤其是團隊協作開發,應儘量減少全域性變數的使用,防止衝突,造成全域性變數汙染,那麼此時我們可以把自己這一部分內容封裝到一個閉包中,讓全域性變數轉換為私有變數。 (function(){})(); 封裝外掛的時候,也會把程式都放到閉包中保護起來,防止和使用者的程式衝突,這時對於一些需要暴露給使用者的方法可以拋到全域性。 JQ:把需要暴露的方法拋到全域性 Zepto:基於RETURN把需要外面使用的方法暴露出去

2.閉包的儲存作用:形成不銷燬的棧記憶體,把一些值儲存下來,方便後面調取使用

tips:

進入函式:形參賦值 變數提升 程式碼自上而下執行

在傳統的ES規範中,只有全域性作用域和函式執行產生的私有作用域,判斷和迴圈並不會產生作用域。

原生JS:閉包,oop,非同步程式設計,promise,async和await,es6新特性,js事件機制

所有的事件繫結都是非同步程式設計(當前事件沒有完成,不再等待,繼續執行下面的任務。),同步程式設計(一件事一件事做,當前事件沒完成,下一個任務不能處理。)

小結:

有佔用,不釋放,無論是變數,還是事件什麼佔用,就是閉包。

ES6中判斷迴圈都是塊級作用域,一般大括號內部都是塊級作用域。(物件除外)

每一輪迴圈,都會形成一個單獨的作用域。