从闭包函数的变量自增的角度 - 解析js垃圾回收机制

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介:

前言

感觉每一道都可以深入研究下去,单独写一篇文章,包括不限于闭包,原型链,从url输入到页面展示过程,页面优化,react和vue的价值等等。

代码实现

const times = (()=>{
  var times = 0;
  return () => times++;
})()
console.log(
  times(),
  times(),
  times(),
  times()
) // 0123,复制代码
AI 代码解读

原理

因为times变量一直被引用,没有被回收,所以,每次自增1。

更简单的实现方式,一行代码实现闭包

const times = ((times = 0)=> () => times++)()
console.log(
  times(),
  times(),
  times(),
  times()
) // 0123复制代码
AI 代码解读

这并非闭包地专利, 变量放在闭包外部同样可以实现阻止变量地垃圾回收机制

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复制代码
AI 代码解读

深入写下去之前,先放出类似的代码

同样的执行,我把函数执行时间放到了前面,自增失败

const times = ((times = 0)=> () => times++)()();  匿名函数只被执行了一次,同时返回函数再次执行一次
console.log(
  times,   // 得到匿名函数返回值, 函数只有配合()才会被执行一次么,此处
  times,   // 此处没有函数被执行
  times,   // 因此打印值为四个零
  times
); // 0000复制代码
AI 代码解读

同样的执行,我把闭包函数执行时间放到了后面,同样自增失败

const times = ((times = 0)=> () => times++); time相当于声明式函数
console.log(
  times()(),    // 此处外部函数执行一次,产生times变量,返回的函数再执行一次times引用次数为0
  times()(),    // 此处外部函数执行一次,产生times变量,返回的函数再执行一次
  times()(),    // 此处外部函数执行一次,产生times变量,返回的函数再执行一次
  times()()
); // 0000复制代码
AI 代码解读

函数[1,2,3,4,4].entires()会返回一个迭代器,一下代码同样实现了类似自增1的效果
image

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】复制代码
AI 代码解读

JavaScript辣鸡回收机制

按照JavaScript里垃圾回收的机制,是从root(全局对象)开始寻找这个对象的引用是否可达,如果引用链断裂,那么这个对象就会回收。换句话说,所有对象都是point关系。引用链就是所谓的指针关系。
当const的过程中,声明的那个函数会被压入调用栈,执行完毕,又没有其他地方引用它,那就会被释放。这个浏览器端,挺难的,但是在nodejs端,就可以用process.memoryUsage()调用查看内存使用情况。

{ 
  rss: 23560192,         // 所有内存占用,包括指令区和堆栈。
  heapTotal: 10829824,   // "堆"占用的内存,包括用到的和没用到的。
  heapUsed: 4977904,     // 用到的堆的部分。同时也是判断内存是否泄露的标准。
  external: 8608         // V8 引擎内部的 C++ 对象占用的内存。
}复制代码
AI 代码解读

如果你想要引用,又不想影响垃圾回收机制,那就用WeakMap,WeakSet这种弱引用吧,es6的新属性。

从引用次数来解释为什么变量times没有被回收

const timeFunc = ((time = 0)=> () => time++)
var b = timeFunc();   // time 变量引用次数+1,不能被回收
console.log(b());
console.log(b());
console.log(b());复制代码
AI 代码解读
// 真的非常神奇,需要满足2个条件
// 1.变量命名于返回函数外部,函数函数内部。
// 2.返回函数引用外部变量,导致外部变量无法触发垃圾回收机制。因为引用次数>1
let timeFunc = (time = 0)=>{
  return (() => time++)()
}
var b = timeFunc();  // b变量接受的是timeFunc返回的函数,由于返回函数内部有引用外部变量,故
console.log(b)
console.log(b)复制代码
AI 代码解读

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)复制代码
AI 代码解读

有些函数调用之后会返回一个对象

var data = new Date();
var a = document.createElement('div');复制代码
AI 代码解读

有些方法是分配新变量或者新对象

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 的结果复制代码
AI 代码解读

命名变量的过程其实是对内存的写入和释放

辣鸡回收

如上文所述,内存是否仍然被需要是无法判断的,下面将介绍垃圾回收算法以及垃圾回收的局限性

