前言
感覺每一道都可以深入研究下去,單獨寫一篇文章,包括不限於閉包,原型鏈,從url輸入到頁面展示過程,頁面優化,react和vue的價值等等。
程式碼實現
const times = (()=>{
var times = 0;
return () => times++;
})()
console.log(
times(),
times(),
times(),
times()
) // 0,1,2,3,複製程式碼
const times = (()=>{
var times = 0;
return () => times++;
})()
console.log(
times(),
times(),
times(),
times()
) // 0,1,2,3,複製程式碼
原理
因為times變數一直被引用,沒有被回收,所以,每次自增1。
更簡單的實現方式,一行程式碼實現閉包
const times = ((times = 0)=> () => times++)()
console.log(
times(),
times(),
times(),
times()
) // 0,1,2,3複製程式碼
const times = ((times = 0)=> () => times++)()
console.log(
times(),
times(),
times(),
times()
) // 0,1,2,3複製程式碼
這並非閉包地專利, 變數放在閉包外部同樣可以實現阻止變數地垃圾回收機制
let time = 0
const times = ()=>{
let time = 10
return function(){
return time++
}
}// 根據JavaScript作用域鏈地規則,閉包內部沒有,就從外面拿變數
const a = times(); // times函式只被執行了1次,產生了一個變數 time
console.log(
a(), // 而times返回的匿名函式卻被執行了5次
a(), // 而times返回的匿名函式卻被執行了5次
a(), // 而times返回的匿名函式卻被執行了5次 其中的差別相差非常遠
a(), // 而times返回的匿名函式卻被執行了5次
a() // 而times返回的匿名函式卻被執行了5次
) // 0,1,2,3複製程式碼
let time = 0
const times = ()=>{
let time = 10
return function(){
return time++
}
}// 根據JavaScript作用域鏈地規則,閉包內部沒有,就從外面拿變數
const a = times(); // times函式只被執行了1次,產生了一個變數 time
console.log(
a(), // 而times返回的匿名函式卻被執行了5次
a(), // 而times返回的匿名函式卻被執行了5次
a(), // 而times返回的匿名函式卻被執行了5次 其中的差別相差非常遠
a(), // 而times返回的匿名函式卻被執行了5次
a() // 而times返回的匿名函式卻被執行了5次
) // 0,1,2,3複製程式碼
深入寫下去之前,先放出類似的程式碼
同樣的執行,我把函式執行時間放到了前面,自增失敗
const times = ((times = 0)=> () => times++)()(); 匿名函式只被執行了一次,同時返回函式再次執行一次
console.log(
times, // 得到匿名函式返回值, 函式只有配合()才會被執行一次麼,此處
times, // 此處沒有函式被執行
times, // 因此列印值為四個零
times
); // 0,0,0,0複製程式碼
同樣的執行,我把閉包函式執行時間放到了後面,同樣自增失敗
const times = ((times = 0)=> () => times++); time相當於宣告式函式
console.log(
times()(), // 此處外部函式執行一次,產生times變數,返回的函式再執行一次times引用次數為0
times()(), // 此處外部函式執行一次,產生times變數,返回的函式再執行一次
times()(), // 此處外部函式執行一次,產生times變數,返回的函式再執行一次
times()()
); // 0,0,0,0複製程式碼
函式[1,2,3,4,4].entires()會返回一個迭代器,一下程式碼同樣實現了類似自增1的效果
const arr = [1,2,3,3,5,6,4,78].entries()
console.log(
arr2.next().value,
arr2.next().value,
arr2.next().value,
arr2.next().value,
arr2.next().value
); // [0, 1], [1, 2], [2, 3], [3, 3], [4, 5] 迭代器返回值, 【index,value】複製程式碼
JavaScript辣雞回收機制
按照JavaScript裡垃圾回收的機制,是從root(全域性物件)開始尋找這個物件的引用是否可達,如果引用鏈斷裂,那麼這個物件就會回收。換句話說,所有物件都是point關係。引用鏈就是所謂的指標關係。
當const的過程中,宣告的那個函式會被壓入呼叫棧,執行完畢,又沒有其他地方引用它,那就會被釋放。這個瀏覽器端,挺難的,但是在nodejs端,就可以用process.memoryUsage()
呼叫檢視記憶體使用情況。
{
rss: 23560192, // 所有記憶體佔用,包括指令區和堆疊。
heapTotal: 10829824, // "堆"佔用的記憶體,包括用到的和沒用到的。
heapUsed: 4977904, // 用到的堆的部分。同時也是判斷記憶體是否洩露的標準。
external: 8608 // V8 引擎內部的 C++ 物件佔用的記憶體。
}複製程式碼
如果你想要引用,又不想影響垃圾回收機制,那就用WeakMap,WeakSet這種弱引用吧,es6的新屬性。
從引用次數來解釋為什麼變數times沒有被回收
const timeFunc = ((time = 0)=> () => time++)
var b = timeFunc(); // time 變數引用次數+1,不能被回收
console.log(b());
console.log(b());
console.log(b());複製程式碼
// 真的非常神奇,需要滿足2個條件
// 1.變數命名於返回函式外部,函式函式內部。
// 2.返回函式引用外部變數,導致外部變數無法觸發垃圾回收機制。因為引用次數>1
let timeFunc = (time = 0)=>{
return (() => time++)()
}
var b = timeFunc(); // b變數接受的是timeFunc返回的函式,由於返回函式內部有引用外部變數,故
console.log(b)
console.log(b)複製程式碼
const timeFunc = ((time = 0)=> () => time++)
var b = timeFunc(); // time 變數引用次數+1,不能被回收
console.log(b());
console.log(b());
console.log(b());複製程式碼
// 真的非常神奇,需要滿足2個條件
// 1.變數命名於返回函式外部,函式函式內部。
// 2.返回函式引用外部變數,導致外部變數無法觸發垃圾回收機制。因為引用次數>1
let timeFunc = (time = 0)=>{
return (() => time++)()
}
var b = timeFunc(); // b變數接受的是timeFunc返回的函式,由於返回函式內部有引用外部變數,故
console.log(b)
console.log(b)複製程式碼
JavaScript中的記憶體簡介(如果缺少必須的基礎知識,想要深入瞭解下去,也是比較難的吧)
像C語言這樣的高階語言一般都有低階的記憶體管理介面,比如 malloc()和free()。另一方面,JavaScript建立變數(物件,字串等)時分配記憶體,並且在不再使用它們時“自動”釋放。 後一個過程稱為垃圾回收。這個“自動”是混亂的根源,並讓JavaScript(和其他高階語言)開發者感覺他們可以不關心記憶體管理。 這是錯誤的。
閉包的本質
JavaScript閉包的形成原理是基於函式變數作用域鏈的規則 和 垃圾回收機制的引用計數規則。
JavaScript閉包的本質是記憶體洩漏,指定記憶體不釋放。
(不過根據記憶體洩漏的定義是無法使用,無法回收來說,這不是記憶體洩漏,由於只是無法回收,但是可以使用,為了使用,不讓系統回收)
JavaScript閉包的用處,私有變數,獲取對應值等,。。
記憶體生命週期
不管什麼程式語言,記憶體生命週期基本是一致的:
- 分配你所需要的記憶體
- 使用分配到的記憶體(讀、寫)
- 不需要時將其釋放歸還
在所有語言中第一和第二部分都很清晰。最後一步在底層語言中很清晰,但是在像JavaScript 等上層語言中,這一步是隱藏的、透明的。
為了不讓程式設計師操心(真的是操碎了心),JavaScript自動完成了記憶體分配工作。
var n = 123; // 給數值變數分配記憶體
var s = "azerty"; // 給字串變數分配記憶體
var obj = {
a: 1,
b: null
}; // 給物件以及其包含的值分配記憶體
var arr = [1,null,"abra"]; // 給函式(可呼叫的物件)分配記憶體
function f(a){
return a+2
} // 給函式(可呼叫物件)分配記憶體
// 為函式表示式也分配一段記憶體
document.body.addEventListener(`scroll`, function (){
console.log(`123`)
},false)複製程式碼
var n = 123; // 給數值變數分配記憶體
var s = "azerty"; // 給字串變數分配記憶體
var obj = {
a: 1,
b: null
}; // 給物件以及其包含的值分配記憶體
var arr = [1,null,"abra"]; // 給函式(可呼叫的物件)分配記憶體
function f(a){
return a+2
} // 給函式(可呼叫物件)分配記憶體
// 為函式表示式也分配一段記憶體
document.body.addEventListener(`scroll`, function (){
console.log(`123`)
},false)複製程式碼
有些函式呼叫之後會返回一個物件
var data = new Date();
var a = document.createElement(`div`);複製程式碼
var data = new Date();
var a = document.createElement(`div`);複製程式碼
有些方法是分配新變數或者新物件
var s1 = `azerty`; // 由於字串屬於引用,所以JavaScript不會為他分配新的記憶體
var s2 = `s.substr(0,3)`; // s2是一個新的字串
var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2);
// 新陣列有四個元素,是 a 連線 a2 的結果複製程式碼
var s1 = `azerty`; // 由於字串屬於引用,所以JavaScript不會為他分配新的記憶體
var s2 = `s.substr(0,3)`; // s2是一個新的字串
var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2);
// 新陣列有四個元素,是 a 連線 a2 的結果複製程式碼
命名變數的過程其實是對記憶體的寫入和釋放
辣雞回收
如上文所述,記憶體是否仍然被需要是無法判斷的,下面將介紹垃圾回收演算法以及垃圾回收的侷限性
引用
辣雞回收演算法主要依賴於引用的概念。在記憶體管理的環境中,如果一個物件有訪問另一個物件的許可權,那麼對於屬性屬於顯示引用,對於原型鏈屬於隱式引用。
引用計數垃圾收集
下面是最簡單的垃圾回收演算法。此演算法把“物件是否被需要”簡單定義為“該物件沒有被其他物件引用到”。
var o = {
a: {
b: 2
}
};
// 兩個物件被建立,一個作為另一個的屬性被引用,另一個被分配給變數o
// 很顯然,沒有一個可以被作為辣雞收集
var o2 = o; // o2變數是第二個對“這個物件”
o = 1; // 現在這個物件的原始引用o被o2替換了
var oa = o2.a; // 引用“這個物件”的a屬性
// 現在,“這個物件”有兩個引用了,一個是o2,一個是oa
o2 = `yo`; // 最初的物件現在已經是零引用了
// 它可以被垃圾回收了
// 然而他的屬性a還在被呼叫,所以不能回收
oa = null; // a屬性的那個物件現在也是零引用了
// 它可以被垃圾回收了複製程式碼