可维护性
编写可维护的代码十分重要,因为大多数开发者会花大量时间去维护别人写的代码。实际开发中,从第一行代码开始写起的情况非常少,通常是要在别人的代码之上构建自己的工作。让自己的代码容易维护,可以保证其它开发者更好地完成自己的工作。
什么是可维护代码
通常,代码“可维护”意味着它具备如下的特点:
- 容易理解:无须求助原始开发者,任何人一看代码就知道它是干什么的,以及它是怎么实现的。
- 符合常识:代码中的一切都显得顺理成章,无论操作有多么复杂。
- 容易适配:即使数据发生变化也不用完全重写、
- 容易扩展:代码架构经过认真设计,支持未来扩展核心功能。
- 容易调试:出问题时,代码可以给出明确的信息,通过它能直接定位问题。 能够写出可维护的JavaScript代码是一项重要的专业技能。这就是业余爱好者和专业开发人员之间的区别,前者用一个周末就拼凑出一个网站,而后者真正了解自己的技术。
编码规范
编码规范对 JavaScript 而言非常重要,因为这门语言实在太灵活了。与大多数面向对象语言不同,JavaScript 并不强迫开发者把任何东西都定义为对象。它支持任何编程风格,包括传统的面向对象编程、声明式编程,以及函数式编程。
下面列举列举一些编码规范的一些基础知识:
- 可读性 想让代码容易维护,首先必须使其可读。可读性必须考虑代码是一种文本文件。为此,代码缩进是保证可读性的重要基础。如果所有人都使用相同的缩进,整个项目的代码就会更容易让人看懂。缩进通常要使用空格数而不是Tab(制表符)来定义,因为后者在不同文本编辑器中的显示不同。一般来说,缩进是4个空格,当然具体多少个可以自己定。可读性的另一方面是代码注释。在大多数编程语言中,广泛接受的做法是每个方法都编写注释。因为 JavaScript 可以在代码中的任何地方创建函数,所以这一点经常被忽视。正因为如此,可能给JavaScript中的每个函数都写注释才更重要,一般来说,以下地方都该写注释。
- 函数和方法:描述用途、完成任务所用算法、参数含义、是否有返回值
- 大型代码块:多行代码但用于完成单一任务的,应该在前面给出注释,把要完成的任务写清楚。
- 复杂的算法:如果使用了独特的方法解决问题,要通过注释解释明白。
- 使用黑科技:如果代码中包含例如解决浏览器兼容的黑科技,要在注释里写出来,避免别人误以为别人任务没用而精简代码等。
- 变量和函数命名 代码中变量和函数适当命名对于其可读性和可维护性至关重要。因为很多js开发者是业余爱好出身,所以很容易用foo、bar命名变量,用dosomething来命名函数。专业js开发者必须改掉这些习惯,这些才能写出可维护的代码。以下是关于命名的通用规则。
- 变量名应该是名词,如car、person。
- 函数名应该以动词开始,例如getName()。返回布尔值的函数通常以is开头,比如isEnabled()。
- 对变量和函数都使用符合逻辑的名称,不用担心长度。长名字的问题可以通过后处理和压缩解决。
- 变量、函数和方法应该以小写字母开头,使用驼峰大小写(camelCase)形式,如 getName() 和 isPerson。类名应该首字母大写,如Person、RequestFactory。常量值应该全部大写并以下划线相接,比如REQUEST_TIMEOUT。
- 名称要尽量用描述性和直观的词汇,但不要过于冗长。getName()一看就知道会返回名称,而PersonFactory一看就知道会产生某个Person对象或实体。
要完全避免没有用的变量名,如不能,如不能表示所包含数据的类型的变量名。通过适当命名,代码读起来就会像故事,因此更容易理解。
- 变量类型透明化,通过注释方式在定义变量的时候说明清楚。
松散耦合
只要应用程序的某个部分对另一个部分依赖得过于紧密,代码就会变成紧密耦合,因而难以维护。典型的问题是在一个对象中直接引用另一个对象,这样,修改其中一个,可能必须还得修改另外一个。紧密耦合的软件难以维护,肯定需要频繁地重写。
考虑到相关技术,web应用程序在某些情况下可能变得过于紧密耦合。关键在于有这个意识,随时注意不要让代码产生紧密耦合。
- 解耦HTML/JavaScript
- 解耦CSS/JavaScript
- 解耦应用程序逻辑/事件处理程序
以下是在解耦应用程序逻辑和业务逻辑时应该注意的几点:
- 不要把event对象传给其它方法,而是只传递event对象中必要的数据。
- 应用程序中每个可能的操作都应该无须事件处理程序就可以执行。
- 事件处理程序应该处理事件,而把后续处理交给应用程序逻辑。 做到上述几点能够给任何代码的可维护性带来巨大的提升,同时也能为将来的测试和开发提供很多的可能性。
编码惯例
- 尊重对象所有权
- 不声明全局变量
- 不要比较null
- 使用常量
性能
作用域意识
- 避免全局查找
- 不使用with语句
选择正确的方法
- 避免不必要的属性查找
- 优化循环 循环是编程中常用的语法构造,因此在JavaScript中也十分常见。优化这些循环是性能优化的重要内容,因为循环会重复多次运行相同的代码,所以运行时间会自动增加。其它语言有很多关于优化循环的研究,这些技术同样适用于JavaScript。优化循环的基本步骤如下:
- 简化终止条件。
- 简化循环体。循环体是最花时间的部分,因此要尽可能优化。要确保其中不包含可以轻松转移到循环外部的密集计算。
- 使用后测试循环。最常见的循环就是for和while循环,这两种循环都属于先测试循环,do-while就是后测试循环,避免了对终止条件初始评估,因此应该会更快。
- 展开循环
- 避免重复解释
- 其它性能优化注意事项
在评估代码性能时还有一些可能需要注意。下面列出的虽然不是主要问题,但在使用比较频繁的时候也可能有所不同。- 原生方法很快
- switch语句很快
- 位操作很快
语句最少化
JavaScript 代码中语句的数量影响操作执行的速度。一条可以执行多个操作的语句,比多条语句中每个语句执行一个操作要快。那么优化的目标就是寻找可以合并的语句,以减少整个脚本的执行时间。为此,可以参考如下几种模式。
- 多个变量声明,多个变量之间以逗号分隔。这种优化很容易做到,且比使用多条语句执行速度更快。
- 插入迭代性值,任何时候只要使用迭代性值(即会递增或递减的值),都要尽可能使用组合语句。
//优化前
let name = values[i];
i++;
//优化后
let name = values[i++];
- 声明对象或数组时,尽量使用字面量进行初始化而避免使用构造函数,且初始化时候就定义好值。
优化DOM交互
在所有 JavaScript 代码中,涉及 DOM 的部分无疑是非常慢的。DOM 操作和交互需要占用大量时间,因为经常需要重新渲染整个或部分页面。此外,看起来简单的操作也可能花费很长时间,因为 DOM 中携带着大量信息。
理解如何优化DOM交互可以极大地提升脚本的执行速度:
- 实时更新最小化,实时更新的次数越多,执行代码所需的时间也越长。反之,实时更新的次数越少,代码执行就越快。示例代码:
//优化前
let list = document.getElementById("myList"),
item;
for (let i = 0; i < 10; i++) {
item = document.createElement("li");
list.appendChild(item);
item.appendChild(document.createTextNode('Item ${i}');
};
//优化后
let list = document.getElementById("myList"),fragment = document.createDocumentFragment(),item;
for (let i = 0; i < 10; i++) {
item = document.createElement("li");
fragment.appendChild(item);
item.appendChild(document.createTextNode("Item " + i));
}
list.appendChild(fragment);
- 使用innerHTML,在页面中创建新 DOM节点的方式有两种:使用 DOM方法如 createElement()和 appendChild(),以及使用 innerHTML。对于少量 DOM 更新,这两种技术区别不大,但对于大量 DOM 更新,使用innerHTML 要比使用标准 DOM 方法创建同样的结构快很多。示例代码:
//优化前
let list = document.getElementById("myList");
for (let i = 0; i < 10; i++) {
list.innerHTML += '<li>Item ${i}</li>'; // 不要
}
//优化后
let list = document.getElementById("myList"),
html = "";
for (let i = 0; i < 10; i++) {
html += '<li>Item ${i}</li>';
}
list.innerHTML = html;
注意
使用 innerHTML 可以提升性能,但也会暴露巨大的 XSS 攻击面。无论何时使用它填充不受控的数据,都有可能被攻击者注入可执行代码。此时必须要当心。
- 使用事件委托,大多数 Web 应用程序会大量使用事件处理程序实现用户交互。一个页面中事件处理程序的数量与页面响应用户交互的速度有直接关系。为了减少对页面响应的影响,应该尽可能使用事件委托。事件委托利用了事件的冒泡。任何冒泡的事件都可以不在事件目标上,而在目标的任何祖先元素上处理。基于这个认知,可以把事件处理程序添加到负责处理多个目标的高层元素上。只要可能,就应该在文档级添加事件处理程序,因为在文档级可以处理整个页面的事件。
- 注意 HTMLCollection 由于 Web 应用程序存在很大的性能问题,任何时候,只要访问 HTMLCollection,无论是它的属性还是方法,就会触发查询文档,而这个查询相当耗时。减少访问 HTMLCollection 的次数可以极大地提升脚本的性能。可能优化 HTMLCollection 访问最关键地方就是循环了。示例:
//优化前
let images = document.getElementsByTagName("img");
for (let i = 0, len = images.length; i < len; i++) {
// 处理
}
//优化后
/*
这里的关键是把 length 保存到了 len 变量中,而不是每次都读一次 HTMLCollection 的 length属性。在循环中使用 HTMLCollection 时,应该首先取得对要使用的元素的引用,如下面所示。这样才能避免在循环体内多次调用 HTMLCollection:
*/
let images = document.getElementsByTagName("img"),
image;
for (let i = 0, len=images.length; i < len; i++) {
image = images[i];
// 处理
}
编写 JavaScript 代码时,关键是要记住,只要返回 HTMLCollection 对象,就应该尽量不访问它。以下情形会返回 HTMLCollection:
- 调用 getElementsByTagName();
- 读取元素的 childNodes 属性;
- 读取元素的 attributes 属性;
- 访问特殊集合,如 document.form、document.images 等。 理解什么时候会碰到 HTMLCollection 对象并适当地使用它,有助于明显地提升代码执行速度。