什麼是閉包?
簡單講,閉包就是指有權訪問另一個函式作用域中的變數的函式。
不好理解?看下面一個簡單的例子:
function func() {
var a = 1, b = 2;
function closure() {
return a + b
}
return closure
}
複製程式碼
上述程式碼中的closure就是一個閉包,是不是有點感覺了?
接下來再看下下面這句話:
通常,函式的作用域及其所有變數都會在函式執行結束後被銷燬。但是,在建立了一個閉包以後,這個函式的作用域就會一直儲存到閉包不存在為止。
有點暈?我們一點點來看:
function addNum() {
var a = 1, b = 2;
console.log("addNum->a", a);
console.log("addNum->b", b);
return (a + b);
}
addNum();
console.log("handle->a", a);
console.log("handle->b", b);
複製程式碼
執行結果如上圖,在函式執行結束後,作用域和變數立即被銷燬。
如果有閉包存在:
function addNum(x) {
return function(y) {
return x + y;
}
}
var handleAddNum = addNum(5);
console.log(handleAddNum(2)); // 7
console.log(handleAddNum(3)); // 8
複製程式碼
結果和上個例子是不是有所不同,按理說,addNum被執行結束後,x就應該別銷燬,但是因為有閉包的存在,x的作用域被繼續保留了,想要銷燬x,就要銷燬閉包。
handleAddNum = null;
複製程式碼
下面在看下一個比較經典的閉包問題:
function test(){
var arr = [];
for(var i = 0;i < 10;i++){
arr[i] = function(){
return i;
};
}
for(var a = 0;a < 10;a++){
console.log(arr[a]());
}
}
test(); //結果連打十個10
複製程式碼
分析下執行過程:
首先,在這個作用域內,都是至上而下執行的,var i
定義了一個整個作用域上的變數,第一個for
語句執行結束後,i為10,
然後是第二個for
語句,執行arr[i] = function(){ return i};
,因為在函式中沒有找到i,會往上一層作用域去找,而上一層的i已經為10,所以會連續輸出十個十。
把var
改動成let
後,看下執行結果:
function test(){
var arr = [];
for(let i = 0;i < 10;i++){
arr[i] = function(){
return i;
};
}
for(var a = 0;a < 10;a++){
console.log(arr[a]());
}
}
test(); //輸出0,1,2,3,4,5,6,7,8,9
複製程式碼
分析下執行過程:
let 在定義變數時會開闢新的塊級作用域,即每次for迴圈都會開闢出一塊的新的作用域,所以當執行arr[i] = function(){ return i};
時,沒有找到i時,向上找,在let開闢的新的塊級作用域中找到了i,即對應的0,1,2,3,4,5,6,7,8,9
閉包中的this物件:
有如下程式碼:
var obj = {
name: 'yy',
getThis: function() {
console.log('this', this);
}
}
obj.getThis();
複製程式碼
執行結果:this Object {name: "yy"}...
就是我們普遍認為的函式在哪裡定義就在哪裡執行。
那再看下面這個例子:
var obj = {
name: 'yy',
getThis: function() {
//匿名函式
return function() {
console.log('this', this);
}
}
}
obj.getThis()();
複製程式碼
執行結果:
this Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage, sessionStorage: Storage, webkitStorageInfo: DeprecatedStorageInfo…}
全域性作用域中呼叫了匿名函式,所以this指向了window。
不要認為函式在哪裡,其內部的this就指向哪裡。匿名函式的執行環境具有全域性性,因此其 this 物件通常指向 window。
鞏固下,想想下面的結果是啥:
var name = "The Window";
var obj = {
name: "My Object",
getName: function(){
var that = this;
console.log('getName:', this);
return function(){
return that.name;
};
}
};
console.log(obj.getName()());
複製程式碼
閉包的應用:設計私有的方法和變數
- 任何在函式中定義的變數,都可以認為是私有變數,因為不能在函式外部訪問這些變數。私有變數包括函式的引數、區域性變數和函式內定義的其他函式。
- 把有權訪問私有變數的公有方法稱為特權方法(privileged method)。
function Animal(){
// 私有變數
var series = "哺乳動物";
function run(){
console.log("Run!!!");
}
// 特權方法
this.getSeries = function(){
return series;
};
}
複製程式碼
單例(singleton):指的是隻有一個例項的物件。JavaScript 一般以物件字面量的方式來建立一個單例物件。
var singleton = {
name: "percy",
speak:function(){
console.log("speaking!!!");
},
getName: function(){
return this.name;
}
};
複製程式碼
模組模式(The Module Pattern):為單例建立私有變數和方法。
var singleton = (function(){
// 私有變數
var age = 22;
var speak = function(){
console.log("speaking!!!");
};
// 特權(或公有)屬性和方法
return {
name: "percy",
getAge: function(){
return age;
}
};
})();
複製程式碼
閉包的缺陷:
- 閉包的缺點就是常駐記憶體會增大記憶體使用量,並且使用不當很容易造成記憶體洩露。
- 如果不是因為某些特殊任務而需要閉包,在沒有必要的情況下,在其它函式中建立函式是不明智的,因為閉包對指令碼效能具有負面影響,包括處理速度和記憶體消耗。