摘要:前言每個函數都有自己的執(zhí)行環(huán)境。當某個函數被調用時,會創(chuàng)建一個執(zhí)行環(huán)境(execution context)及相應的作用域鏈,并把作用域鏈賦值給一個特殊的內部屬性(即[[Scope]])。然后使用this、arguments和函數參數、內部變量、內部函數引用來初始化函數的活動對象(activation object)。作用域鏈(堆棧)是指向活動對象的指針列表,該函數的活動對象在棧頂,全局變量對象在
前言
每個函數都有自己的執(zhí)行環(huán)境。當某個函數被調用時,會創(chuàng)建一個執(zhí)行環(huán)境(execution context)及相應的作用域鏈,并把作用域鏈賦值給一個特殊的內部屬性(即[[Scope]])。然后使用this、arguments和函數參數、內部變量、內部函數引用來初始化函數的活動對象(activation object)。作用域鏈(堆棧)是指向活動對象的指針列表,該函數的活動對象在棧頂,全局變量對象在棧底。
PS:在 JavaScript 的執(zhí)行中會一直存在一個Execute Context Stack , 最下面一個是Global Context,創(chuàng)建的execution context會被壓入這個棧。
這里面提到幾個關鍵字:
1. 執(zhí)行環(huán)境(execution context)
聲明該函數有權訪問的變量和函數。
2. 作用域鏈(scope chain)
作用域鏈的創(chuàng)建規(guī)則是復制上一層環(huán)境的作用域鏈,并將指向本環(huán)境變量對象的指針放到鏈首。本質上是一個指向變量對象的指針列表,它只是引用,實際上不包含變量對象。另外JavaScript是函數作用域的,并沒有像Java、C那樣有塊級作用域。
PS:JS主要是詞法作用域(lexical scope,也即是靜態(tài)作用域),在詞法解析階段既確定了。但是有兩個特例,就是eval和with可以構成動態(tài)作用域。
3. 活動對象(activation object)
保存該函數arguments和函數參數、內部變量和內部函數引用。
例子:
function compare(value1, value2){ if(value1 < value2){ return -1; } else if(value1 > value2){ return 1; } else{ return 0; } } var result = compare(5, 10);
相應的執(zhí)行環(huán)境圖示:
閉包概念
JavaScript高級程序設計里面對閉包的描述是,“閉包是指有權訪問另一個函數作用域中的變量的函數”。我覺得嚴格上講,這個“另一個”是閉包函數的外部函數。
ECMAScript中,閉包指的是:
1. 從理論角度:所有的函數。因為它們都在執(zhí)行的時候就將上層上下文的數據保存起來了(體現在作用域鏈)。哪怕是簡單的全局變量也是如此,因為函數中訪問全局變量就相當于是在訪問自由變量,這個時候使用最外層的作用域。
2. 從實踐角度:以下函數才算是閉包:
(1). 即使創(chuàng)建它的上下文已經銷毀,它仍然存在(比如,內部函數從父函數中返回)。
(2). 在代碼中引用了自由變量。
PS:自由變量是指在函數中使用的,但既不是函數參數也不是函數的局部變量的變量。
閉包使用場景
1. 循環(huán)遍歷中的延遲使用變量
我們可能在循環(huán)中為某些元素注冊事件,或setTimeout執(zhí)行一些代碼,這段代碼使用到循環(huán)部分的變量(我稱這個變量為延遲使用變量),可能需要使用閉包保證變量的準確。
例子:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script type="text/javascript"> function onMyLoad(){ var arr = document.getElementsByTagName("p"); //有問題寫法 for(var i = 0; i < arr.length;i++){ arr[i].onclick = function(){ alert(i); } } //閉包寫法 for(var i = 0;i<arr.length;i++){ (function (arg) { arr[i].onclick = function () { alert(arg); } })(i); } } </script></head><body onload="onMyLoad()"> <p>產品一</p> <p>產品二</p> <p>產品三</p> <p>產品四</p> <p>產品五</p> </body> </html>
2. 私有變量
JavaScript沒有私有變量這東西,只是用閉包模擬而已。
例子:
function MyObject(){ var privateVar = 10; this.getPrivateVar = function(){ return privateVar; } }
3. 模塊模式
為單例創(chuàng)建私有變量和特權方法(有權訪問私有變量和私有函數的公有方法)就是模塊模式。
例子:
var singleton = (function(){ var privateVar = 10; var getPrivateVar = function(){ return privateVar; } return { getPrivateVar : getPrivateVar } })();
4. 塊級作用域
可以用來模擬塊級作用域。
閉包注意地方
1. 閉包會使函數中的變量常駐內存,所以如果濫用閉包,會導致內存回收不回來,影響腳本性能。
例子:
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; }
上面的代碼并未利用到閉包的益處,因此,應該修改為如下常規(guī)形式:
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype.getName = function() { return this.name; }; MyObject.prototype.getMessage = function() { return this.message; };
PS:前者每次實例化時候,每個對象里面都存在getName 、getMessage方法,后者則不會。
Q&A
1. 閉包是函數還是一種代碼行為?所有函數都是閉包嗎?
參考閉包概念
2. 變量查找順序?
當在函數中訪問一個變量的時候,搜索順序是先搜索自身的活動對象,如果存在則返回,如果不存在將繼續(xù)搜索作用域鏈上的活動對象,依次查找,直到找到為止。如果整個作用域鏈上都無法找到,則返回undefined。如果函數存在prototype原型對象,則在查找完自身的活動對象后,再查找自身的原型對象,再繼續(xù)查找。