js内存深入学习(一)
一. 内存空间储存
某些情况下,调用堆栈中函数调用的数量超出了调用堆栈的实际大小,浏览器会抛出一个错误终止运行。这个就涉及到内存问题了。
1. 数据结构类型
- 栈: 后进先出(LIFO)的数据结构
- 堆: 一种树状结构
- 队列: 先进先出(FIFO)的数据结构
2. 变量的存放
JS内存空间分为栈(stack)、堆(heap)、池(一般也会归类为栈中)。 其中栈存放变量,堆存放复杂对象,池存放常量,所以也叫常量池。
1、基本类型 --> 保存在栈内存中,因为这些类型在内存中分别占有固定大小的空间,通过按值来访问。基本类型一共有6种:Undefined、Null、Boolean、Number 、String和Symbol
2、引用类型 --> 保存在堆内存中,因为这种值的大小不固定,因此不能把它们保存到栈内存中,但内存地址大小的固定的,因此保存在堆内存中,在栈内存中存放的只是该对象的访问地址。当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。
在计算机的数据结构中,栈比堆的运算速度快,Object是一个复杂的结构且可以扩展:数组可扩充,对象可添加属性,都可以增删改查。将他们放在堆中是为了不影响栈的效率。而是通过引用的方式查找到堆中的实际对象再进行操作。所以查找引用类型值的时候先去栈查找再去堆查找。
例子:
<script>
var a = {n:1};
var b = a;
a.x = a = {n:2};
console.log(a.x);// --> undefined
console.log(b.x);// --> {n:2}
</script>
解析:
var a = {n:1}; var b = a;
在这里a指向了一个对象{n:1}(我们姑且称它为对象A),b指向了a所指向的对象,也就是说,在这时候a和b都是指向对象A的。a.x = a = {n:2};
- 我们知道js的赋值运算顺序永远都是从右往左的,不过由于“.”是优先级最高的运算符,所以这行代码先“计算”了a.x。a指向的对象{n:1}新增了属性x(虽然这个x是undefined的)
- 依循“从右往左”的赋值运算顺序先执行 a={n:2} ,这时候,a指向的对象发生了改变,变成了新对象{n:2}(我们称为对象B)
- 接着继续执行 a.x=a, 由于一开始js已经先计算了a.x,便已经解析了这个a.x是对象A的x,所以在同一条公式的情况下再回来给a.x赋值,所以应理解为对象A的属性x指向了对象B。
另外, 闭包中的变量并不保存中栈内存中,而是保存在堆内存中,这也就解释了函数之后之后为什么闭包还能引用到函数内的变量。
function A() {
let a = 1
function B() {
console.log(a)
}
return B
}
函数 A 弹出调用栈后,函数 A 中的变量这时候是存储在堆上的,所以函数B依旧能引用到函数A中的变量。现在的 JS 引擎可以通过逃逸分析辨别出哪些变量需要存储在堆上,哪些需要存储在栈上。
二. 内存空间管理
1. 内存生命周期
JavaScript的内存生命周期是
1、分配你所需要的内存
2、使用分配到的内存(读、写)
3、不需要时将其释放、归还
JavaScript有自动垃圾收集机制,垃圾收集器会每隔一段时间就执行一次释放操作,找出那些不再继续使用的值,然后释放其占用的内存。
- 局部变量和全局变量的销毁
- 局部变量:局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了,因此垃圾收集器很容易做出判断并回收。
- 全局变量:全局变量什么时候需要自动释放内存空间则很难判断,所以在开发中尽量避免使用全局变量。
- 以Google的V8引擎为例,V8引擎中所有的JS对象都是通过堆来进行内存分配的
- 初始分配:当声明变量并赋值时,V8引擎就会在堆内存中分配给这个变量。
- 继续申请:当已申请的内存不足以存储这个变量时,V8引擎就会继续申请内存,直到堆的大小达到了V8引擎的内存上限为止。
- V8引擎对堆内存中的JS对象进行分代管理
- 新生代:存活周期较短的JS对象,如临时变量、字符串等。
- 老生代:经过多次垃圾回收仍然存活,存活周期较长的对象,如主控制器、服务器对象等。
2. 垃圾回收算法
- 2.1 引用计数(现代浏览器不再使用)
引用计数算法简单理解,就是看一个对象是否有指向它的引用。如果没有其他对象指向它了,说明该对象已经不再需要了。
// 创建一个对象person,他有两个指向属性age和name的引用
var person = {
age: 12,
name: 'aaaa'
}; person.name = null; // 虽然name设置为null,但因为person对象还有指向name的引用,因此name不会回收 var p = person;
person = 1; //原来的person对象被赋值为1,但因为有新引用p指向原person对象,因此它不会被回收 p = null; //原person对象已经没有引用,很快会被回收
引用计数有一个致命的问题,那就是循环引用
如果两个对象相互引用,尽管他们已不再使用,但是垃圾回收器不会进行回收,最终可能会导致内存泄露。
function cycle() {
var o1 = {};
var o2 = {};
o1.a = o2;
o2.a = o1; return "cycle reference!"
} cycle();
cycle函数执行完成之后,对象o1和o2实际上已经不再需要了,但根据引用计数的原则,他们之间的相互引用依然存在,因此这部分内存不会被回收。所以现代浏览器不再使用这个算法。
但是IE依旧使用,如下,变量div有事件处理函数的引用,同时事件处理函数也有div的引用,因为div变量可在函数内被访问,所以循环引用就出现了。
var div = document.createElement("div");
div.onclick = function() {
console.log("click");
};
- 2.2 标记清除(常用)
标记清除算法将“不再使用的对象”定义为“无法到达的对象”。即从根部(在JS中就是全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,保留。那些从根部出发无法触及到的对象被标记为不再使用,稍后进行回收。所以像上面的例子,虽然是循环引用,但从全局来说并没有被使用到,所以就可以正确被垃圾回收处理了。
算法由以下几步组成:
- 垃圾回收器创建了一个“roots”列表。roots通常是代码中全局变量的引用。JavaScript 中,“window”对象是一个全局变量,被当作 root 。window对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是圾);
- 所有的 roots 被检查和标记为激活(即不是垃圾)。所有的子对象也被递归地查。从 root 开始的所有对象如果是可达的,它就不被当作垃圾。
- 所有未被标记的内存会被当做垃圾,收集器现在可以释放内存,归还给操作系了。
对于主流浏览器来说,只需要切断需要回收的对象与根部的联系。但可能还存在着与DOM元素绑定有关的内存问题:
email.message = document.createElement(“div”);
displayList.appendChild(email.message); // 稍后从displayList中清除DOM元素
displayList.removeAllChildren();
上面代码中,div元素已经从DOM树中清除,但是该div元素还绑定在email对象中,所以如果email对象存在,那么该div元素就会一直保存在内存中。如果不再需要使用的话,需要手动设置email.message = null。
另外ES6 新出的两种数据结构:WeakSet 和 WeakMap,表示这是弱引用,它们对于值的引用都是不计入垃圾回收机制的。
const wm = new WeakMap();
const element = document.getElementById('example'); wm.set(element, 'some information');
wm.get(element) // "some information"
先新建一个 Weakmap 实例,然后将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制。
js内存深入学习(一)的更多相关文章
- js内存深入学习(二)
继上一篇文章 js内存深入学习(一) 3. 内存泄漏 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存.否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃. 对于不再用到的内 ...
- JS内存空间详细图解
JS内存空间详细图解 变量对象与堆内存 var a = 20; var b = 'abc'; var c = true; var d = { m: 20 } 因为JavaScript具有自动垃圾回收机 ...
- 前端学习:JS面向对象知识学习(图解)
前端学习:JS面向对象知识学习(图解) 前端学习:JS(面向对象)代码笔记 JS面向对象图解知识全览 创建类和对象 方式1:使用Object()函数 方式2:使用自变量 方式3:使用工厂函数 创建多个 ...
- 浏览器中js执行机制学习笔记
浏览器中js执行机制学习笔记 RiverSouthMan关注 0.0772019.05.15 20:56:37字数 872阅读 291 同步任务 当一个脚本第一次执行的时候,js引擎会解析这段代码,并 ...
- js callee,caller学习
原文地址:js callee,caller学习 /* * caller 返回一个对函数的引用,该函数调用了当前函数. * 如果函数是由顶层调用的,那么 caller包含的就是 null . * 如果在 ...
- 【09-23】js原型继承学习笔记
js原型继承学习笔记 function funcA(){ this.a="prototype a"; } var b=new funcA(); b.a="object a ...
- Knockout.Js官网学习(系列)
1.Knockout.Js官网学习(简介) 2.Knockout.Js官网学习(监控属性Observables) Knockout.Js官网学习(数组observable) 3.Knockout.Js ...
- json2.js的初步学习与了解
json2.js的初步学习与了解,想要学习json的朋友可以参考下. json2.js的初步学习与了解 1.)该js的下载地址是:http://www.json.org/json2.js 2.)在页面 ...
- JS内存泄露常见原因
详细内容请点击 分享的笔记本-前端 开发中,我们常遇见的一些关于js内存泄露的问题,有时候我们常常会找半天找不出原因,这里给大家介绍简单便捷的方法 1.闭包上下文绑定后没有释放: 2.观察者模式在 ...
随机推荐
- 小白学习随笔the first week
The First Week 一.计算机基础 1.软件(应用程序) 2.解释器/编译器 - 解释型语言:将代码每一行传递给计算机一行,常用编程语言python,PHP,Ruby. - 编译型语言:将代 ...
- 虚拟机下Ubuntu扩容及磁盘重新分区-Gparted
转自: https://blog.csdn.net/timsley/article/details/50742755
- [LeetCode] Car Fleet 车队
N cars are going to the same destination along a one lane road. The destination is target miles awa ...
- promise的理解
为什么会有promise,他的作用是什么? promise主要是为了解决js中多个异步回调难以维护和控制的问题. 什么是promise? 从图中,我们可以看出,Promise是一个函数,这个函数上有在 ...
- Java语法细节 - try_finally和set相关
目录 try-catch-finally的关系 TreeSet和HashSet的实现原理和区别 BigDecimal中的一些坑 当BigDecimal在HashSet/HashMap和TreeSet/ ...
- 2019年我的OKR(objectives and key results)目标与关键成果法
一.学习目标目标1:每天必背诵英语单词(可可英语App,百词斩App),掌握英语的基本从句语法,听力训练必备(英语四六级听力题,主要是为通过四六级考试)目标2:考研准备,高数(大一上下册),现代(大 ...
- 十五、过滤器(Filter)
过滤器(Filter) 过滤器概述 1 什么是过滤器 过滤器JavaWeb三大组件之一,它与Servlet很相似!不它过滤器是用来拦截请求的,而不是处理请求的. 当用户请求某个Servlet时,会先执 ...
- Windows下SQLMAP的安装图解
第一步:BurpSuite扫描工具安装 //来源:http://www.mamicode.com/info-detail-563355.html Burp Suite 是用于渗透测试web 应用程序的 ...
- Spring AOP实现 Bean字段合法性校验
使用SpringAop 验证方法参数是否合法 先定义两个注解类ValidateGroup 和 ValidateFiled ValidateGroup .java package com.zf.an ...
- [Swift]LeetCode16. 最接近的三数之和 | 3Sum Closest
Given an array nums of n integers and an integer target, find three integers in nums such that the s ...