zoukankan      html  css  js  c++  java
  • JavaScript性能优化

    0. 提要

      自从JavaScript诞生以来,用这门语言编写网页的开发人员有了极大的增长。与此同时,JavaScript代码的执行效率也越来越受到关注。因为JavaScript最初是一个解释型语言,执行速度要比编译型语言慢得多。Chrome是第一款内置优化引擎,将JavaScript变异成本地代码的浏览器。此后,主流浏览器纷纷效仿,陆续实现了JavaScript的编译执行。

      即使到了编译执行JavaScript的新阶段,仍然会存在低效率的代码。不过,还是有一些方式可以改进代码的整体性能的。

      主要有以下四个方面:

    1. 作用域方面。
    2. 选择方法方面。
    3. 最小化语句数方面。
    4. 优化DOM交互方面。

    1.作用域方面

    1.1避免全局查找

      可能优化脚本性能最重要的就是注意全局查找。使用全局变量和函数肯定要比局部的开销更大,因为要涉及作用域链上的查找。请看以下函数:

    function updateUI(){
    var imgs = document.getElementsByTagName("img");
    for (var i=0, len=imgs.length; i < len; i++){
    imgs[i].title = document.title + " image " + i;
    }
    var msg = document.getElementById("msg");
    msg.innerHTML = "Update complete.";
    }

      该函数可能看上去完全正常,但是它包含了三个对于全局document对象的引用。如果在页面上有多个图片,那么for循环中的document引用就会被执行多次甚至上百次,每次都会要进行作用域链查找。通过创建一个指向document对象的局部变量,就可以通过限制一次全局查找来改进这个函数的性能:

    function updateUI(){
    var doc = document;
    var imgs = doc.getElementsByTagName("img");
    for (var i=0, len=imgs.length; i < len; i++){
    imgs[i].title = doc.title + " image " + i;
    }
    var msg = doc.getElementById("msg");
    msg.innerHTML = "Update complete.";
    }

      这里,首先将 document 对象存在本地的 doc 变量中;然后在余下的代码中替换原来的 document。与原来的的版本相比,现在的函数只有一次全局查找,肯定更快。
      将在一个函数中会用到多次的全局对象存储为局部变量总是没错的。

    1.2避免with语句

      在性能非常重要的地方必须避免使用with语句。和函数类似,with语句会创建自己的作用域,因此会增加其中执行的代码的作用域链的长度。由于额外的作用域链查找,在with语句中执行的代码肯定会比外米娜执行的代码要慢。

      对比一下以下两段作用相同的代码:

    function updateBody(){
    with(document.body){
    alert(tagName);
    innerHTML = "Hello world!";
    }
    }
    function updateBody(){
    var body = document.body
    alert(body.tagName);
    body.innerHTML = "Hello world!";
    }

      第一个使用了with语句,第二个没有使用。第二个代码虽然稍微长了点,但是阅读起来比with语句版本更好,它确保让你知道tagName和innerHTML是属于哪个对象的。同时,这段代码通过将document.body存储在局部变量中省去了额外的全局查找。

    2.选择方法方面

    2.1避免不必要的属性查找

    var values = [5, 10];
    var sum = values[0] + values[1];
    alert(sum);
    var values = { first: 5, second: 10};
    var sum = values.first + values.second;
    alert(sum);

      对比一下上述两个代码,第一段代码使用数组相加来得到sum值,第二段代码使用访问对象的属性方法相加来得到sum值,前者为O(1),后者为O(n)。

      对象上的任何属性查找都要比访问变量或者数组花费更长时间,因为必须在原型链中对拥有该名称的属性进行一次搜索。如第二段代码进行一两次属性查找并不会导致显著的性能问题,但是进行成百上千次则肯定会减慢执行速度。

      简而言之,属性查找越多,执行时间就越长。

    2.2优化循环

    1. 减值迭代——大多数循环使用一个从0开始、增加到某个特定值的迭代器。在很多情况下,从最大值开始,在循环中不断减值的迭代器更加高效。
    2. 简化终止条件——由于每次循环过程都会计算终止条件,所以必须保证它尽可能快。也就是说避免属性查找或其他O(n)的操作。
    3. 简化循环体——循环体是执行最多的,所以要确保其被最大限度地优化。确保没有某些可以被很容易移出循环的密集计算。
    4. 使用后测试循环——最常用for循环和while循环都是前测试循环。而如do-while这种后测试循环,可以避免最初终止条件的计算,因此运行更快。

    2.3展开循环

      当循环的次数是确定的,消除循环并使用多次函数调用往往更快。

    for (var i=values.length -1; i >= 0; i--){
    process(values[i]);
    }
    //消除循环
    process(values[0]);
    process(values[1]);
    process(values[2]);

      如果数组的长度总是一样的,对每个元素都调用process()可能更优。这个例子假设values数组里面只有3个元素,直接对每个元素调用process()。这样展开循环可以消除建立循环和处理终止条件的额外开销,使代码运行得更快。

    2.4性能的其他注意事项

    • 原生方法较快——只要有可能,使用原生方法而不是自己用JavaScript重写一个。
    • switch语句较快
    • 位运算符较快——当进行数学运算的时候,位运算操作要比任何布尔运算或者算数运算快。选择性地用位运算替换算数运算可以极大提升复杂运算的性能。诸如取模,逻辑与和逻辑或都可以考虑用位运算来替换。

    3.最小化语句数方面

    3.1多个变量声明

    //4 个语句—— 很浪费
    var count = 5;
    var color = "blue";
    var values = [1,2,3];
    var now = new Date();

    改为

    //一个语句
    var count = 5,
    color = "blue",
    values = [1,2,3],
    now = new Date();

      此处,变量声明只用了一个var语句,之间由逗号隔开。在大多数情况下这种优化都非常容易做,并且要比单个变量分别声明快很多。

    3.2插入迭代值

    var name = values[i];
    i++;

    改为

    var name = values[i++];

    3.3使用数组和对象字面量

      有两种创建数组和对象的方法:构造函数和字面量。使用构造函数总是要用到更多的语句来插入元素或者定义属性,而字面量可以将这些操作在一个语句中完成。

    //用 4 个语句创建和初始化数组——浪费
    var values = new Array();
    values[0] = 123;
    values[1] = 456;
    values[2] = 789;
    //用 4 个语句创建和初始化对象——浪费
    var person = new Object();
    person.name = "Nicholas";
    person.age = 29;
    person.sayName = function(){
    alert(this.name);
    };

    改为

    //只用一条语句创建和初始化数组
    var values = [123, 456, 789];
    //只用一条语句创建和初始化对象
    var person = {
    name : "Nicholas",
    age : 29,
    sayName : function(){
    alert(this.name);
    }
    };

      重写后的代码只包含两条语句,一条创建和初始化数组,另一条创建和初始化对象。之前用了八条语句的东西现在只用了两条,减少了75%的语句量。在包含成千上万行JavaScript的代码库中,这些优化的价值更大。

    4.优化DOM交互

      在JavaScript各个方面中,DOM毫无疑问是最慢的一部分。DOM操作与交互要消耗大量时间,因为它们往往需要重新渲染整个页面或者某一部分。进一步说,看似细微的操作也可能要花很久来执行,因为DOM要处理非常多的信息。理解如何优化与DOM的交互可以极大得提高脚本完成的速度。

    4.1最小化现场更新

      一旦你需要访问的DOM部分是已经显示的页面的一部分,那么你就是在进行一个现场更新。之所以叫现场更新,是因为需要立即(现场)对页面对用户的显示进行更新。每一个更改,不管是插入单个字符,还是移除整个片段,都有一个性能惩罚,因为浏览器要重新计算无数尺寸以进行更新。现场更新进行得越多,代码完成执行所花的时间就越长;完成一个操作所需的现场更新越少,代码就越快。

      要修正这个性能瓶颈,需要减少现场更新的数量。一般有2种方法。

    1. 将列表从页面上移除,最后进行更新,最后再将列表插回到同样的位置。这个方法不是非常理想,因为每次页面更新的时候它会不必要的闪烁。
    2. 使用文档片段来构建DOM结构,接着将其添加到List元素中。这个方式避免了现场更新和页面闪烁问题。
    var list = document.getElementById("myList"),
    item,
    i;
    for (i=0; i < 10; i++) {
    item = document.createElement("li");
    list.appendChild(item);
    item.appendChild(document.createTextNode("Item " + i));
    }

    改为

    var list = document.getElementById("myList"),
    fragment = document.createDocumentFragment(),
    item,
    i;
    for (i=0; i < 10; i++) {
    item = document.createElement("li");
    fragment.appendChild(item);
    item.appendChild(document.createTextNode("Item " + i));
    }
    list.appendChild(fragment);

    在这个例子中只有一次现场更新,它发生在所有项目都创建好之后。文档片段用作一个临时的占位符,放置新创建的项目。然后使用appendChild()将所有项目添加到列表中。

      一旦需要更新DOM,请考虑使用文档片段来构建DOM结构,然后再将其添加到现存的文档中。

    4.2使用innerHTML

      有两种在页面上创建DOM节点的方法:

    1. 使用诸如createElement()和appendChild()之类的DOM方法。
    2. 使用innerHTML。

      对于小的DOM更改而言,两种方法效率都差不多。然而,对于大的DOM更改,使用innerHTML要比使用标准DOM方法创建同样的DOM结构快得多。

    var list = document.getElementById("myList"),
    item,
    i;
    for (i=0; i < 10; i++) {
    item = document.createElement("li");
    list.appendChild(item);
    item.appendChild(document.createTextNode("Item " + i));
    }

    改为

    var list = document.getElementById("myList"),
    html = "",
    i;
    for (i=0; i < 10; i++) {
    html += "<li>Item " + i + "</li>";
    }
    list.innerHTML = html;

    参考文献:《JavaScript高级程序设计(第三版)》。

  • 相关阅读:
    【JavaSE】成员方法快速入门和方法的调用机制原理
    HarmonyOS实战—实现注册登录和修改密码页面
    苹果CMS自动定时采集教程
    HarmonyOS实战—统计按钮点击次数
    HarmonyOS实战—点击更换随机图片
    C语言 main 函数
    C语言 vprintf 函数和 printf 函数区别
    C语言 vprintf 函数
    C语言 va_start / va_end / va_arg 自定义 printf 函数
    C语言 va_arg 宏
  • 原文地址:https://www.cnblogs.com/huahai/p/6351472.html
Copyright © 2011-2022 走看看