`
wjlgryx
  • 浏览: 297996 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

 JavaScript分析

阅读更多

4.3 JavaScript分析

随着客户端应用程序越来越复杂,对JavaScript进行分析的需求渐渐显露。当通过大量函数调用和对象交互来生成复杂的用户界面时,要找出影响程序性能的问题来源也变得困难起来。分析脚本不仅能够从脚本优化中排除主观臆断因素,而且还能让人们了解程序运行时的细节,以及给定时间段内所执行的函数调用次数。

开发人员在开始编写代码时,通常都倾向于在有效代码和有效编码之间采用折衷方案。而为了清晰起见,对代码的评估应该采用最短有效路线,同时花最少时间去编写代码。但是,这样一来,降低效率的因素将不可避免地侵入代码,无论开发人员多用心多勤奋。为了找出低效代码的关键点,代码分析工具将大显身手。

有一个需要坚守的挑战性规则:把JavaScript优化工作推迟到开发的最后阶段。届时,架构转换、对象及其用法的变化、错误修复,所有可变因素都将尘埃落定。开发一套功能完善、容易维护的应用程序是任务清单的核心所在。代码优化过程中分析程序的运用同样也在严肃的应用程序开发中占有一席之地,但不到代码几乎不需改动的最后一刻,不应该轻易实施代码分析。

虽然Venkman的分析程序提供了不少有用功能,例如包含或排除整个文件或其中定义的部分函数,但本节将以讨论Firebug的分析程序为主。原因是大多数Ajax开发人员都会为检查XMLHttpRequest而安装Firebug。尽管Firebug的界面、输出和针对扩展的调用与Venkman有所不同,但其中的原理都是相同的,正像与其他代码分析程序一样。

下面我们将要介绍的Firebug的输出包含许多方面的信息,而且通过单击列标题可以按照相应列的信息重新排序。从左到右,分别描述如下。

l Function——这一列中不仅显示了函数的名称,同时还包括一个指向函数在JavaScript文件中声明位置的链接。通过单击该链接可以迅速看到要分析的代码,从而为研究长时间运行的函数或方法提供了便利。

l Calls——显示了调用该函数的次数。

l Percent——执行该函数所用时间占总执行时间(以毫秒单位显示在分析首部)的百分比,表示函数自有时间(Own Time)。

l Own Time——花在函数直接作用域上的时间。在查找哪个函数占用时间最多时,这里往往能够提供最有用的信息。例如,可以看到运行时间位居第二的urlEncodeObject函数,调用它的时间占用了脚本运行时间的近20%。

l Time——花在该函数作用域中的累计总时间,能够反映出在调用栈中花在这个函数上的总时间。如图4-13所示,run函数需要68.363 ms才能返回,其中包括在它内部调用的所有函数。由于onclick事件首先调用run函数,因此onclick调用的总时间就等于run函数的总时间加上调用run的时间,即68.41 ms

l Avg——花在该函数上的平均总时间。连同最小和最大运行时间一起,这个平均值有助于分析在示例中被多次调用的函数。

l Min——花在该函数上的最小总时间。

l Max——花在该函数上的最大总时间。这个指标通常能够反映出一个函数的伸缩性,因为即使一个函数的平均执行时间很少,但在另外一些情况下其平均时间可能会提高。

l File——与Function类相似,这一列不仅显示了文件名和函数定义所在的行号,同样也提供了到文件中相应行号的链接。利用这种链接功能可以方便地在分析结果与代码的Script选项标签之间切换。

4-13 Firebug中JavaScript分析工具的输出

识别瓶颈

由于前面例子中运行时间最长的函数被库函数和其他对象调用,因此在执行过程中,这个函数成为瓶颈的可能性就比较大。相对而言,执行时间位居第二的函数urlEncodeObject则很少调用其他函数,而且分析结果显示其Own Time事实上完全包含了其Time:

    // 从对象到url编码值的非递归串行化

AjaxRequest.prototype.urlEncodeObject = function(obj) {

    var first = true;

    var string = '';

    for (i in obj) {

        var temp_obj = obj[i];

        // 对字符串直接量不需要使用toString()

        if (typeof temp_obj != 'string') {

            temp_obj = temp_obj.toString();

        }

        var temp_key = encodeURIComponent(i);

        temp_obj = encodeURIComponent(temp_obj);

        if (first) {

            first = false;

            string += temp_key + '=' + temp_obj;

        } else {

            string += '&' + temp_key + '=' + temp_obj;

        }

    }

    return string;

    }

由于这个函数没有调用任何实例变量或方法,因此通过在Firebug的控制台中使用console.profile()console.profileEnd()函数直接调用它,可以发现它的执行速度最快。在前面介绍代码分析的例子中,调用这个函数时只传递了一个小对象:{ "one" : 1 , "two" : 2 },当时只是为了示范它如何编码对象。但为了查找该函数中可能隐含的性能损失,需要让它处理更大些的数据:

    var test = {};

    for (var i = 0; i < 100000; i++) test['i' + i] = i;

上面的调用生成了一个包含10万个成员变量的对象。由于输出会严格匹配输入,因此对键和值的实际编码结果应该会保持最小化,最终是把对象转换成URL编码形式的变量/值对。接下来的调用会启动一个以“Encoding”为标签的分析程序,然后调用编码函数进行编码,最后停止分析程序:

    console.profile("Encoding");

AjaxRequest.prototype.urlEncodeObject(test);

    console.profileEnd("Encoding");

当前函数完成对整个对象的编码花了2287 ms,同时也给了我们进行代码分析的一个良好起点。反观这个函数,其实对它执行时间影响最大的就是循环,因为它需要循环10万次。虽然字符串连接操作也要花点时间,但可以忽略不计,因此该操作没有提升性能的空间。

不过,在连接字符串之前,还需要先声明变量,然后再对对象的值进行比较,最后才会开始编码。虽然编码操作会发生在每个键/值对上,但循环的其他部分肯定会有提升性能的空间。要记住,循环中的每条语句都要运行10万次,而临时的变量不需要在每次迭代时都重新声明。也就是说,只要声明一次就够了,剩下的就是在每次迭代中为临时变量重新赋值。不过,它们仍然需要存在,因为函数不能(或不应该)修改obj变量引用的对象。

接着,来看一看if语句,由于在Firebug控制台中通过循环创建的每个值,它们的类型都是"number"而非"string";这意味着每次都会调用toString()函数。实际上,当作为参数传递到encodeURIComponent()函数中时,不仅对象、函数和数组(对它们进行typeof检查都将返回"object")会被强制转型为字符串,而且数值、布尔值和未定义值也需要进行不同的处理,或者不加处理。

下面我们重写这个函数,不让它处理数值,因为数值在编码时可以直接使用。另外,将布尔值转换为1或0,而不是字符串形式的“true”和“false”;将未定义值简单地表示为空值。遗憾的是,URL编码的值无法表示null值,因此只能将未定义值显示为等价的空字符串:

    // 从对象到url编码值的非递归串行化

AjaxRequest.prototype.urlEncodeObject = function(obj) {

    var first = true;

    var string = '';

    var temp_key;

    var temp_obj;

    for (i in obj) {

        var temp_obj = obj[i];

        temp_key = encodeURIComponent(i);

        switch (typeof temp_obj) {

            case 'number':

                temp_obj = obj[i];

                break;

            case 'boolean':

                temp_obj = (obj[i]) ? 1 : 0;

                break;

            case 'undefined':

                temp_obj = '';

                break;

            default:

                temp_obj = encodeURIComponent(temp_obj);

                break;

        }

        if (first) {

            first = false;

            string += temp_key + '=' + temp_obj;

        } else {

            string += '&' + temp_key + '=' + temp_obj;

        }

    }

    return string;

    }

虽然代码的行数增加了,但这个新函数在处理相同数据时只用了927 ms,即只是修改前所花时间的40%。此外,作为一个额外的收获,布尔值和未定义值都不再以字符串形式来描述它们的值了。

4-14所示的例子分析了一个人类玩家与一个JavaScript对手之间进行Othello(奥赛罗)游戏时的情况,该游戏编写于几年前。通过分析可以看出,调用数量重复的机会很少,因为每个玩家采取的操作在不同游戏间的差异很大。在这种情况下,每个函数的平均、最小和最大运行时间都将变得比调用次数,甚至比整个运行时间的百分比更有价值。

4-14 对一个完整游戏中的JavaScript调用进行分析的结果

由于JavaScript只在游戏玩家采取操作时运行,也因为应用程序遵循了事件驱动的设计,整个运行时间大约394 ms其实是有意义的。不过,其中有两个特殊的函数:movethink,它们的平均时间比其他函数高得多。此外,虽然checkline函数的平均时间较低,但它的最大时间却比预期要多。通过留心与前面同样的问题(例如重复声明、无效递归和循环,以及盲目的逻辑),也可以将这个例子中每个函数的运行时间减少一部分(如图4-15所示)。

4-15 分析程序反映了性能的改善

 

本文来自:http://ajava.org/readbook/AJAX/srajax/16800.html

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics