QQ羣招募中:646258285(招募中,沒幾個人說話),
需要交流的朋友可以直接加我微信( DntBeliv )或QQ( 1121864253 )
作用域鏈
和函數的原型一樣,當創建一個函數之後,也會爲函數對象添加一個屬性[[Scopes]]記錄他的作用域。該屬性時一個棧,應該不可以修改,但是我們可以在調試模式中看到。
[[Scopes]]屬性記錄了當前作用域與活動對象,在函數運行時會向棧內壓入一個元素,標識當前的作用域,等函數運行結束後,會將棧頂的元素彈出,銷燬掉當前的作用域。
此外,當我們討論閉包的時候,[[Scopes]]屬性有一個非常重要的特點,那就是他會存儲調用函數的作用域。舉個例子,A函數內創建了函數B,那麼B的[[Scopes]]就會有A函數作用域。此外,所有函數都會包括global作用域。我們也可以猜到,表示函數運行時作用域的是棧頂元素,而棧頂的屬性會屏蔽掉非棧頂相同屬性。
我們看個例子,因爲無法看到函數運行時的情況,所有這裏看不到函數運行時的作用域。這裏主要看一下單個函數和函數內創建函數的不同。
function createComparisonFuntion(propertyName) {
return function (obj1, obj2) {
var value1 = obj1[propertyName];
var value2 = obj2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
}
var compareNames = createComparisonFuntion("name");
var result = compareNames({
name: "Zachary",
age: 28
}, {
name: "Nicholas",
age: 29
});
compareNames = null;
這裏,compareNames 就是createComparisonFuntion函數內創建的函數,compareNames 的作用域應該包括了reateComparisonFuntion作用域,我們來看一下是不是這樣。
可以看到compareNames包括了許多屬性,其中有我們之前提到的arguments、caller等,這裏主要關注[[Scopes]],可以看到裏面有兩個元素,第一個元素(也即是棧頂元素)就是它的調用函數createComparisonFuntion,第二個元素是全局作用域。因爲函數還沒有被調用,所以函數運行時的作用域還沒有被創建。最後我們再來看一下createComparisonFuntion的情況。
最後給一個完整的作用域鏈的圖:
閉包
閉包是指有權訪問另一個函數作用域中變量的函數。我們利用[[Scopes]]就可以實現閉包。
還是上面那個例子,爲什麼createComparisonFuntion已經銷燬了我們還可以訪問到"name"這個參數?這就是因爲compareNames的[[Scopes]]還引用着這個參數,因爲該部分內存還在使用中,因此不會被垃圾回收機制回收。看下面的證明:
現在來看幾個例子:
function createFunction() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
};
}
return result;
}
這段代碼希望創建10個不同i值的函數,但實際上所有的i返回都是10。爲什麼會這樣呢?因爲返回的10個函數訪問的i實際上都是通過閉包訪問的createFunction作用域中的活動對象i,只有這一個變量,當createFunction()調用結束後,i就等於10。
var name = "The window";
var obj = {
name: "my object",
getName: function () {
var that = this;
return function () {
return that.name;
};
}
};
//實際上寫成console.log(obj.getName()());就行了,爲了便於理解,寫成這個
console.log((obj.getName())());//"my object"
在使用vue的回調時經常用到that = this,一直不知道爲啥,現在清楚了吧,如果直接返回this.name,那麼這個this指的是當前作用域,那麼它的值取決於這個(被返回的)函數的調用對象。而如果是that,則就是通過閉包訪問的前一個函數的作用域。
再來看一個內存泄露的問題:
//element較大,會佔用很大內存,不建議這樣寫
function assignHandler(){
var element = document.getElementById("someEle");
element.onclick = function(){
console.log(element.id);
};
}
//建議這樣寫
function assignHandler(){
var element = document.getElementById("someEle");
var id = element.id;
element.onclick = function(){
console.log(id);
};
element = null;
}
模仿塊級作用域與私有變量
JavaScript中沒有塊級作用域,看下面這個例子:
for (var i = 0; i < 10; i++) {}
var i;
console.log(i);//10
但是函數類似於塊級作用域,我們可以通過閉包模仿塊級作用域:
//函數聲明後面不能跟圓括號
(function() {for (var i = 0; i < 10; i++) {}})();
console.log(i);//報錯:ReferenceError: i is not defined
進一步,我們可以創建具有私有變量的對象:
//函數表達式後面可以跟圓括號
var dingleton = function () {
//私有變量和私有函數
var privateVar = 10;
function privateFun(){
return false;
}
//創建對象
var obj = new Object();
//添加特權/公有屬性和方法
obj.publicProperty = true;
obj.publicMethod = function(){
privateVar++;
return privateFun();
}
//返回這個對象
return obj;
}();