無標題文檔

Javascript 优化计划(程序优化篇)

继续我们的 Javascript 优化计划, 上期 已经做到怎么尽可能的缩小 Javascript 脚本的文件体积便于传输。不过这样做仅仅是不够的,因为 Javascript 代码的速度被分割成两部分:下载时间(取决于文件的大小)和执行速度(取决于代码算法)。

当客户端载入 Javascript 脚本以后,真正的之行速度就取决于代码本身是否最优化了。这篇就是讲述如何优化代码本身的执行速度(听起来非常有技术的样子)。

关注作用域

浏览器中,Javascript 默认的变量范围是 window,也就是全局变量。在 window 中的变量只在页面从浏览器关闭以后才释放。而 Javascript 同时也有局部变量(私有变量)的概念,通常它在容器(比如 function)中执行完毕就会被释放。

所以很容易理解当调用某变量时,解释器就会自下(容器)由上(window)寻找变量,寻找的变量本身也是需要一点时间的。所以,解释器在作用树( 《Javascript 高级程序设计》 中称为「范围树」)中遍历的范围越短,那么脚本运行就会越快。

本人不擅长施教,下面的代码请自行理解

var country = "China";

function fn1() {
    alert(country);
}

function fn2() {
    var province = "Zhejiang";
    fn1();
}

function fn3() {
    var city = "Hangzhou";
    fn2();
}

fn3();

扩展阅读请点击 这里这里

使用局部变量

理解了上述的细节以后,接下来就非常可以理解了。使用局部变量可以带来更快的执行速度,因为解释器无需因为搜索变量而离开当前执行范围。同时,局部变量让允许完毕就会被释放,所以它们不会一直占用内存。

这里要注意的是,使用闭包会打破这一规则,详细信息可以参看 这里 和以前我做的 一道题目

避免使用 with 语句

搜索变量范围越小,运行速度越快,所以就很很容易理解避免使用 with 语句的原因。比如

alert(document.title);
alert(document.body.tagName);
alert(document.location);

可以写成

with (document) {
    alert(title);
    alert(body.tagName);
    alert(location);
}

虽然代码缩减的程度,并且也非常的容易理解。但是使用 with 语句的同时,要强制解释器不仅在作用树(范围树)内查找局部变量,还强制检测每个变量及指定的对象,看其是否有此变量或者属性。

因此,最好避免使用 with 语句。最短的代码并不一定总是最高效的。

选择正确的算法

这似乎就是废话,所有的程序员都明白正确的算法对于之行效率是多么的重要。这里就不多解释,可以参考 这篇这篇 。我始终相信,好的经验都是在实际 coding 中获得的。

循环的花招

Javascript 和大部分的程序语言一样,循环都会花费大量的执行时间,所以保持循环的高效可以减少执行时间。下面有几个花招,也是从 那本书 中获得的,照本宣科一下。

反转循环

有一个很有趣的例子,比如一个典型的循环会是这样写

for (var i = 0; i < element.length; i++) {
    // ...
}

但写成下面这个样子就有助于降低算法的复杂度,因为它用常数(O)作为条件循环以减少执行时间

for (var i = element.length - 1; i >= 0; i--) {
    // ...
}

书中的解释可能无法理解,那么我重新将其写成

var element_length = element.length;
for (var i = 0; i < element_length; i++) {
    // ...
}

可能会更好理解一些,因为它不会重复在循环中获取 element 的 length 属性,但书中的更改方法少了一个变量。

翻转循环

用 do...while 来替代 while 语句可以进一步的减少执行时间。比如

var i = 0;
while (i < element.length) {
   // ...
   i++;
}

可以改写为 do...while 语句为这个样子

var i = 0;
do {
    // ...
    i++;
} while (i < element.length);

当然,按照上一条的花招我们还可以优化成这个样子

var i = element.length - 1;
do {
    // ...
} while (--i >= 0);

这是因为 do...while 语句事先将循环体载入以后再做条件判断。不过本人认为还是保持程序的逻辑优先。

