65.9K
CodeProject 正在变化。 阅读更多。
Home

客户端代码最佳实践

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.86/5 (16投票s)

2008年1月26日

CPOL

5分钟阅读

viewsIcon

71687

本文基于 ASP.NET AJAX 演示了 AJAX 的最佳实践。

引言

在我们开发 AJAX 应用程序时,我们常常不经意地忽视了放弃坏习惯,这会导致在网站规模不大时并不那么显著的影响。但是,对于大量使用 Pageflakes、NetVibes 等 AJAX 技术的网站,这往往会导致严重的性能问题。

一个页面中存在如此多的 AJAX 小部件,轻微的内存泄漏问题可能会导致网站因一个恼人的“操作已中止”错误而崩溃。如果存在大量的 WebService 调用和集合之间的多次迭代,低效的代码可能会使网站变得非常沉重,导致浏览器消耗大量内存,需要昂贵的 CPU 周期,并最终导致不令人满意的用户体验。在本文中,许多此类问题将在 ASP.NET AJAX 的背景下进行演示。

多使用“var”

少用“var”可能会导致计算错误以及逻辑错误。而且,如果未使用 var,JavaScript 解释器将很难确定变量的作用域。考虑以下简单的 JavaScript 代码

function pageLoad()
{
    i = 10;
    loop();
    alert(i);   // here, i = 100
}

function loop()
{
    for(i=0; i<100; ++i)
    {
        // Some actions
    }
}

在这里,循环使用了在 pageLoad 中之前使用的变量 i。因此,它会带来错误的结果。与 .NET 代码不同,在 JavaScript 中,变量可以跟随方法调用。所以,最好不要混淆解释器,并在代码中多使用 var

function pageLoad()
{
    var i = 10;
    loop();
    alert(i);   // here, i = 10
}

function loop()
{
    for(var i=0; i<100; ++i)
    {
        // Some actions
    }
}

减少作用域

这种情况并不常见。但是,如果您遇到这样的代码,请确保它是一个非常糟糕的实践。引入更多作用域对 JavaScript 解释器来说是一个性能问题。它会在作用域链中添加一个新的作用域。请看以下示例作用域:

