在上一篇文章「執行環境和做用域」中,我試着梳理了執行環境和做用域的關係。但實際上,文章中並無提到做用域,而是介紹了執行環境和做用域鏈。這裏先把上篇文章的坑填了。javascript
上篇文章的最後,我提了一個問題。這裏把代碼稍微修改下,以下:java
var name = 'window';
outer();
function outer(){
var name = 'outer';
inner(); //輸出什麼?
}
function inner(){
console.log(name);
}複製代碼
這段代碼很簡單,但很容易迷惑人。不少人可能會認爲,應該輸出「outer」,但實際上結果確實「window」。這是爲何呢?瀏覽器
做用域和執行上下是兩個不一樣的概念。執行上下文在上篇文章中已經解釋過了:在函數執行時建立。那麼,做用域怎麼理解呢?微信
做用域能夠理解爲一套規則,這套規則用來管理引擎如何在當前做用域以及嵌套的子做用域中根據標識符名稱進行變量查找。同執行環境同樣,做用域只有兩種(不考慮eval):全局做用域與函數做用域。閉包
在js中代碼整個的執行分爲兩個階段:代碼編譯和代碼執行。代碼編譯由編譯器完成,將代碼翻譯成可執行代碼。代碼執行由js引擎完成,主要任務是執行可執行的代碼。在代碼編譯階段,做用域規則就已經被肯定了。到代碼執行時,執行上下文被建立,同時,做用域鏈做爲做用域規則的具體實現被構建出來。過程以下圖:
函數
再回頭去看開頭的問題,就不難理解了:在編譯階段,inner函數的相關的做用域規則就已經肯定了,在而outer函數中執行時,只是具體地實現了相關的做用域的規則,也就是構建做用域鏈,而這個做用域鏈上面沒有outer函數執行環境相對應的變量對象,而是有全局執行環境對應的window對象,所以,結果是‘window’。post
閉包在js高程中的解釋是:有權訪問另外一個函數做用域中的變量的函數。簡單說就是,假設函數a是定義在函數b中的函數,那麼函數a就是一個閉包。正常狀況下,在函數的外部訪問不到函數內部的變量,但有了閉包就能夠間接的實現訪問內部變量的須要。也就是說,閉包是鏈接函數內部和外部的橋樑。這就是閉包的第一個做用:訪問函數內部的變量。還有另一個做用就是:讓被引用的變量值始終保持在內存中。性能
在上一篇文章中提到代碼當執行到一個函數時,會建立一個臨時的活動對象,並把這個對象做爲變量對象推入環境棧中。當這個函數執行完的時候,這個對象就會出桟,並被銷燬。但當閉包中引用了函數中的變量時,那麼,這個變量就會保存在內存中。也就是上面提到的閉包的第二個做用。之因此爲這樣,是由於JavaScript的回收機制。ui
基本全部瀏覽器都是使用「標記清除」的方式回收內存。也就是說,當變量進入執行環境的時候(在函數中聲明一個變量),就給變量添加標記,而當函數執行完的,變量再也不被引用的時候,再添加刪除的標記,垃圾收集器就會自動清楚這個變量佔有的內存。但在閉包中引用了函數中的變量,而閉包又被看成結果返回時,閉包中的由於被引用就不會被清除。例如,下面的代碼:this
function fn1(){
var a = 1;
return function(){
console.log(++a);
}
}
var fn2 = fn1();
fn2(); //輸出2
fn2(); //輸出3複製代碼
在這段代碼中,fn1中的閉包函數被看成結果返回,在閉包中的引用的變量a由於被引用而沒有被清除,一直保存在內存當中,因此執行fn2的時候會輸出不斷增長的結果:2和3。
一、因爲閉包會使得函數中被引用的變量一直保存在內存中,消耗內存,因此謹慎使用閉包,不然會形成網頁的性能問題。
二、閉包會改變父函數內部變量的值。若是父函數再次被執行的,而在外部已經執行過閉包修改變量的值,那麼,此次執行的結果就會和上次的不同。
最後再來一段代碼,想下輸出的代碼,沒疑問的化,閉包的運行機制基本就掌握了:
var name = "window";
var obj = {
name : "obj",
getName : function(){
return function(){
return this.name;
};
}
};
console.log(obj.getName()());複製代碼
寫在結尾:
若是以爲我寫的文章對你有幫助,歡迎掃碼關注個人公衆號:海痕筆記
微信號:haihenbiji