菜鸟文章:js性能优化

59 阅读6分钟

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("计时");