编程风格

  • 约定缩进,使用tab或者空格缩进取决于团队的规范。

  • 不要省略分号

  • 行长度80个字符

  • 换行:通常在运算符后换行,下一行增加两个层级的缩进。

  • 空行:为了提高代码的可读性。下面这些情况插入空行

    • 方法之间插入空行

    • 在方法中的局部变量和第一条语句之间

    • 在多行或者单行注释之前

    • 在方法内的逻辑片段之间插入空行

  • 命名:采用驼峰大小写(Camel Case)命名法

UI层的松耦合

将CSS从JavaScript中抽离,尽量在JavaScript中使用CSS的类,而非直接操作style样式。

1
2
3
4
5
6
7
8

// 原生

element.className += " class"; // 注意class前面是带有空格的

// jQuery

$(element).addClass('className');

避免全局变量

全局变量所带来的问题

* 命名冲突

* 增加代码脆弱性:依赖全局变量使得代码和上下文是深耦合。

* 难以测试

要避免意外产生的全局变量

我们都知道,在JavaScript中,如何没有使用var声明变量的话,那么JavaScript会自动创建一个全局变量。比如下面的例子:

1
2
3
4

var count = 10;

title = 'title';

本意应该是声明两个变量,结果不小心将分号输错位逗号,结果就会将title创建成了全局变量。

解决办法

采用单全局变量方式

全文只创建一个全局变量,并将所有的属性和方法都挂载在这个全局对象上。

比如jQuery,定义了$jQuery两个全局对象。只有在$被其他类库使用了的情况下,为了避免冲突,才应该使用jQuery。

命名空间

即使使用“单全局变量方式”也可能存在着全局污染,所以一般可以使用命名空间来解决。

使用模块

ES6已经可以支持module语法了

“异步模块定义”(AMD)

事件处理

先来看个例子:

1
2
3
4
5
6
7
8
9
10
11

// 这是将应用逻辑和用户交互程序混合在一起的代码
// 不好的写法
function handleClick(event) {
// body...
var popup = document.querySelector('#popup');
popup.style.left = event.clientX + 'px';
popup.style.top = event.clientY + 'px';
popup.className = 'popup';}
// 事件处理程序
addListener(element, 'click', handleClick);

在上面这个例子中,应用逻辑(显示popup)和事件处理程序的代码混在一起了。

规则一:隔离应用逻辑

应用逻辑(application logic)指和应用相关的功能性代码,而不是和用户行为相关的。所谓隔离应用逻辑就是将应用逻辑从所有事件处理程序中抽离出来。

比如下面这个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


//规则一:将应用逻辑从事件处理程序中抽离
var MyApplication = {
handleClick: function(event) {
// body...
this.shopPop(event);
},
shopPopup: function(event) {
// body...
var popup = document.querySelector('#popup');
popup.style.left = event.clientX + 'px';
popup.style.top = event.clientY + 'px';
popup.className = 'popup';
}
};
addListener(element, 'click', function (event) {
// body...
MyApplication.handleClick(event);
})

规则二:不要分发事件对象

对于上面的代码,showPopup()中,只需要event的两个参数:clientX和clientY。所以不需要将整个event对象作为参数传入,只需要传入所需要的参数即可。如下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22


//规则二:不要分发事件对象,传递具体的参数
var MyApplication = {
handleClick: function(event) {
// 在应用处理之前阻止默认事件或者阻止事件冒泡
event.preventDefault();
event.stopPropagation();
// 传入应用逻辑
this.shopPop(event.clientX, event.clientY);
},
shopPopup: function(x, y) {
// body...
var popup = document.querySelector('#popup');
popup.style.left = x + 'px';
popup.style.top = y + 'px';
popup.className = 'popup';
}};
addListener(element, 'click', function (event) {
// body...
MyApplication.handleClick(event);
})

这种思想就是设计模式中所遵循的单一职责原则(SRP),就是一个方法尽量只负责一个功能。将不同的功能细分到不同的方法中。

避免“空比较”

之所以不建议和null做比较是因为任何有值,任何对象都和null不相等。正确的做法是对于不同类型的值应该采用与之相对应的方法。

检测原始值(string, number, boolean, null, undefined)typeof

typeof会返回一个表示值的类型。比如

1
2
3
4
5
6


typeof string; // string
typeof number; //number
typeof boolean; // boolean
typeof undefined; //undefined

typeof运算符的独特之处在于,将其用于一个未声明的变量也不会报错。对于未声明的变量和值为undefined的变量通过typeof都将返回”undefined”

检测引用值用instanceof

检验自定义类型唯一的方法就是instanceof
检测函数

1
2
3
4
5
6
7
8

// 不好的写法

console.log(myFn instanceof Function); // true

// 好的写法

console.log(typeof myFn === 'function');

检测数组

使用ECMAScript5中的方法isArray()判断

1
2
3
4

var arr = [];

console.log(Array.isArray(arr)); // true

但是由于这个方法可能在低版本浏览器并不兼容,所以可以采用下面的方法进行判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


function isArray(value){

if(typeof Array.isArray === 'function'){

return Array.isArray(value);

}

else{

return object.prototype.toString.call(value) === '[object array]';

}

检测属性

判断属性是否存在最好的办法就是用in运算符和hasOwnProperty()in运算符仅仅会简单得判断属性是否存在,而不会去读属性的值。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

//检测属性

var object = {

count: 0,

related: null

}



// 好的写法

if('count' in object){

// do something

}



// 不好的写法 检测假值

if(object['count']){

// 代码不会执行

}



// 或者采用hasOwnProperty()

if(object.hasOwnProperty('count')){

// 代码将执行

}

将配置数据从代码中分离出来

哪些配置数据需要从代码中分离出来:

  • URL

  • 需要展现给用户的字符串

  • 重复的值

  • 设置值,一些配置参数

  • 任何可能发生变更的值

不同的框架对于保存配置文件的方式各不相同,但思想都是一样,抽离出易变项,单独分离成文件,与代码中的数据和应用逻辑隔离。

抛出自定义错误

在JavaScript中抛出错误是一门艺术。搞清楚代码中哪里适合抛出错误,将有助于在代码发生错误时,大大缩短调试时间。

JavaScript中抛出错误的方法

1
2
3
4
5
6
7
8
9
10

// 好的写法

throw new Error('Somethind bad happend')



// 不好的写法

throw "message";

错误类型

  • Error,所有错误的基本类型,所有的错误都继承与它。实际上引擎从来不会抛出该类型的错误;

  • EvalError,通过eval()函数执行代码发生错误时抛出;

  • RangeError,一个数字超过边界时抛出;

  • ReferenceError,期望的对象不存在时抛出,这个错误很常见;

  • SyntaxError,给eval()函数传递的代码中有语法错误时抛出;

  • TypeError,变量不是期望的类型时抛出;

  • URIError,给encodeURI()、encodeURIComponent()、decodeURI()或者decodeURIComponent()等函数传递格式非法的URI字符串时抛出。