从闭包函数的变量自增的角度 - 解析js垃圾回收机制
GitHub
前言
感覺(jué)每一道都可以深入研究下去,單獨(dú)寫一篇文章,包括不限于閉包,原型鏈,從url輸入到頁(yè)面展示過(guò)程,頁(yè)面優(yōu)化,react和vue的價(jià)值等等。
代碼實(shí)現(xiàn)
const times = (()=>{var times = 0;return () => times++; })() console.log(times(),times(),times(),times() ) // 0,1,2,3,復(fù)制代碼原理
因?yàn)閠imes變量一直被引用,沒(méi)有被回收,所以,每次自增1。
更簡(jiǎn)單的實(shí)現(xiàn)方式,一行代碼實(shí)現(xiàn)閉包
const times = ((times = 0)=> () => times++)() console.log(times(),times(),times(),times() ) // 0,1,2,3復(fù)制代碼這并非閉包地專利, 變量放在閉包外部同樣可以實(shí)現(xiàn)阻止變量地垃圾回收機(jī)制
let time = 0 const times = ()=>{let time = 10return function(){return time++} }// 根據(jù)JavaScript作用域鏈地規(guī)則,閉包內(nèi)部沒(méi)有,就從外面拿變量const a = times(); // times函數(shù)只被執(zhí)行了1次,產(chǎn)生了一個(gè)變量 time console.log(a(), // 而times返回的匿名函數(shù)卻被執(zhí)行了5次a(), // 而times返回的匿名函數(shù)卻被執(zhí)行了5次a(), // 而times返回的匿名函數(shù)卻被執(zhí)行了5次 其中的差別相差非常遠(yuǎn)a(), // 而times返回的匿名函數(shù)卻被執(zhí)行了5次a() // 而times返回的匿名函數(shù)卻被執(zhí)行了5次 ) // 0,1,2,3復(fù)制代碼深入寫下去之前,先放出類似的代碼
同樣的執(zhí)行,我把函數(shù)執(zhí)行時(shí)間放到了前面,自增失敗
const times = ((times = 0)=> () => times++)()(); 匿名函數(shù)只被執(zhí)行了一次,同時(shí)返回函數(shù)再次執(zhí)行一次 console.log(times, // 得到匿名函數(shù)返回值, 函數(shù)只有配合()才會(huì)被執(zhí)行一次么,此處times, // 此處沒(méi)有函數(shù)被執(zhí)行times, // 因此打印值為四個(gè)零times ); // 0,0,0,0復(fù)制代碼同樣的執(zhí)行,我把閉包函數(shù)執(zhí)行時(shí)間放到了后面,同樣自增失敗
const times = ((times = 0)=> () => times++); time相當(dāng)于聲明式函數(shù) console.log(times()(), // 此處外部函數(shù)執(zhí)行一次,產(chǎn)生times變量,返回的函數(shù)再執(zhí)行一次times引用次數(shù)為0times()(), // 此處外部函數(shù)執(zhí)行一次,產(chǎn)生times變量,返回的函數(shù)再執(zhí)行一次times()(), // 此處外部函數(shù)執(zhí)行一次,產(chǎn)生times變量,返回的函數(shù)再執(zhí)行一次times()() ); // 0,0,0,0復(fù)制代碼函數(shù)[1,2,3,4,4].entires()會(huì)返回一個(gè)迭代器,一下代碼同樣實(shí)現(xiàn)了類似自增1的效果
JavaScript辣雞回收機(jī)制
按照J(rèn)avaScript里垃圾回收的機(jī)制,是從root(全局對(duì)象)開始尋找這個(gè)對(duì)象的引用是否可達(dá),如果引用鏈斷裂,那么這個(gè)對(duì)象就會(huì)回收。換句話說(shuō),所有對(duì)象都是point關(guān)系。引用鏈就是所謂的指針關(guān)系。
當(dāng)const的過(guò)程中,聲明的那個(gè)函數(shù)會(huì)被壓入調(diào)用棧,執(zhí)行完畢,又沒(méi)有其他地方引用它,那就會(huì)被釋放。這個(gè)瀏覽器端,挺難的,但是在nodejs端,就可以用process.memoryUsage()調(diào)用查看內(nèi)存使用情況。
如果你想要引用,又不想影響垃圾回收機(jī)制,那就用WeakMap,WeakSet這種弱引用吧,es6的新屬性。
從引用次數(shù)來(lái)解釋為什么變量times沒(méi)有被回收
const timeFunc = ((time = 0)=> () => time++) var b = timeFunc(); // time 變量引用次數(shù)+1,不能被回收 console.log(b()); console.log(b()); console.log(b());復(fù)制代碼// 真的非常神奇,需要滿足2個(gè)條件 // 1.變量命名于返回函數(shù)外部,函數(shù)函數(shù)內(nèi)部。 // 2.返回函數(shù)引用外部變量,導(dǎo)致外部變量無(wú)法觸發(fā)垃圾回收機(jī)制。因?yàn)橐么螖?shù)>1 let timeFunc = (time = 0)=>{return (() => time++)() } var b = timeFunc(); // b變量接受的是timeFunc返回的函數(shù),由于返回函數(shù)內(nèi)部有引用外部變量,故 console.log(b) console.log(b)復(fù)制代碼JavaScript中的內(nèi)存簡(jiǎn)介(如果缺少必須的基礎(chǔ)知識(shí),想要深入了解下去,也是比較難的吧)
像C語(yǔ)言這樣的高級(jí)語(yǔ)言一般都有低級(jí)的內(nèi)存管理接口,比如 malloc()和free()。另一方面,JavaScript創(chuàng)建變量(對(duì)象,字符串等)時(shí)分配內(nèi)存,并且在不再使用它們時(shí)“自動(dòng)”釋放。 后一個(gè)過(guò)程稱為垃圾回收。這個(gè)“自動(dòng)”是混亂的根源,并讓JavaScript(和其他高級(jí)語(yǔ)言)開發(fā)者感覺(jué)他們可以不關(guān)心內(nèi)存管理。 這是錯(cuò)誤的。
閉包的本質(zhì)
JavaScript閉包的形成原理是基于函數(shù)變量作用域鏈的規(guī)則 和 垃圾回收機(jī)制的引用計(jì)數(shù)規(guī)則。
JavaScript閉包的本質(zhì)是內(nèi)存泄漏,指定內(nèi)存不釋放。
(不過(guò)根據(jù)內(nèi)存泄漏的定義是無(wú)法使用,無(wú)法回收來(lái)說(shuō),這不是內(nèi)存泄漏,由于只是無(wú)法回收,但是可以使用,為了使用,不讓系統(tǒng)回收)
JavaScript閉包的用處,私有變量,獲取對(duì)應(yīng)值等,。。
內(nèi)存生命周期
不管什么程序語(yǔ)言,內(nèi)存生命周期基本是一致的:
- 分配你所需要的內(nèi)存
- 使用分配到的內(nèi)存(讀、寫)
- 不需要時(shí)將其釋放\歸還
在所有語(yǔ)言中第一和第二部分都很清晰。最后一步在底層語(yǔ)言中很清晰,但是在像JavaScript 等上層語(yǔ)言中,這一步是隱藏的、透明的。
為了不讓程序員操心(真的是操碎了心),JavaScript自動(dòng)完成了內(nèi)存分配工作。
var n = 123; // 給數(shù)值變量分配內(nèi)存 var s = "azerty"; // 給字符串變量分配內(nèi)存var obj = {a: 1,b: null }; // 給對(duì)象以及其包含的值分配內(nèi)存var arr = [1,null,"abra"]; // 給函數(shù)(可調(diào)用的對(duì)象)分配內(nèi)存function f(a){return a+2 } // 給函數(shù)(可調(diào)用對(duì)象)分配內(nèi)存// 為函數(shù)表達(dá)式也分配一段內(nèi)存 document.body.addEventListener('scroll', function (){console.log('123') },false)復(fù)制代碼有些函數(shù)調(diào)用之后會(huì)返回一個(gè)對(duì)象
var data = new Date(); var a = document.createElement('div');復(fù)制代碼有些方法是分配新變量或者新對(duì)象
var s1 = 'azerty'; // 由于字符串屬于引用,所以JavaScript不會(huì)為他分配新的內(nèi)存 var s2 = 's.substr(0,3)'; // s2是一個(gè)新的字符串var a = ["ouais ouais", "nan nan"]; var a2 = ["generation", "nan nan"]; var a3 = a.concat(a2); // 新數(shù)組有四個(gè)元素,是 a 連接 a2 的結(jié)果復(fù)制代碼命名變量的過(guò)程其實(shí)是對(duì)內(nèi)存的寫入和釋放
辣雞回收
如上文所述,內(nèi)存是否仍然被需要是無(wú)法判斷的,下面將介紹垃圾回收算法以及垃圾回收的局限性
引用
辣雞回收算法主要依賴于引用的概念。在內(nèi)存管理的環(huán)境中,如果一個(gè)對(duì)象有訪問(wèn)另一個(gè)對(duì)象的權(quán)限,那么對(duì)于屬性屬于顯示引用,對(duì)于原型鏈屬于隱式引用。
引用計(jì)數(shù)垃圾收集
下面是最簡(jiǎn)單的垃圾回收算法。此算法把“對(duì)象是否被需要”簡(jiǎn)單定義為“該對(duì)象沒(méi)有被其他對(duì)象引用到”。
var o = {a: {b: 2} }; // 兩個(gè)對(duì)象被創(chuàng)建,一個(gè)作為另一個(gè)的屬性被引用,另一個(gè)被分配給變量o // 很顯然,沒(méi)有一個(gè)可以被作為辣雞收集var o2 = o; // o2變量是第二個(gè)對(duì)“這個(gè)對(duì)象”o = 1; // 現(xiàn)在這個(gè)對(duì)象的原始引用o被o2替換了var oa = o2.a; // 引用“這個(gè)對(duì)象”的a屬性// 現(xiàn)在,“這個(gè)對(duì)象”有兩個(gè)引用了,一個(gè)是o2,一個(gè)是oao2 = 'yo'; // 最初的對(duì)象現(xiàn)在已經(jīng)是零引用了// 它可以被垃圾回收了// 然而他的屬性a還在被調(diào)用,所以不能回收oa = null; // a屬性的那個(gè)對(duì)象現(xiàn)在也是零引用了// 它可以被垃圾回收了復(fù)制代碼總結(jié)
以上是生活随笔為你收集整理的从闭包函数的变量自增的角度 - 解析js垃圾回收机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: BMP图像信息隐藏
- 下一篇: Swift4.1第二章 The Basi