JavaScript最佳实践

1. 可维护性

1.1 什么是可维护性

可维护性的代码有一些特征:

  1. 可理解性——其他人可以接受代码并理解它的意图和一般途径,无需原开发人员完整解释

  2. 直观性——代码中的东西一看就明白,无论操作多么复杂

  3. 可适应性——代码以一种数据上的变化不要求完全重写方法

  4. 可扩展性——在代码架构上考虑到在未来对核心功能扩展

  5. 可调试性——有地方出错,代码给予足够的信息直接地确定问题所在啊

1.2 代码约定

以下小节讨论代码约定的概论

1.2.1 可读性

  • 缩进:常见的缩进大小为4个空格,

  • 注释

    1. 函数和方法——每个函数或方法都应该包含一个注释,描述其目的和用于完成任务用到的算法。事先的假设也很重要,如参数代表什么,函数是否有返回值
    2. 大段代码——完成单个任务的多行代码应该在前面放一个描述任务的注释
    3. 复杂算法——如果使用一种独特的方式解决问题,应该解释一下你是如何做的,不仅帮助其他浏览你代码的人,也帮助自己下次理解
    4. Hack——不要假设其他人在看代码能够理解 hack 所要应付的浏览器问题,直接写出。

1.2.2 变量和函数命名

  1. 变量和函数应该使用合乎逻辑的名字,不用担心长度。长度问题可以通过后处理和压缩来缓解。

  2. 函数名应该以动词开始

1.2.3 变量类型透明

由于 js 中变量是松散类型,很容易忘记变量所应包含的数据类型。合适的命名方式可以一定程度上缓解这问题。

初始化。当定义一个变量后就应该被初始化为一个值,来暗示将来如何用。

1
2
3
4
5
//通过初始化指定变量类型
var found = flase; //布尔型
var count = -1; //数字
var name = ""; //字符串
var person = null; //对象

1.3 松散耦合

只要应用的某个部分过分依赖于另外一个部分,代码就是耦合过紧。因为 Web 应用设计的技术,有多种情况使它耦合过紧

1. 解耦 HTML/JavaScript:
HTML 是数据,JavaScript 是行为,记住这两点。

1
2
3
4
5
//将 HTML 紧密耦合到 JavaScript
function insert(msg){
var container = document.getElementById("container");
container.innerHTML = "<div> aa </div>";
}

当使用上面这个例子,有一个页面布局问题,可能和动态创建的 HTML 没有被正确格式化有关。不过要定位这个错误非常困难,因为你可能一般先看到页面的源代码来查找那烦人的 HTML,但是没有找到,因为他是动态生成的。因此一般来说,应该避免在 JavaScript 中大量创建 HTML。一定要保持层次分离,这样可以很容易确定错误来源。

一般在页面中直接包含并隐藏标记,等整个页面渲染好之后,再用 javaScript 显示该标志,而非生成它。

2. 解耦 CSS/JavaScript
最常见耦合的例子就是使用 JavaScript 来更改某些样式

1
2
3
//CSS 对 JavaScript 的紧密耦合
element.style.color = "red";
element.style.BackgroundColor = "blue";

由于 CSS 主要负责页面的显示,当显示出现任何问题都应该只是查看 CSS 文件来解决,然而使用 JavaScript 来更改样式就出现了第二个可能已更改和检查的地方。结果 JavaScript 也负责了页面的显示。如果未来需要修改样式表,那 CSS 和 JavaScript 文件都要修改,这是一个噩梦。

可以让耦合更松散,通过动态更改样式类而非特定的样式来实现。

1
2
//CSS 对 JavaScript 的松散耦合
element.className = "edit";

3. 解耦应用逻辑/事件处理程序
每个 Web 应用都有相当多的事件处理程序,然而很少能有将应用逻辑从事件处理程序中分离的。请看例子

1
2
3
4
5
6
7
8
9
10
function handleKeyPress(event){
event = EventUtil.getEvent(event);
if(event.keyCode == 13){
var target = EventUtil.getTarget(event);
var value = pareInt(target.value);
if(value > 5){
doucument.getElementById("msg").style.display = "block";
}
}
}

