1.垃圾回收
javascript是使用垃圾回收的语言,也就是说执行环境负责在代码执行时管理内存。在c和c++语言中,跟踪内存使用对开发者来说是个很大的负担。 而javascript为开发者卸下了这个负担,通过自动内存管理实现内存分配和闲置资源回收。 基本思路很简单,确定哪个变量不会再使用,然后释放它的内存。这个过程是周期性的,既垃圾回收程序每隔一定时间就会自动运行。
分类:标记清理,引用计数
标记清理:当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。而当变量离开上下文时,也会被加上离开上下文的标记。垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中变量,以及被在上下文中的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了。
引用计数: 对每一个值都记录它被引用的次数。声明变量并给他赋一个引用值时,这个值的引用次数为1. 如果一个值又被赋给另一个变量,那么引用次数加1.如果该变量被其他变量覆盖,则引用次数减1.当一个变量的引用次数为0的时候。就可以回收了。
存在的问题:会造成循环引用(所谓循环引用,就是对象A有一个指针指向对象B,而对象B也引用了对象A.) 比如
function problem(){
let objectA = new Object();
let objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = ObjectA;
//引用数都是2 如果是标记清除,在函数结束后 都会被销毁。 但是基于引用清除。那么他们永远不会得到清理,内存也不会释放。因为他们的引用数永远不会变成0
}
2.内存管理
将内存占有量保持在一个较小的值可以让页面性能更好。优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据,如果数据不再必要,那么把它设置为null,从而释放其引用。这也叫作解除引用
function createPerson(name){
let localPerson = new Object();
localPerson.name = name;
return localPerson;
}
let globalPerson = createPerson("鲁班");
//解除globalPerson的引用
globalPerson = null;
3.隐藏类和删除操作
V8引擎在将解释后的javascrip代码编译为实际的机器码时会利用 “隐藏类” ,在运行期间,V8会将创建的对象与隐藏类关联起来。以跟踪他们的属性特征,能够共享相同隐藏类的对象性能会更好。V8会针对这种情况进行优化,但不一定能够做到。比如下面的代码;
function Article(){
this.title = "今天是个好日子!";
}
let a1 = new Article();
let a2 = new Article();
//此时V8会在后台配置,让这两个类实例共享相同的隐藏类。 因为这两个实例共享同一个构造函数和原型。
//如果你在之后添加了这行代码
a2.author = "鲁班";
//此时两个实例就会对应两个不同的隐藏类。 就会对性能产生明显影响.
//解决方案 (避免先创建再补充, 一次性声明所有的属性) 此时就会带来潜在的性能提升
function Article(author){
this.title = "今天是个好日子!";
this.author = author;
}
let a1 = new Article("鲁班");
let a2 = new Article("鲁班七号");
//delete存在的问题
function Article(author){
this.title = "今天是个好日子!";
this.author = "鲁班";
}
let a1 = new Article();
let a2 = new Article();
delete a1.author; //删除a1的author属性
//此时 它们也不再共享一个隐藏类,和动态添加属性导致的后果一样。最佳的方法是把不想要的属性设置为null
//优化后的代码
function Article(author){
this.title = "今天是个好日子!";
this.author = "鲁班";
}
let a1 = new Article();
let a2 = new Article();
a1.author = null;
4.内存泄漏
写得不好的javascript 可能出现难以察觉的内存泄漏问题,javascript内存泄漏大部分是由不合理的引用导致的。
1.全局变量造成的内存泄漏
//不用let 什么的变量导致的全局污染和内存泄漏
function setName(){
name = '鲁班';
}
//定时器造成的内存泄漏
let name = "鲁班";
setInterval(function(){
console.log(name);
},100)
5.网页生成原理
大家要理解网页性能为何不好,那就需要理解网页生成的原理
1.网页生成过程:
1.html代码转化成DOM
2.css代码转化成CSSOM(css Object Model)
3.结合DOM和CSSOM,生成一颗渲染树(包含每个节点的视觉信息)
4.生成布局(layout),讲所有的渲染树的所有节点进行平面合成。、
5.将布局绘制(paint)在屏幕上
1-3步非常快,耗时的是4-5步
生成布局(flow)和绘制(paint)这两步,合称为"渲染"(render)
2.重排和重绘
网页生成的时候,至少会渲染一次。用户访问的过程中,还会不断重新渲染。
以下三种情况,会导致网页重新渲染。、
*修改DOM
*修改样式表
*用户事件
重新渲染,就需要重新生成布局和重新绘制,前者叫做“重排”(reflow),后者叫做 “重绘(repaint)”
需要注意的是, "重绘"不一定需要"重排" ,比如改变某个网页元素的颜色,就只会触发"重绘",不会触发"重排",因为布局没有改变。但是, "重排"必然导致"重绘" ,比如改变一个网页元素的位置,就会同时触发"重排"和"重绘",因为布局改变了。
总结:重排和重绘不断触发,是导致网页性能低下的根本原因。
提高网页性能,就是要降低"重排"和"重绘"的频率和成本,尽量少触发重新渲染。
前面提到,DOM变动和样式变动,都会触发重新渲染。但是,浏览器已经很智能了,会尽量把所有的变动集中在一起,排成一个队列,然后一次性执行,尽量避免多次重新渲染。
//只会触发一次重绘和重排
box.style.color = 'blue';
box.style.marginTop = '10px';
//造成多次重排和重绘的操作 (涉及读写操作)
div.style.color = 'blue';
let margin = parseInt(div.style.marginTop);
div.style.marginTop = (margin + 10) + 'px';
//所以,从性能角度考虑,尽量不要把读操作和写操作,放在一个语句里面。
// 不好的写法
div.style.left = div.offsetLeft + 10 + "px";
div.style.top = div.offsetTop + 10 + "px";
// 好的写法
let left = div.offsetLeft;
let top = div.offsetTop;
div.style.left = left + 10 + "px";
div.style.top = top + 10 + "px";
//尽量不要一次性操作多次属性
//欠佳的做法
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top = top + "px";
// 好的做法
el.className += " theclassname";
一般规则:
1.样式表越简单,重排和重绘就越快。
2.重排和重绘的DOM元素层级越高,成本就越高。
3.table元素的重排和重绘成本,要高于div元素
6.与dom操作相关的优化细节
1.innerHTML
在进行内容添加操作的时候,特别是涉及到循环建议先追加内容 再把内容整体添加到元素中
let box = document.querySelector(".box");
console.time("hello:");
for(let i = 0;i<5000;i++){
box.innerHTML += "猪脚饭";
}
console.timeEnd("hello:");
//优化的代码
let box = document.querySelector(".box");
let str = "";
console.time("hello:");
for(let i = 0;i<5000;i++){
str += "猪脚饭";
}
box.innerHTML = str;
console.timeEnd("hello:");
2.追加元素
let box = document.querySelector(".box");
console.time("计时");
for(let i = 0;i<5000;i++){
let span = document.createElement('span');
span.innerText = "鲁班"+i;
box.append(span);
}
console.timeEnd("计时");
//优化代码 (createDocumentFragment 文档碎片)
let box = document.querySelector(".box");
let flag = document.createDocumentFragment();
for(let i = 0;i<5000;i++){
let span = document.createElement('span');
span.innerText = "鲁班"+i;
flag.append(span);
}
console.timeEnd("计时");