条件判断

优化 if 语句

用 if 和多个 else 语句时,将就有可能的情况放在最先,依次类推。同时尽量减少 else 和 if 的数量,将条件按照二叉树的方式进行排列。例如

if (i > 0 && i < 10) {
    alert('between 0 and 10');
} else if (i > 9 && i < 20) {
    alert('between 10 and 20');
} else if (i > 19 && i < 30) {
    alert('between 19 and 30');
} else {
    alert('out of range');
}

可以将这段代码写成

if (i > 0) {
    if (i < 10) {
        alert('between 0 and 10');
    } else {
        if (i < 20) {
            alert('between 10 and 20');
        } else {
            if (i < 30) {
                alert('between 20 and 30');
            } else {
                alert('Greater than or equal 30');
            }
        }
    }
} else {
    alert('less than or equal 0');
}

这个样子。虽然看上去非常的复杂,但是它已经考虑了很多代码潜在的条件判断情况,所以执行得更快。

switch 和 if

用 switch 还是 if 已经是老生常谈的问题了。一般来说,超过两个 if...else 判断的时候,最好是使用 switch 语句。这样做可以使代码更加清晰并且效率更高。同时,case 条件也可以使用任何类型的值。

语句瘦身

其实非常可以容易理解,脚本中的语句越少,执行所需的时间越短(听起来与上述观点有矛盾)。有很多方法可以将代码中的语句缩短,比如下面的一些花招。

定义多个变量

很明显,一条语句可以定义多个变量。这样做不仅可以缩小代码体积,还可以减少语句数量以减少执行时间。比如下面的代码

var webSite   = "www.gracecode.com";
var haveLunch = function () {...}; 

就可以精简为

var webSite = "www.gracecode.com", haveLunch = function () {...};

相信这样的语句也不会给阅读带来多大的障碍。

迭代因子

使用迭代因子,尽可能的合并语句。比如

var girlFriend = girl[i];
i++;

这样的语句可以使用

var girlFriend = girl[i++];

替代。不过建议特别小心 i++ 和 ++i 的区别 (该死的 C 语言后遗症)。

使用数组和对象字面量

这点其实在 上一篇 的时候就提到过,在这里就不复述。比如

var mySite = new Object;
mySite.author = "feelinglucky";
mySite.location = "http://www.gracecode.com";

就可以精简到

var mySite = {author:"feeinglucky", location:"http://www.gracecode.com"};

这样子。

其他的花招

优先使用内置方法

比如

function power(number, n) {
   var result = number;
   
   for (var i = 1; i < n; i++) {
       result *= number;
   }

   return result;
}

这样的函数,完全就可以使用 Math.pow 来完成。Javascript 已经有很多现成的内置方法,只要允许最好使用它们。

存储常用的值

当多次用到同一个值的时候,可以先将其存储在局部变量中,以便快速访问。这个就不复述了,偷个懒不好意思。

DOM 操作

节约 DOM 操作

Javascript 对 DOM 的处理,可能是最耗费时间的操作之一。每次 Javascript 对 DOM 的操作,浏览器都会改变页面的表现、重新渲染页面,从而有明显的时间损耗。比较环保的做法就是尽可能不在 DOM 中进行 DOM 操作。

请看下面的例子,为 ul 添加 10 个条目

var oUl = document.getElementById("ulItem");

