Home > Web Front-end > JS Tutorial > Summary of JavaScript performance optimization knowledge

Summary of JavaScript performance optimization knowledge

黄舟
Release: 2017-02-25 13:23:53
Original
1646 people have browsed it

The performance issues of JavaScript cannot be underestimated, which requires our developers to pay more attention to some details when writing JavaScript programs. This article introduces the knowledge points of JavaScript performance optimization in great detail, which is definitely useful information.

Preface

I have been learning javascript, and I have also read "Sharp Development of Jquery Kernel Detailed Explanation and Practice". There are only two words for this book, which may be because of the sharpness of javascript. I don’t understand it thoroughly enough, or I’m too stupid. More importantly, I’m not good at thinking and I’m too lazy to think about it, so I don’t have a deep understanding of some of the essence of it.

Since I want to improve myself and cannot enter a wider world, I have to find a place of my own to live well, so I usually accumulate some common knowledge of using jQuerry intentionally or unintentionally, especially for When it comes to performance requirements, I always wonder if there is a better way to achieve it.

The following are some tips I have summarized for reference only. (I will first say a general title, then use a short paragraph to explain the meaning, and finally use a demo to briefly explain)

Avoid global search

Global will be used in a function Objects are stored as local variables to reduce global lookups, because accessing local variables is faster than accessing global variables

        function search() {
            //当我要使用当前页面地址和主机域名
            alert(window.location.href + window.location.host);
        }
        //最好的方式是如下这样  先用一个简单变量保存起来
        function search() {
            var location = window.location;
            alert(location.href + location.host);
        }
Copy after login

Timer

If you are targeting code that is constantly running, should not use setTimeout, but should use setInterval, because setTimeout will initialize a timer every time, and setInterval will only initialize a timer at the beginning. Device

        var timeoutTimes = 0;
        function timeout() {
            timeoutTimes++;
            if (timeoutTimes < 10) {
                setTimeout(timeout, 10);
            }
        }
        timeout();
        //可以替换为:
        var intervalTimes = 0;
        function interval() {
            intervalTimes++;
            if (intervalTimes >= 10) {
                clearInterval(interv);
            }
        }
        var interv = setInterval(interval, 10);
Copy after login

String connection

If you want to connect multiple strings, you should use less +=, such as

s+=a;

s+=b;

s+=c;

should be written as s+=a + b + c;

and if it is collection Strings, for example, if you perform += operations on the same string multiple times, it is best to use a cache, use JavaScript arrays to collect them, and finallyuse the join method to connect them

        var buf = [];
        for (var i = 0; i < 100; i++) {
            buf.push(i.toString());
        }
        var all = buf.join("");
Copy after login

Avoid with statements

Similar to functions, the with statement creates its own scope and therefore increases the length of the scope chain of the code executed within it, due to the additional effects When searching for domain chains, the code executed in the with statement will definitely be slower than the code executed outside. Try not to use the with statement when you can not use the with statement.

 with (a.b.c.d) {
            property1 = 1;
            property2 = 2;
        }
        //可以替换为:
        var obj = a.b.c.d;
        obj.property1 = 1;
        obj.property2 = 2;
Copy after login

Convert numbers to strings

It is generally best to use "" + 1 to convert numbers to strings, although it looks relatively A bit ugly, but in fact this is the most efficient, in terms of performance:

("" +) > String() > .toString() > new String()

Convert floating point numbers to integers

Many people like to use parseInt(). In fact, parseInt() is used to convert strings into numbers, not between floating point numbers and integers. For conversion, we should use Math.floor() or Math.round()

Various type conversion

var myVar = "3.14159",
        str = "" + myVar, //  to string  
        i_int = ~ ~myVar,  //  to integer  
        f_float = 1 * myVar,  //  to float  
        b_bool = !!myVar,  /*  to boolean - any string with length 
                                and any number except 0 are true */
        array = [myVar];  //  to array
Copy after login

