<<编写可维护的javascript>>总结

良好的代码规范能够增强代码的可读性,保持好的编码习惯是非常必要的,这篇文章记录由<<编写可维护的javascript>>总结的风格,以便日后查阅.

代码规范

缩进

每一行层级由2个空格组成,避免使用制表符Tab缩进.实际开发过程中,为方便使用,可以将编辑器的Tab格式修改为空格,在sublime-text的setting中增加:

1
2
"tab_size": 2,
"translate_tabs_to_spaces": true,

代码行

每一行代码的长度不超过80个字符.若超过80个字符换行时,应在一个运算符后换行,且下一行应该增加两个缩进(4个字符).

1
2
3
// good
function someFuncName(argument1, argument2, argument3, argument4, argument5,
argument6) {}

空行

适当地增加空行可以提升代码的可读性,例如在以下场景:

  • 函数之间
  • 每个控制语句之前
  • 注释之前
  • 局部变量与第一条语句之间

注释

使用注释可以帮助他人理解代码,当在以下情况时应当使用注释:

  • 代码逻辑复杂不易于理解
  • 可能被误以为是错误的代码
  • 必要但并不明显的针对特定浏览器代码
  • 对于对象/方法/属性

注释分为单行注释(//)和多行注释(/**/),注释符号与说明之间应当有一个空格.

单行注释

  • 独占一行,用于解释下一行代码
  • 代码行尾部,用于解释它之前的代码,此时应该与代码结尾至少一个缩进

多行注释

  • 首行仅以/*开始
  • 中间行以*开头并保持左对齐
  • 末行仅以*/结尾,并保持对齐
1
2
3
4
5
// good
/*
* [Description]
* [Description]
*/

命名

采用驼峰式大小写(第一个单词小写,其后单词首字母均为大写)命名方式,例如:

1
var thisName;

变量

以名词作为前缀.

1
2
3
4
5
6
// good
var name = 'Tom';
var myName = 'Jack';

// bad
var getName = 'Kevin';

函数

以动词作为前缀.

1
2
3
4
5
//good
function getData() {}

// bad
function theData() {}

常量

所有字母均为大写,且用下划线(_)分隔每个单词.

1
2
3
// good
var MIN_VALUE = 1;
const MAX_VALUE = 100; // ES6

构造函数

以名词命名,且每个单词首字母为大写.

1
2
// good
function Person() {}

严格模式

严格模式应当限制在函数内部使用,避免在全局中使用.若需要在多个函数中一次声明严格模式,可以使用立即执行函数的用法.

1
2
3
4
5
6
7
8
(function() {
'use strict';

function func1() {}

function func2() {}

}());

语句

例如if/for等保留字与其后的条件的左括号应当有一个空格,块语句的左花括号紧接第一条语句,且与有括号有一个空格,例如:

1
2
3
4
5
6
7
8
// good
if (condition) {
// code
}

for (condition) {

}

for-in 循环

  • 遍历对象实例属性
  • 遍历从原型链继承的属性

当遍历自定义对象的属性,应当避免遍历原型属性,可以使用hasOwnProperty()函数判断属性是否是实例属性.

1
2
3
4
5
6
7
var prop;

for (prop in object) {
if (object.hasOwnProperty(prop)) {
// code
}
}

变量声明

使用单var模式声明变量,每个变量初始化独占一行,赋值运算符(=)对齐,对于没有初始值的变量,应当在语句尾部.

1
2
3
4
5
// good
var name = 'Kevin',
age = 12,
school,
i;

相等

使用全等或全不等(=== or !==)

禁止使用原始包装类型

Javascript有三种原始包装类型:String, Boolean, Number.应该避免使用这些类型去实例化一个对象.

1
2
3
4
5
6
7
8
9
// bad
var name = new String('Kevin');
var booled = new Boolean(true);
var number = new Number(100);

// good
var name = 'Kevin';
var booled = true;
var number = 100;

除此之外,在定义一个数组或者对象时,也应该避免用new操作符

1
2
3
4
5
6
7
// good
var array = [],
object = {};

// bad
var array = new Array(),
object = new Object();

编程实践

UI层的松耦合

将javascript从css中抽离

1
2
3
4
// bad
.box {
width: expression(document.body.offsetWidth + "px");
}

将css从javascript中抽离

不使用对style属性直接赋值的方式,在修改元素样式时,应该操作css的className.

1
2
3
4
5
6
7
8
9
// good
.reveal {
// some css setting.
}

element.className += ' reveal';

// bad
element.style.color = 'red';

避免使用全局变量

在浏览器中,全局对象往往是window对象;在Node环境中,全局对象一般是global.
创建全局变量容易造成命名冲突导致原生属性或方法被覆盖等等问题.

单全局变量

创建一个唯一的全局对象,且命名不会与内置API冲突,并将所有属性和方法挂载在这个全局对象中.

1
2
3
4
5
6
// good
var Main = {};

Main.func = function() {
// code
}

命名空间

每个功能都挂载在同一个全局对象中,在团队开发中也会出现组员之间命名冲突等等问题.
因此,可以使用命名空间的方式,对每个功能模块划分,然后共同挂载在一个全局对象中,各个功能模块互不影响.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var MainGlobal = {
namespace: function(ns) {
var parts = ns.split('.'), // 分割字符串
object = this, // this指向MainGlobal
i,
len;

for (i = 0, len = parts.length; i < len; i++) {
if (!object[parts[i]]) {
object[parts[i]] = {};
}
object = object[parts[i]];
}

return object;
}
}

MainGlobal.namespace("Main.Business");

模块

目前流行的模块方式:AMD,CMD和CommonJS.

事件处理

隔离应用逻辑

将与应用相关的功能性代码抽离,不与事件行为代码混合,增加复用性.

1
2
3
4
5
6
7
8
9
10
11
// good
var Application = {
handleClick: function(event) {
this.showUp(event);
},

showUp: function(event) {
var popUp = document.getElementById('popup');
// logic code
}
}

不分发事件对象

在上面的实例代码中,event事件对象会被无限分发下去,应用逻辑不应当依赖与event对象来完成功能.
因此我们可以明确方法需要的参数,设计好方法的Interface,同时也方便单元测试.

1
2
3
4
5
6
7
8
9
10
var Application = {
handleClick: function(event) {
this.showUp([argument]); // [argument]指的是具体传参值
},

showUp: function(argument1, argument2) {
var popUp = document.getElementById('popup');
// logic code
}
}

避免空比较

检测原始值

Javascript有5种基本类型:字符串,数字,布尔值,null,undefined.对于基本类型的检测,
一般可以使用typeof运算符(除了null).对于null,可以使用===或!==.

1
2
3
4
5
6
7
8
9
10
11
var str = 'string',
num = 123,
bool = true;

console.log(typeof str); // 返回"string"
console.log(typeof num); // 返回"number"
console.log(typeof bool); // 返回"boolean"
console.log(typeof undefined); // 返回"undefined"

// special
console.log(typeof null); // 返回"object"

检测引用值

引用值也称对象,包括Object,Array,Date,Error几种内置引用类型.此时使用typeof均返回”object”,
因此,最好的方法可以用instanceof运算符. 

这里有一个特殊情况,由于所有的对象都继承Object,因此对引用值检测Object时都会返回true.

1
console.log(new Date() instanceof Object);  // true

检测函数

使用typeof检测函数,返回”function”.

1
2
3
function fuc() {}

console.log(typeof func === 'function'); // true

检测数组

在ECMAScript5中,可以使用Array内置方法isArray检测是否是一个数组

检测属性

判断属性是否存在的最好方法是使用in运算符.若只想检测实例对象上的属性,则使用hasOwnProperty方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var object = {
count: 0,
related: null
};

// good
if ('count' in object) {
// code
}

// bad
if (object['count']) { // 这里的值为0,不会进入if语句
// code
}

抽离配置数据

抽离配置数据能够提升可维护性,在变更的时候不需要多处改动代码,只需要改动配置数据的值.

  • URL
  • 需要展示给用户的字符串
  • 重复的值
  • 配置项
  • 任何可发生变更的值