一、可维护性
1 什么是可维护的代码
- 可理解性——其他人可以接受代码并理解它的意图和一般途径,而无需原开发人员的完整解释。
- 直观性——代码中的东西一看就能明白,不管其操作过程多么复杂。
- 可适应性——代码以一种数据上的变化不要求完全重写的方法撰述。
- 可扩展性——在代码架构上已考虑到在未来允许对核心功能进行扩展。
- 可调试性——当有地方出错时,代码可以给予你足够的信息来尽可能直接地确定问题所在。
2 代码约定
1)可读性
可读性与代码作为文本文件的格式化方式有关。可读性的大部分内容都是和代码的缩进相关。可读性的另一方面是注释。
2)变量和函数命名
3)变量类型透明
3 松散耦合
1)解耦HTML/JavaScript
HTML呈现应该尽可能与JavaScript保持分离。当JavaScript用于插入数据时,尽量不要直接插入标记。一般可以在页面中直接包含并隐藏标记,然后等到整个页面渲染好之后,就可以用JavaScript显示该标记,而非生成它。另一种方法是进行Ajax请求并获取更多要显示的HTML,这个方法可以让同样的渲染层来输出标记,而不是直接嵌在JavaScript中。
2)解耦CSS/JavaScript
JavaScript只通过动态更改样式类而非特定样式。
3)解耦应用逻辑/事件处理程序
将应用逻辑和事件处理程序相分离,这样两者分别处理各自的东西。一个事件处理程序应该从事件对象中提取相关信息,并将这些信息传送到处理应用逻辑的某个方法中。
一下是要牢记的应用和业务逻辑之间松散耦合的几条原则:
- 勿将event对象传给其它方法;只传来自event对象中所需的数据;
- 任何可以在应用层面的动作都应该可以在不执行任何事件处理程序的情况下进行;
- 任何事件处理程序都应该处理事件,然后将处理转交给应用逻辑。
4 编程实践
1 尊重对象所有权
- 不要为实例或原型添加属性
- 避免全局量
- 避免与null进行比较
- 使用常量
二、性能
1 注意作用域
1)避免全局查找
可能优化脚本性能最重要的就是注意全局查找。使用全局变量和函数肯定要比局部的开销更大,因为要设计作用域链上的查找。
糟糕的例子:
function updateUI() { var imgs = document.getElementByTagName("img"); for(var i = 0, len = imgs.length; i < len; i++) { imgs[i].title = document.title + " image" + i; } var msg = document.getElementById("msg"); msg.innerHTML = "Update complete."; }
改为:
function updateUI() { var doc = document; var imgs = doc.getElementsByTagName("img"); for(var i = 0, len = imgs.length; i < len; i++) { imgs[i].title = doc.title + " image " + i; } var msg = doc.getElementById("msg"); msg.innerHTML = "Update complete"; }
2)避免with语句
with语句会创建自己的作用域,一次会增加其中执行的代码的作用域链的长度。由于额外的作用域链查找,在with语句中执行的代码肯定会比外面执行的代码要慢。
2 选择正确方法
1)避免不必要的属性查找
使用变量和数组要比访问对象上的属性更有效率,后者是一个O(n)操作。对象上的任何属性查找都要比访问变量或者数组花费更长的时间,因此必须在原型链中对拥有该名称的属性进行一次搜索。简而言之,属性查找越多,执行时间就越长。
尽可能多地使用局部变量将属性查找替换为值查找。
2)优化循环
3)展开循环
4)避免双重解释
当JavaScript代码想解析JavaScript的时候就会存在双重解释惩罚。当使用eval()函数或者是Function构造函数以及使用setTimeout()传入一个字符串参数时都会发生这种情况。因为代码时包含在字符串中,不能在初始的解析过程中完成,也就是说在JavaScript代码运行的同事必须新启动一个解析器来解析新的代码。实例化一个新的解析器有不容忽视的开销,所以这种代码要比直接解析慢的多。
5)性能的其他注意事项
- 原生方法较快
- Switch语句较快
- 位运算符较快
三、最小化语句数
1 多个变量声明
2 插入迭代值
当使用迭代值(也就是在不同的位置进行增加或较少的值)的时候,尽可能合并语句。
3 使用数组和对象字面量
两种创建数组和对象的方法:使用构造函数或者是使用字面量。使用构造函数总是要用到很多的语句来插入元素或者定义属性,而字面量可以将这些操作在一个语句中完成。
四、优化DOM交互
1 最小化现场更新
一旦你需要访问的DOM部分是已经显示的页面的一部分,那么你就是在进行一个现场更新。之所以叫现场更新,是因为需要立即(现场)对页面对用户的显示进行更新。每一个更改,不管是插入单个字符,还是移除整个片段,都有一个性能惩罚,因为浏览器要重新计算无数尺寸以进行更新。现场更新进行得越多,代码完成执行所花的时间就越长;完成一个操作所需的现场更新越少,代码就越快。
糟糕的例子:
var list = document.getElementById("myList"), item, i; for(i = 0; i < 10; i++) { item = document.createElement("li"); list.appendChild(item); item.appendChild(document.createTextNode("Item " + i)); }
更改:
var list = document.getElementById("myList"), fragment = document.createDocumentFragment(), item, i; for(i = 0; i < 10; i++) { item = document.createElement("li"); fragment.appendChild(item); item.appendChild(document.createTextNode("Item " + i)); } list.appendChild(fragment);
2 使用innerHTML
有两种在页面上创建DOM节点的方法:使用诸如createElement()和appendChild()之类的DOM方法,以及使用innerHTML。对于小的DOM更改而言,两种方法效率都差不多。然而,对于大的DOM更改,使用innerHTML要比使用标准DOM方法创建同样的DOM结构快的多。
当把innerHTML设置为某个值时,后台会创建一个HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JavaScript的DOM调用。由于内部方法是编译好的而非解释执行的,所以执行快得多。
前面的例子还可以用innerHTML改写如下:
var list = document.getElementById("myList"); html = "", i; for(i = 0; i < 10; i++) { html += "<li>Item " + i + "</li>"; } list.innerHTML = html;
3 使用事件代理
页面上的事件处理程序的数量和页面相应用户交互的速度之间有个负相关。为了减轻这种惩罚,最好使用事件代理。
4 注意HTMLCollection
任何时候要访问HTMLCollection,不管它是一个属性还是一个方法,都是在文档上进行一个查询,这个查询开销很昂贵。最小化访问HTMLCollection的次数可以极大地改进脚本的性能。
三、部署
1 构建过程
写的代码不应该原封不动地放入浏览器中,理由如下。
- 知识产权问题——如果把带有完整注释的代码放到线上,那别人就更容易知道你的意图,对它再利用,并且可能找到安全漏洞。
- 文件大小——书写代码要保证容易阅读,才能更好地维护,但是这对性能是不利的。浏览器并不能从额外的空白字符或者冗长的函数名和变量名中获得什么好处。
- 代码组织——组织代码要考虑到可维护性并不一定是传送给浏览器的最好方式。
推荐Web应用中尽可能使用最少的JavaScript文件,是因为HTTP请求是Web中的主要性能瓶颈之一。记住通过<script>标记引用JavaScript文件是一个阻塞操作,当代码下载并运行的时候会停止其他所有的下载。因此,尽量从逻辑上将JavaScript代码分组成部署文件。
2 验证
3 压缩
当谈及JavaScript文件压缩,其实在谈论两个东西:代码长度和配重(Wire weight)。代码长度指的是浏览器所需解析的字节数,配重指的是实际从服务器传送到浏览器的字节数。
1)文件压缩
压缩器一般进行如下一些步骤:
- 删除额外的空白(包括换行);
- 删除所有注释;
- 缩短变量名。
2)HTTP压缩
配重指的是实际从服务器传送到浏览器的字节数。因为现在的服务器和浏览器都具有压缩功能,这个字节数不一定和代码长度一样。