这个事件处理程序既包含应用逻辑,又进行事件的处理。这让问题变得双重性,一是如果没有预想的结果,那是因为事件处理程序没有被调用还是逻辑失败?二是如果一个后续的事件引发同样的应用逻辑,那还得复制功能代码。

较好的改动是将应用逻辑和事件处理程序分离。

1
2
3
4
5
6
7
8
9
10
11
12
13
function handleValue(value){
value = parseInt(value);
if(value > 5){
doucument.getElementById("msg").style.display = "block";
}
}
function handleKeyPress(event){
event = EventUtil.getEvent(event);
if(event.keyCode == 13){
var target = EventUtil.getTarget(event);
handleValue(target.value);
}
}

注意函数 handleValue 中没有任何东西会依赖于任何事件处理程序,它只是接收一个值,并根据该值处理。

以下是应牢记的应用和业务逻辑之间松散耦合的几条原则:

  • 不要将 event 对象传给其他方法;只传来自 event 对象所需的数据

  • 任何事件处理程序都应该处理事件,然后讲处理转交给应用逻辑

  • 任何可以在应用层面的动作都应该可以在不执行任何事件处理程序下进行

1.4 编程实践

书写可维护的 Js 并不仅仅关于如何格式化代码,还关系到代码做什么问题。在企业中往往有大量人员一起创作一个 Web 应用。这种情况下目标是是确保每个人所使用的浏览器环境都有一致和不变的规则。

1. 尊重对象所有权。(最重要)

它的意思是你不能修改不属于你的对象。简单来说,如果你不负责创建或维护某个对象、它的对象或者它的方法,那么你就不能对他们进行修改。更具体来说:

  • 不要为实例或原型添加属性;

  • 不要为实例或原型添加方法;

  • 不要重定义已存在的方法;

著名的 Prototype JavaScript 库就出现过这种例子:它为 document 对象实现了 getElementsByClassName() 方法,返回一个 Array 的实例并增加了一个 each() 方法。使用该库的人习惯

1
document.getElementsByClassName("msg").each();

….BOOM

你可以通过以下方式为对象创建新的功能:

  • 创建包含所需功能的新对象,并用它与相关对象进行交互。
  • 创建自定义类型,继承需要修改的类型,为它添加功能。

2. 避免全局量

最多创建一个全局变量,让其他对象和函数存在其中。

1
2
3
4
5
//两个全局量——避免!
var name = "Nicholas";
function sayName(){
alert(name);
}

1
2
3
4
5
6
7
//一个全局量——推荐!
var MyApplication = {
name : "Nicholas",
sayName : function(){
alert(this.name);
}
}

消除了前一段代码存在的问题。首先,变量 name 覆盖了 window.name 属性,可能与其他功能产生冲突;其次,它有主消除功能作用域之间的混淆。

命名空间:单一的全局对象 xxx 作为一个容器,其中定义了其他对象。用这种方式将功能组合在一起的对象。用来确定每个人都统一使用的全局对象的名字,并且尽可能唯一。

1
2
3
4
5
6
7
8
//创建全局对象
var Wrox = {};
//为 Professional JavaScript 创建命名空间
Wrox.ProJs = {};
//将书中用到的对象附加上去
Wrox.ProJs.EventUtil = {..};

避免了其他库的命名冲突。

3. 避免与 null 进行比较

直接将值与 null比较是使用过度的,并且由于不充分的类型检测导致错误。

1
2
3
4
5
function sortArray(values){
if(values != null){
values.sort();
}
}

这里的 if 语句有其他值可以通过,包括字符串、数字、它们会导致函数跑出错误。因为 JavaScript 不做任何自动的类型检查。

修改为:

1
2
3
4
5
function sortArray(values){
if( values instanceof Array){
values.sort();
}
}

这个版本可以阻止所有非法值,完全用不着 null。

如果看到了与 null 比较代码,尝试使用以下技术替换:

  • 值应为引用类型,使用 instanceof 操作符检查其构造函数;

  • 值应为基本类型,使用 typeof 检查其类型;

  • 如果希望对象包含某个特定的方法名,则使用 typeof 操作符确保指定名字的方法存在于对象上。

代码中 null 比较越少,越容易确定代码的目的。

4. 使用常量

1
2
3
var Constans = {
Url: xxxx
};