引用

辣鸡回收算法主要依赖于引用的概念。在内存管理的环境中,如果一个对象有访问另一个对象的权限,那么对于属性属于显示引用,对于原型链属于隐式引用。

引用计数垃圾收集

下面是最简单的垃圾回收算法。此算法把“对象是否被需要”简单定义为“该对象没有被其他对象引用到”。

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属性的那个对象现在也是零引用了
		// 它可以被垃圾回收了复制代码
AI 代码解读


原文发布时间:2018-6-20
原文作者: Object点assign
本文来源 掘金如需转载请紧急联系作者
目录
打赏
0
0
0
0
15465
分享
相关文章
金融数据分析:解析JavaScript渲染的隐藏表格
本文详解了如何使用Python与Selenium结合代理IP技术,从金融网站(如东方财富网)抓取由JavaScript渲染的隐藏表格数据。内容涵盖环境搭建、代理配置、模拟用户行为、数据解析与分析等关键步骤。通过设置Cookie和User-Agent,突破反爬机制;借助Selenium等待页面渲染,精准定位动态数据。同时,提供了常见错误解决方案及延伸练习,帮助读者掌握金融数据采集的核心技能,为投资决策提供支持。注意规避动态加载、代理验证及元素定位等潜在陷阱,确保数据抓取高效稳定。
56 17
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
全网最全情景,深入浅出解析JavaScript数组去重:数值与引用类型的全面攻略
如果是基础类型数组,优先选择 Set。 对于引用类型数组,根据需求选择 Map 或 JSON.stringify()。 其余情况根据实际需求进行混合调用,就能更好的实现数组去重。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
JavaScript基础——JavaScript变量名称命名规范
JavaScript变量命名规范是编写高质量代码的重要部分。通过遵循基本规则、使用常见命名约定并应用最佳实践,可以提高代码的可读性和可维护性。希望本文能帮助开发者在日常编程中更好地理解和应用JavaScript变量命名规范,从而编写出更清晰、更可靠的代码。
57 11
JavaWeb JavaScript ③ JS的流程控制和函数
通过本文的详细介绍,您可以深入理解JavaScript的流程控制和函数的使用,进而编写出高效、可维护的代码。
77 32
最细最有条理解析:事件循环(消息循环)是什么?为什么JS需要异步
度一教育的袁进老师谈到他的理解:单线程是异步产生的原因,事件循环是异步的实现方式。 本质是因为渲染进程因为计算机图形学的限制,只能是单线程。所以需要“异步”这个技术思想来解决页面阻塞的问题,而“事件循环”是实现“异步”这个技术思想的最主要的技术手段。 但事件循环并不是全部的技术手段,比如Promise,虽然受事件循环管理,但是如果没有事件循环,单一Promise依然能实现异步不是吗? 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
柯里化是一种强大的函数式编程技术,它通过将函数分解为单参数形式,实现了灵活性与可复用性的统一。无论是参数复用、延迟执行,还是函数组合,柯里化都为现代编程提供了极大的便利。 从 Redux 的选择器优化到复杂的数据流处理,再到深度嵌套的函数优化,柯里化在实际开发中展现出了非凡的价值。如果你希望编写更简洁、更优雅的代码,柯里化无疑是一个值得深入学习和实践的工具。从简单的实现到复杂的应用,希望这篇博客能为你揭开柯里化的奥秘,助力你的开发之旅! 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
盘点JavaScript中所有声明变量的方式及特性
本文详细介绍了JavaScript中变量定义的多种方式,包括传统的`var`、`let`和`const`,以及通过`this`、`window`、`top`等对象定义变量的方法。每种方式都有其独特的语法和特性,并附有代码示例说明。推荐使用`let`和`const`以避免作用域和提升问题,谨慎使用`window`和`top`定义全局变量,不建议使用隐式全局变量。掌握这些定义方式有助于编写更健壮的JS代码。
60 11
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
149 17
除了 Generator 函数,还有哪些 JavaScript 异步编程解决方案?
【10月更文挑战第30天】开发者可以根据具体的项目情况选择合适的方式来处理异步操作,以实现高效、可读和易于维护的代码。

热门文章

最新文章

推荐镜像

更多
目录
目录