function pageLoad()
{
    scope1();
    function scope1()
    {
        alert(’scope1');
        scope2();

        function scope2()
        {
            alert(’scope2');
        }
    }
}

引入更多作用域会迫使解释器在其为代码执行维护的作用域链中遍历更多部分。因此,不必要的作用域会降低性能,而且这也不是一种好的设计。

谨慎处理 DOM 元素拼接

这是一种非常常见的坏习惯。我们经常遍历数组,构建 HTML 内容,然后不断地将它们拼接到一个特定的 DOM 元素中。每次执行循环中的代码块时,都会创建 HTML 标记,查找一个 div,访问一个 divinnerHTML,并且对于 += 运算符,您将再次查找同一个 div,访问其 innerHTML,并在赋值之前进行拼接。

function pageLoad()
{
    var links = ["microsoft.com", "tanzimsaqib.com", "asp.net"];

    $get(‘divContent’).innerHTML = ‘My favorite sites:<br />’

    for(var i=0; i<links.length; ++i)
        $get(‘divContent’).innerHTML += ‘<a href="http://www.’ 
        + links[i] + ‘">http://www.’ + links[i] + ‘</a><br />’;
}

然而,正如您所知,访问 DOM 元素是 JavaScript 中最耗时的操作之一。因此,明智的做法是将所有 HTML 内容拼接到一个字符串中,最后将其赋值给 DOM 元素。这为浏览器节省了大量的工作。

function pageLoad()
{
    var links = ["microsoft.com", "tanzimsaqib.com", "asp.net"];
    var content = ‘My favorite sites:<br />’

    for(var i=0; i<links.length; ++i)
        content += ‘<a href="http://www.’ + links[i] 
        + ‘">http://www.’ + links[i] + ‘</a><br />’;

    $get(‘divContent’).innerHTML = content;
}

当有内置方法时,避免使用自己的方法

避免实现自己的 getElementById 方法,这会导致脚本到 DOM 的封送开销。每次遍历 DOM 以查找特定 HTML 元素时,都需要 JavaScript 解释器将脚本封送到 DOM。使用 document 对象的 getElementById 总是更好的。所以,在编写函数之前,请检查是否可以使用内置函数实现类似的功能。

在循环中避免使用 Array.length

这是 AJAX 中性能问题的非常常见的原因。我们经常使用如下代码:

var items = []; // Suppose a very long array
for(var i=0; i<items.length; ++i)
    ; // Some actions

如果数组非常大,这可能会导致严重的性能问题。JavaScript 是一种解释型语言,因此当解释器逐行执行代码时,每次检查循环内的条件,您最终都会每次访问 length 属性。 wherever applicable,如果数组的内容在循环执行期间不需要更改,就没有必要每次都访问 length 属性。将 length 值提取到一个变量中,并在每次迭代中使用它。

var items = []; // Suppose a very long array
var count = items.length;
for(var i=0; i<count; ++i)
    ; // Some actions

避免字符串拼接,使用数组代替

您不觉得以下代码块是牢记所有可能的最佳实践而编写的吗?有什么性能改进的选项吗?

function pageLoad()
{
    var stringArray = new Array();
    
    // Suppose there're a lot of strings in the array like:
    stringArray.push('<div>');
    stringArray.push('some content');
    stringArray.push('</div>');
    
    // ... code edited to save space
    
    var veryLongHtml = $get('divContent').innerHTML;
    var count = stringArray.length;
    
    for(var i=0; i<count; ++i)
        veryLongHtml += stringArray[i];    
}

嗯,正如您所见,divinnerHTML 已经被缓存,这样浏览器在遍历 stringArray 时就不必每次都访问 DOM,从而避免了更耗时的 DOM 方法。但是,在循环体内,JavaScript 解释器必须执行以下操作:

veryLongHtml = veryLongHtml + stringArray[i];

而且,veryLongHtml 包含一个相当长的字符串,这意味着在该操作中,解释器必须在每次迭代中检索长字符串,然后将其与 stringArray 的元素拼接起来。一个非常简短但高效的解决方案是使用数组的 join 方法,如下所示,而不是遍历数组:

veryLongHtml = stringArray.join('');

这比我们之前做的更高效,因为它将数组与较小的字符串连接起来,占用的内存更少。

引入函数委托

看看下面的循环。这个循环在每次迭代中调用一个函数,函数执行一些操作。您能想到任何性能改进吗?

for(var i=0; i<count; ++i)
    processElement(elements[i]);

对于足够大的数组,函数委托可能会带来显著的性能改进。

var delegate = processElement;
    
for(var i=0; i<count; ++i)
    delegate(elements[i]);

性能改进的原因是,JavaScript 解释器会将函数视为局部变量,而在每次迭代中不会在其作用域链中查找函数体。

引入 DOM 元素和函数缓存

我们之前已经见过 DOM 缓存,函数委托也算是一种函数缓存。看看下面的片段:

for(var i=0; i<count; ++i)
    $get('divContent').appendChild(elements[i]);

正如您可以弄清楚的那样,代码将是这样的:

var divContent = $get('divContent');
    
for(var i=0; i<count; ++i)
    divContent.appendChild(elements[i]);

这样就可以了,但您也可以缓存浏览器函数,如 appendChild。因此,最终的优化将如下所示:

var divContentAppendChild = $get('divContent').appendChild;
    
for(var i=0; i<count; ++i)
    divContentAppendChild(elements[i]);

Switch 语句的问题

与 .NET 语言或任何其他编译型语言不同,JavaScript 解释器无法优化 switch 块。尤其是在 switch 语句与不同类型的数据一起使用时。由于顺序发生的转换操作,这对浏览器来说是一项繁重的工作;尽管如此,它仍然是一种优雅的决策分支方式。

结论

在本文中,我们看到了许多用于优化 AJAX 应用程序性能的技术,当然,这些都不是新颖独特的主意,所以您也可能在其他地方找到类似的想法。

© . All rights reserved.