原文網址:https://knife.blog.csdn.net/article/details/122152814
簡介
說明
本文介紹JavaScript的閉包的作用、用途及其原理,
閉包的定義
閉包是指內部函式總是可以訪問其所在的外部函式中宣告的變數和引數,即使在其外部函
數被回傳(壽命終結)了之后,
閉包的作用(特點)
- 函式嵌套函式
- 內部函式可以參考外部函式的引數或者變數
- 外部函式的引數和變數不會被垃圾回收,因為被內部函式參考,
閉包與全域變數
| 項 | 閉包 | 全域變數 |
| 名字是否可重復 | 不同的閉包的變數名可以重復, | 全域變數的名字不能重復, |
| 變數被誤用 | 不會被誤用, 閉包的變數只能通過給出的函式訪問,不能直接訪問, | 可能會被誤用, 比如:我寫了個函式,把一個公共變數寫到了全域變數,結果被其他人直接讀寫了,沒有用我提供的函式,這就導致了混亂, |
閉包的用途
柯里化
可以通過引數來生成不同的函式,
function makeWelcome(x) {
return function(y) {
return x + y;
};
}
let sayHello = makeWelcome("Hello,");
let sayHi = makeWelcome("Hi,");
console.log(sayHello("Tony"));
console.log(sayHi("Tony"));
結果
Hello,Tony
Hi,Tony
實作公有變數
需求:實作一個累加器,每次呼叫就增加一次,
function makeCounter(){
let count = 0;
function innerFunction(){
return count++;
}
return innerFunction;
}
let counter = makeCounter();
console.log(counter());
console.log(counter());
console.log(counter());
結果
0
1
2
快取
設想有一個處理程序很耗時的函式物件,可以將計算出來的值存盤起來,當呼叫這個函式的時候,首先在快取中查找,如果找不到,則進行計算,然后更新快取并回傳值;如果找到了,直接回傳查找到的值即可,
閉包可以做到這一點,因為它不會釋放外部的參考,從而函式內部的值可以得以保留,
本處為了簡單,直接寫讀寫快取的示例,(而不是讀不到再計算,然后存到快取),
let cache = function () {
// Map允許鍵為任意型別,如果這么寫:let storage = {},則鍵只能為字串
let storage = new Map();
return {
setCache: function (k, v) {
storage[k] = v;
},
getCache: function (k) {
return storage[k];
},
deleteCache: function (k) {
delete storage[k];
}
}
}();
cache.setCache('a', 1);
console.log(cache.getCache('a'))
結果
1
封裝(屬性私有化)
只能通過提供的閉包的形式來訪問內部變數,(此法不好,建議使用原型鏈),
let person = function(){
//變數作用域為函式內部,外部無法訪問
let name = "defaultName";
return {
getName: function(){
return name;
},
setName: function(newName){
name = newName;
}
}
}();
console.log(person.name);
console.log(person.getName());
person.setName("Hello");
console.log(person.getName());
結果
undefined
defaultName
Hello
閉包的原理
以計數器為例:
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
console.log(counter());
console.log(counter());
console.log(counter());
結果
0
1
2
每次 makeCounter() 呼叫的開始,都會創建一個新的詞法環境物件,以存盤該makeCounter 運行時的變數,
因此,我們有兩層嵌套的詞法環境:

在執行 makeCounter() 的程序中創建了一個僅占一行的嵌套函式: return count++ ,我們尚未運行它,僅創建了它,
所有的函式在“誕生”時都會記住創建它們的詞法環境,原理:所有函式都有名為 [[Environment]] 的隱藏屬性,該屬性保存了對創建該函式的詞法環境的參考:

因此, counter.[[Environment]] 有對 {count: 0} 詞法環境的參考,這就是函式記住它創建于何處的方式,與函式被在哪兒呼叫無關, [[Environment]] 參考在函式創建時被設定并永久保存,
稍后,當呼叫 counter() 時,會為該呼叫創建一個新的詞法環境,并且其外部詞法環境參考獲
取于 counter.[[Environment]] :

現在,當 counter() 中的代碼查找 count 變數時,它首先搜索自己的詞法環境(為空,因為
那里沒有區域變數),然后是外部 makeCounter() 的詞法環境,并且在哪里找到就在哪里修
改(在變數所在的詞法環境中更新變數),
這是執行后的狀態:

如果我們呼叫 counter() 多次, count 變數將在同一位置增加到 2, 3等,
垃圾收集
簡介
通常,函式呼叫完成后,會將詞法環境和其中的所有變數從記憶體中洗掉,因為現在沒有任何對它們的參考了,
與 JavaScript 中的任何其他物件一樣,詞法環境僅在可達時才會被保留在記憶體中,但是,如果有一個嵌套函式在函式結束后仍可達,則它具有參考詞法環境的[[Environment]] 屬性,
如果在函式執行完成后,詞法環境仍然可達,則此嵌套函式仍然有效,例如:
function f() {
let value = 123;
return function() {
alert(value);
}
}
// g.[[Environment]] 存盤了對相應 f() 呼叫的詞法環境的參考
let g = f();
如果多次呼叫 f() ,并且回傳的函式被保存,那么所有相應的詞法環境物件也會保留在記憶體中,例如:
function f() {
let value = Math.random();
return function () {
alert(value);
};
}
// 陣列中的 3 個函式,每個都與來自對應的 f() 的詞法環境相關聯
let arr = [f(), f(), f()];
當詞法環境物件變得不可達時,它就會死去(就像其他任何物件一樣),換句話說,它僅在至少有一個嵌套函式參考它時才存在,
在下面的代碼中,嵌套函式被洗掉后,其封閉的詞法環境(以及其中的 value )也會被從記憶體中洗掉:
function f() {
let value = 123;
return function() {
alert(value);
}
}
let g = f(); // 當 g 函式存在時,該值會被保留在記憶體中
g = null; // 現在記憶體被清理了
實際開發中的優化
正如我們所看到的,理論上當函式可達時,它外部的所有變數也都將存在,但在實際中,JavaScript 引擎會試圖優化它,它們會分析變數的使用情況,如果從代碼中可以明顯看出有未使用的外部變數,那么就會將其洗掉,
V8(Chrome,Opera)的一個重要的副作用是,此類變數在除錯中將不可用,
打開 Chrome 瀏覽器的開發者工具,并嘗試運行下面的代碼,
function f() {
let value = Math.random();
function g() {
debugger;
}
return g;
}
let g = f();
g();
當代碼執行到“debugger;”這個地方時會暫停,此時在控制臺中輸入 console.log(value);,
結果:報錯:VM146:1 Uncaught ReferenceError: value is not defined
這可能會導致有趣的除錯問題,比如:我們可以看到的是一個同名的外部變數,而不是預期的變數:
let value = "Surprise!";
function f() {
let value = "the closest value";
function g() {
debugger;
}
return g;
}
let g = f();
g();
當代碼執行到“debugger;”這個地方時會暫停,此時在控制臺中輸入 console.log(value);,
結果:輸出:Surprise,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/395139.html
標籤:其他