for (var i = 0; i < 10; i++) {
    var oLi = document.createElement("li");
    oUl.appendChild(oLi);
    oLi.appendChild(document.createTextNode("Item " + i);
}

乍看起来似乎无懈可击,但是这段代码的确有问题。首先是循环中的 oUl.appendChild(oLi); 的调用,每次执行过后浏览器就会重新渲染页面;其次,给列表添加添加文本节点(oLi.appendChind(document.createTextNode(\"Item \" + i);),也这会造成页面重新渲染。每次运行都会造成两次页面重新渲染,总计 20 次。

要解决这个问题就如上面所言的,减少 DOM 操作,将列表项目在添加好文本节点以后再添加。下面的代码就可以与上述的代码完成同样的任务。

var oUl   = document.getElementById("ulItem");
var oTemp = document.createDocumentFragment();

for (var i = 0; i < 10; i++) {
    var oLi = document.createElement("li");
    oLi.appendChild(document.createTextNode("Item " + i);
    oTemp.appendChild(oLi);
}

oUl.appendChild(oTemp);

遵循标准的 DOM

说点书中没有的(照本宣科完毕),Javascript 其实在寻找节点(Node)也会花上一段时间。对 Web 标准友好的 (x)html 文档相对杂乱文章的页面来说,Javascript 执行速度两者也会有所差别。

浏览器处理页面有模式之分,这也许也是为什么要编写遵循 Web 标准的页面的原因之一。具体信息可以参考 这里一些言论

缓存 Ajax

Ajax 虽然提供了页面异步请求调用,但别忘记了它还是访问服务器的。Javascript 作为驱动层本身可以作为缓存使用,虽然在页面重新载入后就会被释放,但对于服务器而言这是一个好的消息。

结束语

不知不觉就写了那么多,很多东西都是书上照本宣科的。《Javascript 高级程序设计》的确是一本不可多得的好书,建议大家有机会都可以去看看。这本书不贵,59 RMB(可能在别的地方还有打折),对于烟民而言也就一条 双精度红喜 ,不过它可比香烟所能带来的快感多得多。

全文完

Javascript 优化计划(文件瘦身篇)

最近一直在研究 Javascript 相关的技术。在 《Javascript 高级程序设计》 有篇章节着重阐述了优化 Javascript 代码的重要性。相信有很多的 Javascript 开发人员在开发的同时或多或少的会接触到此类的问题。

在大部分情况下,代码的优化并不是实际开发中所需要着重的部分。但是一旦代码完成了以后,开发者总是期待自己的代码能够越短越高效越好。结合从书中获得的知识以及本人实际开发过程中的经验,下面说明本人所采取的一些花招(也算是照本宣科一下)。

前言

相比脚本语言,编译型的语言并不需要太关心优化问题。在极大的程度上,编译时编译器都会之行优化操作。比如所有的变量、函数、对象等等都会替换成只有处理器才能理解的符号和指针(就是通常所指的可执行文件)。其他的脚本语言也并不需要过分在意文件的大小问题,但是 Javascript 则不同。

因为它首先要通过从服务器端下载源代码,然后再由客户端的浏览器执行。因此,Javascript 代码的速度被分割成两部分:下载时间(取决于文件的大小)和执行速度(取决于代码算法)。这篇主要讨论的是 Javascript 的下载时间优化,也就是如何尽可能的缩小 Javascript 文件本身的容量。

在这里要记住的一个数字是 1160,这是能放入单个 TCP/IP 包中的字节数。所以,最好的期望值是能将每个 Javascript 文件保持在 1160 字节一下,以获取最优的下载时间。

删除注释

这似乎是是废话,不过很多开发人员都会忘记。在实际生产环境中,脚本中的注释都应该删除。在开发期间注释相当的重要,它可以帮助团队理解代码。但在实际生产环境中,注释会明显使脚本文件体积变大。删除它们并不会给脚本实际运行带来任何的影响。

删除制表符和空格

具有良好缩进和空格的代码通常都具有良好的可读性。但是浏览器并不需要这些额外的制表符和空格,所以最好删除它们。当然也不要忘记函数参数,赋值语句以及比较操作之间的空格。比如

function showMeTheMoney(money)
{
    if (!money) {
        return false;
    } else {
        ...
    }
}

可以优化成

function showMeTheMoney(money){if(!money){reutrn false;}else{...}}

这样可以减少部分容量。

删除所有的换行

有许多关于在 Javascript 中换行应该存在的思考,但底线都是换行要增加代码的可读性。但过分的换行也会造成代码体积的增加。

可能处于某种原因而不能删除换行符,这样则要保证文件是 Unix 格式的。因为 Windows、Mac 格式的换行符用两个字符表示换行;Unix 仅用一个。所以将文件转换成 Unix 格式也可以节约一些字节数。

替换变量名

这可能是最无聊的一种做法,这通常不是手工完成的。毕竟变量的名称对解释器来说毫无意义(只是对开发人员来说会更友好一些),在生产环境中将描述性的变量名替换成更简单、更短的名称也可以缩减一部分体积。比如上述的代码可以缩减成:

function sm(m){if(!m){reutrn false;}else{...}}

这样虽然看起来会比较的头痛,不过实际执行效果是一样的。

使用工具

实际使用上述的方法可能会有一些困难,幸好有现成的外部工具能完成这些步骤。下面简单的介绍几个:

ECMAScript Cruncher: http://saltstorm.net/depo/esc/

其他方法

替换布尔值

对于比较来说,true 就等于 1,false 就等于 0 。因此,脚本包含的字面量 true 都可以用 1 来替换,而 false 可以用 0 来替换。对于 true 节省了 3 个字节,而 false 则节省了 4 个字节。

缩短否定检测

代码中常常会出现检测某个值是否有效的语句。而大部分条件非的判断就是判断某个变量是否为 undefined、null 或者 false,比如:

if (myValue != undefined) {
    // ...
}

if (myValue != null) {
    // ...
}

if (myValue != false) {
    // ...
}

这些虽然都正确,但用逻辑非操作符也可以有同样的效果:

if (!myValue) {
    // ...
}

这样的替换也可以节省一部分字节。

使用数组和对象字面量

这个比较好理解,比如一下两行是相同的:

var myArray = new Array;
var myArray = [];

然而第二行比第一行短很多,而且也能非常容易的理解。类似的还有对象声明:

var myObject = new Object;
var myObject = {};

举个例子,比如下面的语句:

var mySite = new Object;
mySite.author = "feelinglucky";
mySite.location = "http://www.gracecode.com";

这样写也可以非常容易的理解,并且短很多:

var mySite = {author:"feeinglucky", location:"http://www.gracecode.com"};

好的,这期就到这里。就向上面说的,Javascript 代码的速度被分割成两部分:下载时间(取决于文件的大小)和执行速度(取决于代码算法)。这次讨论的是下载时间方面的优化,下期讨论之行速度方面的优化(这样看起来非常有技术含量,不是么)。

下面布置家庭作业。或许大家会有兴趣了解下 jQuery 是怎么把自己 70KB 的代码压缩至 20KB 左右的。

未完待续...

JavaScript 程序编码规范

这是我根据 Cloudwater 在译言上发表的 Javascript 程序编码规范 整理而来(英文版 在这里 )。感谢 Cloudwater 翻译了这篇文档,它对于我来说非常的重要。我将其整理成了 PDF 格式的文档,便于保存和查看。有任何排版上的问题,欢迎大家不吝指出。

最后下载地址:

PDF 格式ZIP 打包 ,另外我还在 Google Doc 上发布了这篇文档 ,有兴趣的可以加入一起完善。

我的照片

嗨!我叫「明城」,八零后、码农、宁波佬,现居杭州。除了这里,同时也欢迎您关注我的 GitHubTwitterInstagram 等。

这个 Blog 原先的名字叫 Gracecode.com 、现在叫 「無標題文檔」 。 要知道作为码农取名是件很难的事情,所以不想在取名这事情上太费心思。

作为八零后,自认为还仅存点点可能不怎么被理解的幽默感,以及对平淡生活的追求和向往。 为了避免不必要的麻烦,声明本站所输出的内容以及观点仅代表个人,不代表自己所服务公司或组织的任何立场。

如果您想联系我,可以发我邮件 `echo bWluZ2NoZW5nQG91dGxvb2suY29tCg== | base64 -d`

分类

搜索

文章