If toString( is defined ) method to perform type conversion, it is recommended

to explicitly call toString(), because after trying all possibilities, the internal operation will try the toString() method of the object to see if it can be converted into String, so directly Calling this method will be more efficient

Multiple type declarations

In JavaScript, all variables can be declared using a single var statement, so that the statements are combined together to reduce the complexity of the entire script. The execution time is just like the above code, and the above code format is also quite standardized, which is easy to understand at a glance.

Insert iterator

For example, var name=values[i]; i++; the first two statements can be written as var name=values[i++]

Use direct quantities

var aTest = new Array(); //替换为
        var aTest = [];
        var aTest = new Object; //替换为
        var aTest = {};
        var reg = new RegExp(); //替换为
        var reg = /../;
        //如果要创建具有一些特性的一般对象,也可以使用字面量,如下:
        var oFruit = new O;
        oFruit.color = "red";
        oFruit.name = "apple";
        //前面的代码可用对象字面量来改写成这样:
        var oFruit = { color: "red", name: "apple" };
Copy after login

Use DocumentFragment to optimize multiple append

Once the DOM needs to be updated, consider using document fragments to build the DOM structure before adding it to in existing documents.

for (var i = 0; i < 1000; i++) {
            var el = document.createElement(&#39;p&#39;);
            el.innerHTML = i;
            document.body.appendChild(el);
        }
        //可以替换为:
        var frag = document.createDocumentFragment();
        for (var i = 0; i < 1000; i++) {
            var el = document.createElement(&#39;p&#39;);
            el.innerHTML = i;
            frag.appendChild(el);
        }
        document.body.appendChild(frag);
Copy after login

Use innerHTML assignment once instead of building dom elements

For large DOM changes, using innerHTML is the same as using standard DOM methods to create The DOM structure is much faster.

        var frag = document.createDocumentFragment();
        for (var i = 0; i < 1000; i++) {
            var el = document.createElement(&#39;p&#39;);
            el.innerHTML = i;
            frag.appendChild(el);
        }
        document.body.appendChild(frag);
        //可以替换为:
        var html = [];
        for (var i = 0; i < 1000; i++) {
            html.push(&#39;<p>&#39; + i + &#39;</p>&#39;);
        }
        document.body.innerHTML = html.join(&#39;&#39;);
Copy after login

Clone the template element instead of createElement

Many people like to use document.write in JavaScript to generate content for the page. In fact, this is less efficient. If you need to insert HTML directly, you can find a container element, such as specifying a p or span, and set their innerHTML to insert your own HTML code into the page. Usually we may use strings to write HTML directly to create nodes. In fact, if we do this, 1. the validity of the code cannot be guaranteed and 2. the string operation efficiency is low, so the document.createElement() method should be used, and if there is a ready-made element in the document For template nodes, you should use the cloneNode() method, because after using the createElement() method, you need to set the attributes of the element multiple times. Using cloneNode() can reduce the number of attribute settings - similarly, if you need to create many elements, you should first Prepare a sample node

        var frag = document.createDocumentFragment();
        for (var i = 0; i < 1000; i++) {
            var el = document.createElement(&#39;p&#39;);
            el.innerHTML = i;
            frag.appendChild(el);
        }
        document.body.appendChild(frag);
        //替换为:
        var frag = document.createDocumentFragment();
        var pEl = document.getElementsByTagName(&#39;p&#39;)[0];
        for (var i = 0; i < 1000; i++) {
            var el = pEl.cloneNode(false);
            el.innerHTML = i;
            frag.appendChild(el);
        }
        document.body.appendChild(frag);
Copy after login

Use firstChild and nextSibling instead of childNodes to traverse dom elements

        var nodes = element.childNodes;
        for (var i = 0, l = nodes.length; i < l; i++) {
            var node = nodes[i];
            //……
        }
        //可以替换为:
        var node = element.firstChild;
        while (node) {
            //……
            node = node.nextSibling;
Copy after login

删除DOM节点

删除dom节点之前,一定要删除注册在该节点上的事件,不管是用observe方式还是用attachEvent方式注册的事件,否则将会产生无法回收的内存。另外,在removeChild和innerHTML=’’二者之间,尽量选择后者. 因为在sIEve(内存泄露监测工具)中监测的结果是用removeChild无法有效地释放dom节点

使用事件代理

任何可以冒泡的事件都不仅仅可以在事件目标上进行处理,目标的任何祖先节点上也能处理,使用这个知识就可以将事件处理程序附加到更高的地方负责多个目标的事件处理,同样,对于内容动态增加并且子节点都需要相同的事件处理函数的情况,可以把事件注册提到父节点上,这样就不需要为每个子节点注册事件监听了。另外,现有的js库都采用observe方式来创建事件监听,其实现上隔离了dom对象和事件处理函数之间的循环引用,所以应该尽量采用这种方式来创建事件监听

重复使用的调用结果,事先保存到局部变量

        //避免多次取值的调用开销
        var h1 = element1.clientHeight + num1;
        var h2 = element1.clientHeight + num2;
        //可以替换为:
        var eleHeight = element1.clientHeight;
        var h1 = eleHeight + num1;
        var h2 = eleHeight + num2;
Copy after login

注意NodeList

最小化访问NodeList的次数可以极大的改进脚本的性能

        var images = document.getElementsByTagName(&#39;img&#39;);
        for (var i = 0, len = images.length; i < len; i++) {

        }
Copy after login

编写JavaScript的时候一定要知道何时返回NodeList对象,这样可以最小化对它们的访问

  • 进行了对getElementsByTagName()的调用

  • 获取了元素的childNodes属性

  • 获取了元素的attributes属性

  • 访问了特殊的集合,如document.forms、document.images等等

要了解了当使用NodeList对象时,合理使用会极大的提升代码执行速度

优化循环

可以使用下面几种方式来优化循环

  • 减值迭代

大多数循环使用一个从0开始、增加到某个特定值的迭代器,在很多情况下,从最大值开始,在循环中不断减值的迭代器更加高效

  • 简化终止条件

由于每次循环过程都会计算终止条件,所以必须保证它尽可能快,也就是说避免属性查找或者其它的操作,最好是将循环控制量保存到局部变量中,也就是说对数组或列表对象的遍历时,提前将length保存到局部变量中,避免在循环的每一步重复取值。

        var list = document.getElementsByTagName(&#39;p&#39;);
        for (var i = 0; i < list.length; i++) {
            //……
        }

        //替换为:
        var list = document.getElementsByTagName(&#39;p&#39;);
        for (var i = 0, l = list.length; i < l; i++) {
            //……
        }
Copy after login

  • 简化循环体

循环体是执行最多的,所以要确保其被最大限度的优化

  • 使用后测试循环

在JavaScript中,我们可以使用for(;;),while(),for(in)三种循环,事实上,这三种循环中for(in)的效率极差,因为他需要查询散列键,只要可以,就应该尽量少用。for(;;)和while循环,while循环的效率要优于for(;;),可能是因为for(;;)结构的问题,需要经常跳转回去。

        var arr = [1, 2, 3, 4, 5, 6, 7];
        var sum = 0;
        for (var i = 0, l = arr.length; i < l; i++) {
            sum += arr[i];
        }

        //可以考虑替换为:

        var arr = [1, 2, 3, 4, 5, 6, 7];
        var sum = 0, l = arr.length;
        while (l--) {
            sum += arr[l];
        }
Copy after login

最常用的for循环和while循环都是前测试循环,而如do-while这种后测试循环,可以避免最初终止条件的计算,因此运行更快。

展开循环

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

避免双重解释

如果要提高代码性能,尽可能避免出现需要按照JavaScript解释的字符串,也就是

  • 尽量少使用eval函数

使用eval相当于在运行时再次调用解释引擎对内容进行运行,需要消耗大量时间,而且使用Eval带来的安全性问题也是不容忽视的。

  • 不要使用Function构造器

不要给setTimeout或者setInterval传递字符串参数

        var num = 0;
        setTimeout(&#39;num++&#39;, 10);
        //可以替换为:
        var num = 0;
        function addNum() {
            num++;
        }
        setTimeout(addNum, 10);
Copy after login

缩短否定检测

       if (oTest != &#39;#ff0000&#39;) {
            //do something
        }
        if (oTest != null) {
            //do something
        }
        if (oTest != false) {
            //do something
        }
        //虽然这些都正确,但用逻辑非操作符来操作也有同样的效果:
        if (!oTest) {
            //do something
        }
Copy after login

条件分支

  • 将条件分支,按可能性顺序从高到低排列:可以减少解释器对条件的探测次数

  • 在同一条件子的多(>2)条件分支时,使用switch优于if:switch分支选择的效率高于if,在IE下尤为明显。4分支的测试,IE下switch的执行时间约为if的一半。

  • 使用三目运算符替代条件分支

        if (a > b) {
            num = a;
        } else {
            num = b;
        }
        //可以替换为:
        num = a > b ? a : b;
Copy after login

使用常量

  • 重复值:任何在多处用到的值都应该抽取为一个常量

  • 用户界面字符串:任何用于显示给用户的字符串,都应该抽取出来以方便国际化

  • URLs:在Web应用中,资源位置很容易变更,所以推荐用一个公共地方存放所有的URL

  • 任意可能会更改的值:每当你用到字面量值的时候,你都要问一下自己这个值在未来是不是会变化,如果答案是“是”,那么这个值就应该被提取出来作为一个常量。

避免与null进行比较

由于JavaScript是弱类型的,所以它不会做任何的自动类型检查,所以如果看到与null进行比较的代码,尝试使用以下技术替换

  • 如果值应为一个引用类型,使用instanceof操作符检查其构造函数

  • 如果值应为一个基本类型,作用typeof检查其类型

  • 如果是希望对象包含某个特定的方法名,则使用typeof操作符确保指定名字的方法存在于对象上

避免全局量

全局变量应该全部字母大写,各单词之间用_下划线来连接。尽可能避免全局变量和函数, 尽量减少全局变量的使用,因为在一个页面中包含的所有JavaScript都在同一个域中运行。所以如果你的代码中声明了全局变量或者全局函数的话,后面的代码中载入的脚本文件中的同名变量和函数会覆盖掉(overwrite)你的。

//糟糕的全局变量和全局函数
var current = null;
function init(){
//...
}
function change() {
    //...
}
function verify() {
    //...
}
//解决办法有很多,Christian Heilmann建议的方法是:
//如果变量和函数不需要在“外面”引用,那么就可以使用一个没有名字的方法将他们全都包起来。
(function(){
var current = null;
function init() {
    //...
}
function change() {
    //...
}
function verify() {
    //...
}
})();
//如果变量和函数需要在“外面”引用,需要把你的变量和函数放在一个“命名空间”中
//我们这里用一个function做命名空间而不是一个var,因为在前者中声明function更简单,而且能保护隐私数据
myNameSpace = function() {
    var current = null;

    function init() {
        //...
    }

    function change() {
        //...
    }

    function verify() {
        //...
    }

//所有需要在命名空间外调用的函数和属性都要写在return里面
    return {
        init: init,
        //甚至你可以为函数和属性命名一个别名
        set: change
    };
};
Copy after login

尊重对象的所有权

因为JavaScript可以在任何时候修改任意对象,这样就可以以不可预计的方式覆写默认的行为,所以如果你不负责维护某个对象,它的对象或者它的方法,那么你就不要对它进行修改,具体一点就是说:

  • 不要为实例或原型添加属性

  • 不要为实例或者原型添加方法

  • 不要重定义已经存在的方法

  • 不要重复定义其它团队成员已经实现的方法,永远不要修改不是由你所有的对象,你可以通过以下方式为对象创建新的功能:

  • 创建包含所需功能的新对象,并用它与相关对象进行交互

  • 创建自定义类型,继承需要进行修改的类型,然后可以为自定义类型添加额外功能

循环引用

如果循环引用中包含DOM对象或者ActiveX对象,那么就会发生内存泄露。内存泄露的后果是在浏览器关闭前,即使是刷新页面,这部分内存不会被浏览器释放。

简单的循环引用:

        var el = document.getElementById(&#39;MyElement&#39;);
        var func = function () {
            //…
        }
        el.func = func;
        func.element = el;
Copy after login

但是通常不会出现这种情况。通常循环引用发生在为dom元素添加闭包作为expendo的时候。

        function init() {
            var el = document.getElementById(&#39;MyElement&#39;);
            el.onclick = function () {
                //……
            }
        }
        init();
Copy after login

init在执行的时候,当前上下文我们叫做context。这个时候,context引用了el,el引用了function,function引用了context。这时候形成了一个循环引用。

下面2种方法可以解决循环引用:

1) 置空dom对象

       function init() {
            var el = document.getElementById(&#39;MyElement&#39;);
            el.onclick = function () {
                //……
            }
        }
        init();
        //可以替换为:
        function init() {
            var el = document.getElementById(&#39;MyElement&#39;);
            el.onclick = function () {
                //……
            }
            el = null;
        }
        init();
Copy after login

将el置空,context中不包含对dom对象的引用,从而打断循环应用。

如果我们需要将dom对象返回,可以用如下方法:

        function init() {
            var el = document.getElementById(&#39;MyElement&#39;);
            el.onclick = function () {
                //……
            }
            return el;
        }
        init();
        //可以替换为:
        function init() {
            var el = document.getElementById(&#39;MyElement&#39;);
            el.onclick = function () {
                //……
            }
            try {
                return el;
            } finally {
                el = null;
            }
        }
        init();
Copy after login

2) 构造新的context

        function init() {
            var el = document.getElementById(&#39;MyElement&#39;);
            el.onclick = function () {
                //……
            }
        }
        init();
        //可以替换为:
        function elClickHandler() {
            //……
        }
        function init() {
            var el = document.getElementById('MyElement');
            el.onclick = elClickHandler;
        }
        init();
Copy after login

把function抽到新的context中,这样,function的context就不包含对el的引用,从而打断循环引用。

通过javascript创建的dom对象,必须append到页面中

IE下,脚本创建的dom对象,如果没有append到页面中,刷新页面,这部分内存是不会回收的!

        function create() {
            var gc = document.getElementById(&#39;GC&#39;);
            for (var i = 0; i < 5000; i++) {
                var el = document.createElement(&#39;p&#39;);
                el.innerHTML = "test";
                //下面这句可以注释掉,看看浏览器在任务管理器中,点击按钮然后刷新后的内存变化
                gc.appendChild(el);
            }
        }
Copy after login

释放dom元素占用的内存

将dom元素的innerHTML设置为空字符串,可以释放其子元素占用的内存。

在rich应用中,用户也许会在一个页面上停留很长时间,可以使用该方法释放积累得越来越多的dom元素使用的内存。

释放javascript对象

在rich应用中,随着实例化对象数量的增加,内存消耗会越来越大。所以应当及时释放对对象的引用,让GC能够回收这些内存控件。

对象:obj = null

对象属性:delete obj.myproperty

数组item:使用数组的splice方法释放数组中不用的item

避免string的隐式装箱

对string的方法调用,比如’xxx’.length,浏览器会进行一个隐式的装箱操作,将字符串先转换成一个String对象。推荐对声明有可能使用String实例方法的字符串时,采用如下写法:

var myString = new String(‘Hello World’);

松散耦合

1、解耦HTML/JavaScript

JavaScript和HTML的紧密耦合:直接写在HTML中的JavaScript、使用包含内联代码的

Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template