從閉包函數的變量自增的角度 - 解析js垃圾回收機制

GitHubvue

前言

感受每一道均可以深刻研究下去,單獨寫一篇文章,包括不限於閉包,原型鏈,從url輸入到頁面展現過程,頁面優化,react和vue的價值等等。node

代碼實現

const times = (()=>{
  var times = 0;
  return () => times++;
})()
console.log(
  times(),
  times(),
  times(),
  times()
) // 0,1,2,3,複製代碼

原理

由於times變量一直被引用,沒有被回收,因此,每次自增1。react

更簡單的實現方式,一行代碼實現閉包

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複製代碼

深刻寫下去以前,先放出相似的代碼

一樣的執行,我把函數執行時間放到了前面,自增失敗git

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的效果
imagees6

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()調用查看內存使用狀況。github

{ 
  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)複製代碼

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 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 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屬性的那個對象如今也是零引用了
		// 它能夠被垃圾回收了複製代碼

相關文章
相關標籤/搜索