首頁 > web前端 > js教程 > 主體

JavaScript效能優化小知識總結

黄舟
發布: 2017-02-25 13:23:53
原創
1597 人瀏覽過

JavaScript的效能問題不容小覷,這就需要我們開發人員在編寫JavaScript程式時多注意一些細節,本文非常詳細的介紹了一下JavaScript效能優化方面的知識點,絕對是乾貨。

前言

一直在學習javascript,也有看過《犀利開發Jquery核心詳解與實作》,對這本書的評價只有兩個字犀利,可能是對javascript理解的還不夠透徹異或是自己太笨,更多的是自己不擅於思考懶得思考以至於裡面說的一些精髓都沒有太深入的理解。

鑑於想讓自己有一個提升,進不了一個更加廣闊的天地,總得找一個屬於自己的居所好好生存,所以平時會有意無意的去積累一些使用jQuerry的常用知識,特別是對於性能要求這一塊,總是會想是不是有更好的方式來實現。

以下是我總結的一些小技巧,僅供參考。 (我先會說一個總標題,然後用一小段話來說明這個意思再最後用一個demo來簡單言明)

避免全局查找

在一個函數中會用到全局物件儲存為局部變數來減少全域查找,因為存取局部變數的速度要比存取全域變數的速度更快些

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

##定時器

如果針對的是不斷運行的程式碼,

不應該使用setTimeout,而應該是用setInterval,因為setTimeout每次都會初始化一個計時器,而setInterval只會在開始的時候初始化一個定時器

        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);
登入後複製

字串連接

如果要連接多個字串,應該少使用+=,如

# s+=a;

s+=b;

s+=c;

#應該寫成s+=a + b + c;

而如果是收集字串,例如多次對同一個字串進行+=操作的話,最好使用一個緩存,使用JavaScript數組來收集,最後

使用join方法連接起來

        var buf = [];
        for (var i = 0; i < 100; i++) {
            buf.push(i.toString());
        }
        var all = buf.join("");
登入後複製

避免with語句

和函數類似,with語句會創建自己的作用域,因此會增加其中執行的程式碼的作用域鏈的長度,由於額外的作用域鏈的查找,在with語句中執行的程式碼肯定會比外面執行的程式碼慢,在

能不使用with語句的時候盡量不要使用with語句

 with (a.b.c.d) {
            property1 = 1;
            property2 = 2;
        }
        //可以替换为:
        var obj = a.b.c.d;
        obj.property1 = 1;
        obj.property2 = 2;
登入後複製

數字轉換成字串

般最好用」" + 1來將數字轉換成字串,雖然看起來比較醜一點,但事實上這個效率是最高的,性能上來說:

(“” +) > String() > .toString() > new String()

浮點數轉換成整數

很多人喜歡使用parseInt(),其實parseInt()是用來將字串轉換成數字,而不是浮點數和整數之間的轉換,我們應該使用Math.floor()或Math.round()

各種型別轉換

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
登入後複製

如果定義了toString( )方法來進行類型轉換的話,推薦

明確呼叫toString(),因為內部的運算在嘗試所有可能性之後,會嘗試物件的toString()方法嘗試能否轉換為String,所以直接呼叫這個方法效率會更高

多個類型宣告

在JavaScript中所有變數都可以使用單一var語句來聲明,這樣就是組合在一起的語句,以減少整個腳本的執行時間,就如上面程式碼一樣,上面程式碼格式也挺規範,讓人一看就明了。

插入迭代器

如var name=values[i]; i++;前面兩個語句可以寫成var name=values[i++]

使用直接量

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" };
登入後複製

使用DocumentFragment優化多次append

一旦需要更新DOM,請考慮使用文檔碎片來建立DOM結構,然後再將其添加到現存的文檔中。

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);
登入後複製

使用一次innerHTML賦值來取代建構dom元素

對於大的DOM更改,使用innerHTML要比使用標準的DOM方法建立同樣的DOM結構快得多。

        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;);
登入後複製

透過範本元素clone,取代createElement

很多人喜歡在JavaScript中使用document.write來為頁面產生內容。事實上這樣的效率較低,如果需要直接插入HTML,可以找一個容器元素,例如指定一個p或span,並設定他們的innerHTML來將自己的HTML程式碼插入頁面。通常我們可能會使用字串直接寫HTML來建立節點,其實這樣做,1無法保證程式碼的有效性2字串操作效率低,所以應該是用document.createElement()方法,而如果文件中存在現成的樣板節點,應該是用cloneNode()方法,因為使用createElement()方法之後,你需要設定多次元素的屬性,使用cloneNode()則可以減少屬性的設定次數-同樣如果需要建立很多元素,應該先準備一個樣板節點

        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);
登入後複製

使用firstChild和nextSibling取代childNodes遍歷dom元素

        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;
登入後複製

删除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;
登入後複製

注意NodeList

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

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

        }
登入後複製

编写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++) {
            //……
        }
登入後複製

  • 简化循环体

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

  • 使用后测试循环

在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];
        }
登入後複製

最常用的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);
登入後複製

缩短否定检测

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

条件分支

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

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

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

        if (a > b) {
            num = a;
        } else {
            num = b;
        }
        //可以替换为:
        num = a > b ? a : b;
登入後複製

使用常量

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

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

  • 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
    };
};
登入後複製

尊重对象的所有权

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

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

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

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

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

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

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

循环引用

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

简单的循环引用:

        var el = document.getElementById(&#39;MyElement&#39;);
        var func = function () {
            //…
        }
        el.func = func;
        func.element = el;
登入後複製

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

        function init() {
            var el = document.getElementById(&#39;MyElement&#39;);
            el.onclick = function () {
                //……
            }
        }
        init();
登入後複製

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();
登入後複製

将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();
登入後複製

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();
登入後複製

把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);
            }
        }
登入後複製

释放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、使用包含内联代码的

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!