JavaScript パフォーマンス最適化に関する知識の概要

黄舟
リリース: 2017-02-25 13:23:53
オリジナル
1611 人が閲覧しました

JavaScript のパフォーマンスの問題を過小評価してはなりません。そのため、開発者は JavaScript プログラムを作成する際にいくつかの詳細に注意を払う必要があります。この記事では、JavaScript のパフォーマンス最適化に関する知識ポイントを詳細に紹介します。これは間違いなく役立つ情報です。

まえがき

私は JavaScript を勉強しており、『シャープ JQuery カーネル開発 詳細解説と実践』も読みました。この本についてコメントするには「シャープ」の 2 語しかありません。 JavaScript を十分に理解していないというか、私が怠け者なので、考えるのが苦手で、考えるのが面倒なので、いくつかのことを深く理解していません。その本質。

私は自分自身を向上させたいが、より広い世界には行けないので、うまく生きるために自分の居場所を見つける必要があるので、特にパフォーマンス要件のために、意図的にまたは意図せずに jQuery を使用するための一般的な知識を常に蓄積しています。それを達成するためのより良い方法があるだろうか。

以下は参考のためにまとめたいくつかのヒントです。 (最初に一般的なタイトルを付け、次に短い段落で意味を説明し、最後にデモを使用して簡単に説明します)

グローバル検索を避ける

関数では、グローバルオブジェクトはグローバル検索を減らすためにローカル変数として保存されます。ローカル変数へのアクセスは、グローバル変数へのアクセスよりも速いためです

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

Timer

継続的に実行されるコードを対象としている場合、 setTimeout は毎回タイマーを初期化するため、 setTimeout を使用すべきではなく、 setInterval を使用する必要があります。 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 (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() を使用することを好みます。文字列を数値に変換するには、浮動小数点と整数間の変換ではなく、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 では、すべての変数を 1 つの var ステートメントを使用して宣言できるため、上記のコードのように、ステートメントを結合してスクリプト全体の実行時間を短縮できます。 , 上 コードの形式もかなり統一されており、一目で分かりやすいです。

反復子を挿入します var name=values[i++] など、最初の 2 つのステートメントは 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 を使用しますappends

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);
ログイン後にコピー

DOM 要素を構築する代わりに、innerHTML 割り当てを一度使用してください

DOM を大規模に変更する場合、標準 DOM メソッドを使用して同じ DOM 構造を作成するよりも innerHTML を使用する方がはるかに高速です。

        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;);
ログイン後にコピー

createElement の代わりにテンプレート要素を複製します

多くの人は、JavaScript で document.write を使用してページのコンテンツを生成することを好みます。実際、これは効率が低くなります。HTML を直接挿入する必要がある場合は、p または span を指定するなどしてコンテナー要素を見つけ、その innerHTML を設定して独自の HTML コードをページに挿入することができます。通常、HTML を直接記述してノードを作成する場合は、文字列を使用します。実際、これを行うと、1. コードの正当性が保証されず、2. 文字列の操作効率が低いため、document.createElement() メソッドを使用する必要があります。テンプレート ノードの場合、createElement() メソッドを使用した後、要素の属性を複数回設定する必要があるため、ドキュメントに既製の要素がある場合は cloneNode() メソッドを使用する必要があります。 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);
ログイン後にコピー

dom 要素を走査するには、childNodes の代わりに firstChild と nextSibling を使用します

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

人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート