这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战web
限制大小算法
64 位为 1.4GB,32 位为 0.7GB浏览器
限制缘由缓存
V8 之因此限制了内存的大小,表面上的缘由是 V8 最初是做为浏览器的 JavaScript 引擎而设计,不太可能遇到大量内存的场景,而深层次的缘由则是因为 V8 的垃圾回收机制的限制。因为 V8 须要保证 JavaScript 应用逻辑与垃圾回收器所看到的不同,V8 在执行垃圾回收时会阻塞 JavaScript 应用逻辑,直到垃圾回收结束再从新执行 JavaScript 应用逻辑,这种行为被称为“全停顿”(stop-the-world)。若 V8 的堆内存为 1.5GB,V8 作一次小的垃圾回收须要 50ms 以上,作一次非增量式的垃圾回收甚至要 1 秒以上。这样浏览器将在 1s 内失去对用户的响应,形成假死现象。若是有动画效果的话,动画的展示也将显著受到影响。markdown
新生代的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象。闭包
V8 引擎的新生代内存大小 32MB(64 位)、16MB(32 位),老生代内存大小为 1400MB(64 位)、700MB( 32 位)。app
将新生代对象移到老生代dom
晋升条件jsp
新生代区域,采用复制算法, 所以其每时每刻内部都有空闲空间的存在(为了完成 From 到 To 的对象复制),可是新生代区域空间较小(32M)且被一分为二,因此这种空间上的浪费也是比较微不足道的。oop
老生代因其空间较大(1.4G),若是一样采用一分为二的作法则对空间大小是比较浪费,且老生代空间较大,存放对对象也较多,若是进行复制算法,则其消耗对时间也会更大。也就是是否使用复制算法来进行垃圾回收,是一个时间 T 关于内存大小的关系,当内存大小较小时,使用复制算法消耗的时间是比较短的,而当内存较大时,采用复制算法对时间对消耗也就更大。
增量标记
因为全停顿会形成了浏览器一段时间无响应,因此 V8 使用了一种增量标记的方式,将完整的标记拆分红不少部分,每作完一部分就停下来,让 JS 的应用逻辑执行一会,这样垃圾回收与应用逻辑交替完成。通过增量标记的改进后,垃圾回收的最大停顿时间能够减小到原来的 1/6 左右
惰性清理
因为标记完成后,全部的对象都已经被标记,不是死对象就是活对象,堆上多少空间格局已经肯定。咱们能够没必要着急释放那些死对象所占用的空间,而延迟清理过程的执行。垃圾回收器能够根据须要逐一清理死对象所占用的内存空间
其余
V8 后续还引入了增量式整理(incremental compaction),以及并行标记和并行清理,经过并行利用多核 CPU 来提高垃圾回收的性能
这里以 Google 浏览器为例,使用 Shift + Esc 唤起 Google 浏览器自带的任务管理器
模拟内存泄漏
在任务管理器里能够看到 JavaScript 内存持续上升
document.body.innerHTML = `<button id="add">add</button>`;
document.getElementById('add').addEventListener('click', function (e) {
simulateMemoryLeak();
});
let result = [];
function simulateMemoryLeak() {
setInterval(function () {
result.push(new Array(1000000).join('x'));
document.body.innerHTML = result;
}, 100);
}
复制代码
这里以 Google 浏览器为例,使用 F12 开启调式,选择 Performance,点击 record(录制),进行页面操做,点击 stop 结束录制以后,开启内存勾选,拖动截图到指定时间段查看发生内存问题时候到页面展现,并定位问题。同时能够查看对应出现红点到执行脚本,定位问题代码。
这里以 Google 浏览器为例,在页面上进行相关操做后,使用 F12 开启调式,选择 Memory,点击 Take snapshot(拍照),在快照中查找 Detached HTMLElement,回到代码中查找对应的分离 dom 存在的代码,在相关操做代码以后,对分离 dom 进行释放,防止内存泄漏。
只有页面的 DOM 树或 JavaScript 代码再也不引用 DOM 节点时,DOM 节点才会被做为垃圾进行回收。 若是某个节点已从 DOM 树移除,但某些 JavaScript 仍然引用它,咱们称此节点为“已分离”。已分离的 DOM 节点是内存泄漏的常见缘由。
模拟已分离 DOM 节点
document.body.innerHTML = `<button id="add">add</button>`;
document.getElementById('add').addEventListener('click', function (e) {
create();
});
let detachedTree;
function create() {
let ul = document.createElement('ul');
for (let i = 0; i < 10; i++) {
let li = document.createElement('li');
ul.appendChild(li);
}
detachedTree = ul;
}
复制代码
基于 Benchmark.js
上图能够看出,test2 的性能要比 test1 的性能要好,从而得知,全局变量的执行速度,访问速度要低于局部变量
上图能够看出,test2 的性能要比 test1 的性能要好,从而得知,缓存全局变量后使用能够提高性能
上图能够看出,test2 的性能要比 test1 的性能要好,从而得知,经过原型对象添加方法与直接在对象上添加成员方法相比,原型对象上的属性访问速度较快。
闭包特色
function foo() {
let name = 'heath';
function fn() {
console.log(name);
}
return fn;
}
let a = foo();
a();
复制代码
闭包使用不当很容易出现内存泄漏
function f5() {
// el 引用了全局变量document,假设btn节点被删除后,由于这里被引用着,因此这里不会被垃圾回收,致使内存泄漏
let el = document.getElementById('btn');
el.onclick = function (e) {
console.log(e.id);
};
}
f5();
function f6() {
// el 引用了全局变量document,假设btn节点被删除后,由于这里被引用着,因此这里不会被垃圾回收,致使内存泄漏
let el = document.getElementById('btn');
el.onclick = function (e) {
console.log(e.id);
};
el = null; // 咱们这里手动将el内存释放,从而当btn节点被删除后,能够被垃圾回收
}
f6();
复制代码
JavaScript 中的面向对象
上图能够看出,test2 的性能要比 test1 的性能要好很多,从而得知,直接访问属性,会比经过方法访问属性速度来的快。
上图能够看出,loop 遍历速度 forEach > 优化 for > for of > for > for in
upload-images.jianshu.io/upload_imag… 上图能够看出,节点克隆(cloneNode)生成节点速度要快于建立节点。
上图能够看出,字面量声明的数据生成速度要快于单独属性赋值行为生成的数据。
公众号:
小何成长
,佛系更文,都是本身曾经踩过的坑或者是学到的东西有兴趣的小伙伴欢迎关注我哦,我是:
何小玍。
你们一块儿进步鸭