前端js规范

背景

JavaScript 是一种客户端脚本语言, Google、阿里等许多开源工程中都有用到它. 这份指南列出了编写 JavaScript 时需要遵守的规则.

现状:
新需求和bug的修改是需要成本,并且这项成本总是不断地增加,特别是对于老代码,更是如此。最好是我们一旦遇到 BUG和新需求,就可以立刻修改它,而这种情况往往在刚写完代码后不久。否则,一旦转移到新的任务上,忘记了这部分代码,就需要重新阅读这些代码:
  • 1. 花时间重新学习和理解相应的问题
  • 2.花时间理解当时用于解决相应问题的代码
  • 3.往往并不是当初写代码的人进行修改代码
  • 4.开发人员通常读代码比写代码更耗时间
  • 5.面对时间紧,要求变现快,我们会在时间压力下写出大量的代码,完成相应的功能,可运行使用。但要想规范化、可维护、高性能,就要重新检查和优化。
因此,减少理解自己以前写的代码的时间,或是减少理解团队中他人写的代码时间,就变得非常的关键。同时,也是影响到开发完成时间和开发者的情绪。
从上可见,易维护的代码意味着代码具有如下特性:
  • 阅读性好
  • 具体一致性
  • 预见性好
  • 看起来如同一个人编写
  • 有文档和注释
团队成员在开始写javascript脚本时,一定要有一个统一的规范,具体如下:

JavaScript 编码风格

缩进

每一行的层级由4个空格组成,避免使用制表符(Tab)进行缩进(在编辑器中,设置后可以使用)。

要求做到代码层次分明、整洁。

1
2
3
4
// 好的写法
if (true) {
    doSomething();
}

行的长度

每行长度不应该超过120个字符。如果一行多于120个字符,应当在一个运算符(逗号,加号等)后换行。下一行应当增加两级缩进(8个字符)。
语句必须以分号作为结束符, 不要忽略分号;

1
2
3
4
5
6
7
8
9
// 好的写法
doSomething(argument1, argument2, argument3, argument4,
        argument5);
// 不好的写法:第二行只有4个空格的缩进
doSomething(argument1, argument2, argument3, argument4,
    argument5);
// 不好的写法:在运算符之前换行
doSomething(argument1, argument2, argument3, argument4
        , argument5);

原始值

字符串应当始终使用双引号(避免使用单引号)且保持一行,避免在字符串中使用斜线另起一行

1
2
3
4
5
6
7
// 好的写法
var name = "Nicholas";
// 不好的写法:单引号
var name = 'Nicholas';
// 不好的写法:字符结束之前换行
var longString = "Here's the story, of a man \
        named Brady.";

数字应当使用十进制整数,科学计数法表示整数,或者十进制浮点小数,小数点前后应当至少保留一位数字。避免使用八进制直接量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 好的写法
var count = 10;
// 好的写法
var price = 10.0;
var price = 10.00;
// 好的写法
var num = 0xA2;
// 好的写法
var num = 1e23;
// 不好的写法:十进制数字以小数点结尾
var price = 10.;
// 不好的写法:十进制数字以小数点开头
var price = .1;
// 不好的写法:八进制(base 8)写法已废弃
var num = 010;

特殊值null除了下述情况应当避免使用。

  • 用来初始化一个变量,这个变量可能被赋值为一个对象。
  • 用来和一个已经初始化的变量比较,这个变量可以也可以不是一个对象。
  • 当函数的参数期望是对象时,被用作参数传入。
  • 当函数的返回值期望是对象时,被用作返回值传出。

例如:

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
// 好的写法
var person = null;
// 好的写法
function getPerson() {
    if (condition) {
        return new Person("Nicholas");
    } else {
        return null;
    }
}
// 好的写法
var person = getPerson();
if (person !== null) {
    doSomething();
}
// 不好的写法:和一个未被初始化的变量比较
var person;
if (person != null) {
    doSomething();
}
// 不好的写法:
function doSomething(arg1, arg2, arg3, arg4) {
    if (arg4 != null) {
        doSomethingElse();
    }
}

避免使用特殊值undefined。判断一个变量是否定义应当使用typeof操作符。

1
2
3
4
5
6
7
8
9
// 好的写法
if (typeof variable == "undefined") {
    // do something
}
 
// 不好的写法:使用了undefined直接量
if (variable == "undefined") {
    // do something
}

运算符间距

二元运算符前后必须使用一个空格来保持表达式的整洁。操作符包括赋值运算符和逻辑运算符。

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
// 好的写法
var found = (values[i] === item);
 
// 好的写法
if (found && (count > 10)) {
    doSomething();
}
 
// 好的写法
for (i = 0; i <= count; i++) {
    process(i);
}
 
// 不好的写法:丢失了空格
var found = (values[i]===item);
 
// 不好的写法:丢失了空格
if (found&&(count>10)) {
    doSomething();
}
 
// 不好的写法:丢失了空格
for (i=0; i<=count; i++) {
    process(i);
}

括号间距

当使用括号时,紧接左括号之后和紧接又括号之前不应该有空格。

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
// 好的写法
var found = (values[i] === item);
 
// 好的写法
if (found && (count > 10)) {
    doSomething();
}
 
// 好的写法
for (i = 0; i <= count; i++) {
    process(i);
}
 
// 不好的写法:左括号之后有额外的空格
var found = ( values[i] === item);
 
// 不好的写法:右括号之前有额外的空格
if (found && (count > 10) ) {
    doSomething();
}
 
// 不好的写法:参数两遍有额外的空格
for (i = 0; i <= count; i++) {
    process( i );
}

对象直接量

对象直接量应当使用如下格式。

  • 起始左花括号应当同表达式保持同一行。
  • 每个属性的名值对应当保持一个缩进,第一个属性应当在左花括号后另起一行。
  • 每个属性的名值对应当使用不含引号的属性名,其后紧跟一个冒号(之前不含空格),而后是值。
  • 倘若属性值是函数类型,函数体应当在属性名之下另起一行,而且其前后均应保留一个空行。
  • 一组相关的属性前后可以插入空格以提升代码的可读性。
  • 结束的右花括号应当独占一行。

例如

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
// 好的写法
var object = {
 
    key1: value1,
    key2: value2,
 
    func: function() {
        // do something
    },
 
    key3: value3
};
 
// 不好的写法:不恰当的缩进
var object = {
                key1: value1,
                key2: value2
            };
 
// 不好的写法:函数体前后缺少空行
var object = {
 
    key1: value1,
    key2: value2,
    func: function() {
        // do something
    },
    key3: value3
};

当对象字面量作为函数参数时,如果值是变量,起始花括号应当同函数名在同一行。所有其余先前列出的规则同样适用。

1
2
3
4
5
6
7
8
// 好的写法
doSomething({
    key1: value1,
    key2: value2
});
 
// 不好的写法:所有的代码在一行上
doSomething({ key1: value1, key2: value2 });

注释

频繁地使用注释有助于他人理解你的代码。如下情况应当使用注释。

  • 代码晦涩难懂。
  • 可能被误认为错误的代码。
  • 必要但不明显的针对特定浏览器的代码。
  • 对于对象、方法或者属性,生成文档是有必要的(使用恰当的文档注释)。

单行注释

单行注释应当用来说明一行代码或者一组相关的代码。单行注释可能有三种使用方式。

  • 独占一行的注释,用来解释下一行代码。
  • 在代码行的尾部的注释,用来解释它之前的代码。
  • 多行:用来注释掉一个代码块。

这里有一些实例代码。

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
// 好的写法
if (condition) {
 
    // 如果代码执行到这里,则表明通过了所有安全性检查
    allowed();
}
 
// 不好的写法:注释之前没有空行
if (condition) {
    // 如果代码执行到这里,则表明通过了所有安全性检查
    allowed();
}
 
// 不好的写法:错误的缩进
if (condition) {
// 如果代码执行到这里,则表明通过了所有安全性检查
    allowed();
}
 
// 不好的写法:这里应当用多行注释
// 接下来的这段代码非常难,那么让我详细说明一下
// 这段代码的作用首先判断条件是否为真
// 只有为真时才会执行。这里的条件是通过
// 多个函数计算出来的,在整个会话生命周期内
// 这个值是可以被修改的
if (condition) {
 
    // 如果代码执行到这里,则表明通过了所有安全性检查
    allowed();
}

对于代码行尾的单行注释的情况,应确保代码结尾同注释之间至少一个缩进。

1
2
3
4
5
// 好的写法
var result = something + somethingElse; // somethingElse will never be null
 
// 不好的写法:代码和注释间没有足够的空格
var result = something + somethingElse;// somethingElse will never be null

注释一个代码块时在连续多行使用单行注释是唯一可以接受的情况。多行注释不应该在这种情况下使用。

1
2
3
4
5
// 好的写法
// if (condition) {
//     doSomething();
//     thenDoSomethingElse();
// }

多行注释

  • 多行注释应当在代码需要更多文字去解释的时候使用。每个多行注释都至少有如下三行。
  • 首行仅仅包括/*注释开始。该行不应当有其他文字。
  • 接下来的行以*开头并保持左对齐。在这些行可以有文字描述。
  • 最后一行以*/开头并同先前行保持对齐。也不应当有其他文字。

多行注释的首行应当保持同它描述代码的相同层次的缩进。后续的每行应当有同样层次的缩进并附加一个空格(为了适当保持*字符的对齐)。每一个多行代码之前应当预留一个空行。

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
41
// 好的写法
if (condition) {
 
    /*
     * 如果代码执行到这里
     * 说明通过了所有的安全性检测
     */
    allowed();
}
 
// 不好的写法:注释之前无空行
if (condition) {
    /*
     * 如果代码执行到这里
     * 说明通过了所有的安全性检测
     */
    allowed();
}
 
// 不好的写法:星号后没有空格
if (condition) {
 
    /*
     *如果代码执行到这里
     *说明通过了所有的安全性检测
     */
    allowed();
}
 
// 不好的写法:错误的缩进
if (condition) {
 
/*
 * 如果代码执行到这里
 * 说明通过了所有的安全性检测
 */
    allowed();
}
 
// 不好的写法:代码尾部注释不要用行注释格式
var result = something + somethingElse; /*somethingElse will never be null*/

注释声明

注释有时候也可以用来给一段代码声明额外的信息。这些声明的格式以单个单词打头并紧跟一个冒号。可使用的声明如下。

TODO

  • 说明代码还未完成。应当包含下一步要做的事情。

HACK

  • 表明代码实现走了一个捷径。应当包含为何使用hack的原因。这也可能表明该问题可能会有更好的解决方法。

XXX

  • 说明代码是有问题的并应当尽快修复。

FIXME

  • 说明代码是有问题的并应当快修复。重要性略次于XXX。

REVIEW

  • 说明代码可能的改动都需要评审。

这些声明可能在一行或多行注释中使用,并且应当遵循同一般类型的格式规则。
例如:

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
// 好的写法
// TODO: 我希望找到一种更快的方式
doSomething();
 
// 好的写法
/*
 * HACK: 不得不针对 IE 做的特殊处理,我计划后续有时间时
 * 重写这部分。这些代码可能需要在v1.2版本之前替换掉。
 */
if (document.all) {
    doSomething();
}
 
// 好的写法
// REVIEW: 有更好的方法吗?
if (document.all) {
    doSomething();
}
 
// 不好的写法:注释声明空格不正确
// TODO:    我希望找到一种更快的方式
doSomething();
 
// 不好的写法:代码和注释应当保持同样的缩进
    // REVIEW: 有更好的方法吗?
if (document.all) {
    doSomething();

变量声明

所有的变量在使用前都应当事先定义。变量定义应当放在函数开头,使用一个var表达式每行一个变量。除了首行。所有行都应当多一层缩进以使变量名能够垂直方向对齐。变量定义时应当初始化,并且赋值操作符应当保持一致的缩进。初始化的变量应当在未初始化变量之前。

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
// 好的写法
var count = 10,
    name = "Nicholase",
    found = false,
    empty;
 
// 不好的写法:不恰当的初始化赋值
var count = 10,
    name = "Nicholase",
    found= false,
    empty;
 
// 不好的写法:错误的缩进
var count = 10,
name = "Nicholase",
found = false,
empty;
 
// 不好的写法:多个定义写在一行
var count = 10, name = "Nicholase",
    found = false, empty;
 
// 不好的写法:为初始化的变量放在最前面
var empty,
    count = 10,
    name = "Nicholase",
    found = false;
 
// 不好的写法:多个var表达式
var count = 10,
    name = "Nicholase";
 
var found = false,
    empty;

函数声明

函数应当在使用前提前定义。一个不是作为方法的函数(也就是说没有作为一个对象的属性)应当使用函数定义的格式(不是函数表达式和Function构造器格式)。函数名和开始圆括号之间不应当有空格。结束的圆括号和右边的花括号之间应该留一个空格。右侧的花括号应当同function关键字保持同一行。开始和结束括号之间不应该有空格。参数名之间应当在逗号之后保留一个空格。函数体应当保持一级缩进。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 好的写法
function doSomething(arg1, arg2) {
    return arg1 + arg2;
}
 
// 不好的写法:第一行不恰当的空格
function doSomething (arg1, arg2) {
    return arg1 + arg2;
}
 
// 不好的写法:函数表达式
var doSomething = function(arg1, arg2) {
    return arg1 + arg2;
}
 
// 不好的写法:左侧的花括号位置不对
function doSomething(arg1, arg2)
{
    return arg1 + arg2;
}
 
// 错误的写法:使用了Function构造器
var doSomething = new Function("arg1", "arg2", "return arg1 + arg2;");

其他函数内部的函数应当在var语句之后立即定义

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
// 好的写法
function outer() {
    var count = 10,
        name = "Nicholase",
        found = false,
        empty;
 
    function inner() {
        // 代码
    }
 
    // 调用 inner() 的代码
}
 
// 不好的写法:inner函数的定义先于变量
function outer() {
 
    function inner() {
        // 代码
    }
 
    var count = 10,
        name = "Nicholase",
        found = false,
        empty;
 
    // 调用 inner() 的代码
}

匿名函数可能作为方法赋值给对象,或者作为其他函数的参数。function关键字同开始括号之间不应有空格。

1
2
3
4
5
6
7
8
9
// 好的写法
object.method = function() {
    // 代码
}
 
// 不好的写法:不正确的空格
object.method = function () {
    // 代码
}

立即调用的函数应当在函数调用的外层用圆括号包裹。

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
// 好的写法
var value = (function() {
 
    // 函数体
 
    return {
        message: "Hi"
    };
}());
 
// 不好的写法:函数调用外层没有用圆括号包裹
var value = function() {
 
    // 函数体
 
    return {
        message: "Hi"
    };
}();
 
// 不好的写法:圆括号位置不当
var value = (function() {
 
    // 函数体
单词
    return {
        message: "Hi"
    };
})();

命名

变量和函数在命名时应当小心。命名应仅限于数字、字母、字符,某些情况下也可以使用下划线。最好不要在任何命名中使用美元符号($)或者反斜杠(\)。

变量命名应当采用驼峰命名格式,首字母小写,每个单词首字母大写。变量名的第一个单词应当是一个名词(而非动词)以避免同函数混淆。不要在变量命名中使用下划线。

1
2
3
4
5
6
7
8
9
10
11
// 好的写法
var accountNumber = "8401-1";
 
// 不好的写法:大写字母开头
var AccountNumber = "8401-1";
 
// 不好的写法:动词开头
var getAccountNumber = "8401-1";
 
// 不好的写法:使用下划线
var account_number = "8401-1";

函数命名也应当采用驼峰命名的格式。函数名的第一个单词应当是动词(而非名词)来避免同变量混淆。函数名最好不要使用下滑线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 好的写法
function doSomething() {
    // 代码
}
 
// 不好的写法:大写字母开头
function DoSomething() {
    // 代码
}
 
// 不好的写法:名词开头
function car() {
    // 代码
}
 
// 不好的写法:使用了下划线
function do_something() {
    // 代码
}

构造函数——通过new运算符创建新对象的函数——也应当以驼峰格式命名并且首字母大写。构造函数名称应当以非动词开头,因为new代表着创建一个对象实例的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 好的写法
function MyObject() {
    // 代码
}
 
// 不好的写法:小写字母开头
function myObject() {
    // 代码
}
 
// 不好的写法:使用下划线
function My_Object() {
    // 代码
}
 
// 不好的写法:动词开头
function getMyObject() {
    // 代码
}

常量(值不会被改变的变量)的命名应当是所有字母大写,不同单词之间用单个下划线隔开。

1
2
3
4
5
6
7
8
// 好的写法
var TOTAL_COUNT = 10;
 
// 不好的写法:驼峰形式
var totalCount = 10;
 
// 不好的写法:混合形式
var total_COUNT = 10;

对象的属性同变量的命名规则相同。对象的方法同函数的命名规则相同。如果属性或者方法是私有的,应当在之前加一个下划线。

1
2
3
4
5
6
7
8
// 好的写法
var object = {
    _count: 10,
 
    _getCount: function() {
        return this._count;
    }
};

严格模式

严格模式应当仅限于在函数内部使用,千万不要在全局使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 好的写法
function doSomething() {
    "use strict";
 
    // 代码
}
 
// 不好的写法:全局使用严格模式
"use strict";
 
function doSomething() {
    // 代码
}

如果期望在多个函数中使用严格模式而不需要多次声明”use strict”,可以使用立即被调用函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 好的写法
(function() {
    "use strict";
 
    function doSomething() {
        // 代码
    }
 
    function doSomethingElse() {
        // 代码
    }
 
}());

赋值

当给变量赋值时,如果右侧是含有比较语句的表达式,需要用圆括号包裹。

1
2
3
4
5
// 好的写法
var flag = (i < count);
 
// 不好的写法:遗漏圆括号
var flag = i < count;

等号运算符

使用===(严格相等)和!==(严格不相等)代替==(相等)和!=(不等)来避免弱类型转换错误。

1
2
3
4
5
// 好的写法
var same = (a === b);
 
// 不好的写法:使用==
var same = (a == b);

三元操作符

三元操作符应当仅仅用在条件赋值语句中,而不要作为if语句的替代品。

1
2
3
4
5
// 好的写法
var value = condition ? value1 : value2;
 
// 不好的写法:没有赋值,应当使用if语句
condition ? doSomething : doSomethingElse();

语句

简单语句

每一行最多只包含一条语句。所有简单的语句都应该以分号(;)结束。

1
2
3
4
5
6
// 好的写法
count++;
a = b;
 
// 不好的写法:多个表达式写在一行
count++; a = b;

返回语句

当返回语句返回一个值的时候不应当使用圆括号包裹,除非在某些情况下这么做可以让返回值更容易理解。例如:

1
2
3
4
5
return;
 
return collection.size();
 
return (size > 0 ? size : defaultSize);

复合语句

复合语句是大括号括起来的语句列表。

  • 括起来的语句应当较复合语句多缩进一个层级。
  • 开始的大括号应当在复合语句所在行的末尾;结尾的大括号应当独自占一行且同复合语句的开始保持同样的缩进。
  • 当语句是控制结构的一部分时,诸如if或者for语句,所有语句都需要用大括号括起来,也包括单个语句。这个约定使得我们更方便地添加语句而不用担心忘记加括号而引起bug。
  • 想if一样的语句开始的关键词,其后应该紧跟一个空格,起始大括号应当在空格之后。

if 语句
if语句应当是下面的格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (condition) {
&nbsp;&nbsp;&nbsp;&nbsp;// 语句
}
 
if (condition) {
&nbsp;&nbsp;&nbsp;&nbsp;// 语句
} else {
&nbsp;&nbsp;&nbsp;&nbsp;// 语句
}
 
if (condition) {
&nbsp;&nbsp;&nbsp;&nbsp;// 语句
} else if (condition) {
&nbsp;&nbsp;&nbsp;&nbsp;// 语句
} else {
&nbsp;&nbsp;&nbsp;&nbsp;// 语句
}

绝不允许在if语句中省略花括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 好的写法
if (condition) {
    doSomething();
}
 
// 不好的写法:不恰当的空格
if(condition) {
    doSomething();
}
 
// 不好的写法:遗漏花括号
if (condition)
    doSomething();
 
// 不好的写法:所有的代码写在一行
if (condition) { doSomething(); }
 
// 不好的写法:所有的代码写在一行且没有花括号
if (condition) doSomething();

for 语句
for语句应当是下面的格式。

1
2
3
4
5
6
for (initialization; condition; update) {
    // 语句
}
for (variable in object) {
    // 语句
}

for语句的初始化部分不应当有变量声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 好的写法
var i,
    len;
for (i = 0, len = 10; i < len; i++) {
    // 语句
}
 
// 不好的写法:初始化时候声明了变量
for (var i = 0, len = 10; i < len; i++) {
    // 语句
}
 
// 不好的写法:初始化时候声明了变量
for (var pop in object) {
    // 语句
}

当使用for-in语句时,记得使用hasOwnProperty()进行双重检查来过滤出对象的成员。
while 语句
while语句应当是下面的格式。

1
2
3
while (condition) {
    // 语句
}

do 语句
do语句应当是下面的格式。

1
2
3
do  {
    // 语句
} while (condition)

switch 语句
switch语句应当是下面的格式。

1
2
3
4
5
6
7
8
9
10
switch (expression) {
    case expression:
        // 语句
        break;
 
    ...
 
    default:
        // 语句
}

switch下的每一个case都应当保持一个缩进。除第一个之外包括default在内的每一个case对应当在之前保持一个空行。

每一组语句(除了default)都应当以break、return、throw结尾,或者用一行注释表示跳过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 好的写法
switch (value) {
    case 1:
        // falls through
 
    case 2:
        doSomething();
        break;
 
    case 3:
        return true;
 
    default:
        throw new Error("This shouldn't happen.");
}

如果一个switch语句不包含default情况,应当用一行注释代替。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 好的写法
switch (value) {
    case 1:
        // falls through
 
    case 2:
        doSomething();
        break;
 
    case 3:
        return true;
 
    // 没有default
}

try 语句
try语句应当是下面的格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
    // 语句
} catch (variable) {
    // 语句
}
 
try {
    // 语句
} catch (variable) {
    // 语句
} finally {
    // 语句
}

留白

在逻辑相关的代码块之间添加空行可以提高代码的可读性。

两行空行仅限在如下情况中使用。

  • 在不同的源代码文件之间。
  • 在类和接口定义之间。

单行空行仅限在如下情况中使用。

  • 方法之间。
  • 方法中局部变量和第一行语句之间。
  • 方法中局部变量和第一行语句之间。
  • 多行或者单行注释之前。
  • 方法中逻辑代码块之间以提升代码的可读性。

空格应当在如下情况中使用。

  • 关键词后跟括号的情况应当用空格隔开。
  • 参数列表中逗号之后应当保留一个空格。
  • 所有的除了点(.)之外的二元运算符,其操作数都应当用空格隔开。单目运算符的操作数之间不应该用空白隔开,诸如一元减号,递增(++),递减(–)。
  • for语句中的表达式之间应当用空格隔开。

需要避免的

  • 切勿使用像String一类的原始包装类型创建新的对象。
  • 避免使用eval()。
  • 避免使用with语句。该语句在严格模式中不复存在,可能在未来的ECMAScript标准中也将去除。

 

JavaScript 语言规范

变量

声明变量必须加上 var 关键字. Decision: 当你没有写 var, 变量就会暴露在全局上下文中, 这样很可能会和现有变量冲突. 另外, 如果没有加上, 很难明确该变量的作用域是什么, 变量也很可能像在局部作用域中, 很轻易地泄漏到 Document 或者 Window 中, 所以务必用var 去声明变量.

常量

常量的形式如: NAMES_LIKE_THIS, 即使用大写字符, 并用下划线分隔. 你也可用 @const 标记来指明它是一个常量. 但请永远不要使用const关键词.

Decision: 对于基本类型的常量, 只需转换命名.

1
2
3
4
5
/**
 * The number of seconds in a minute.
 * @type {number}
 */
goog.example.SECONDS_IN_A_MINUTE = 60;

对于非基本类型, 使用 @const 标记.

1
2
3
4
5
6
7
8
9
10
/**
 * The number of seconds in each of the given units.
 * @type {Object.<number>}
 * @const
 */
goog.example.SECONDS_TABLE = {
  minute: 60,
  hour: 60 * 60,
  day: 60 * 60 * 24
}

这标记告诉编译器它是常量.

至于关键词 const, 因为 IE 不能识别, 所以不要使用.

分号

总是使用分号.

Decision:

如果仅依靠语句间的隐式分隔, 有时会很麻烦. 你自己更能清楚哪里是语句的起止.

而且有些情况下, 漏掉分号会很危险:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1.
MyClass.prototype.myMethod = function() {
  return 42;
// No semicolon here.
 
(function() {
  // Some initialization code wrapped in a function to create a scope for locals.
})();
 
var x = {
  'i': 1,
  'j': 2
// No semicolon here.
 
// 2.  Trying to do one thing on Internet Explorer and another on Firefox.
// I know you'd never write code like this, but throw me a bone.
[normalVersion, ffVersion][isIE]();
 
var THINGS_TO_EAT = [apples, oysters, sprayOnCheese]  // No semicolon here.
 
// 3. conditional execution a la bash
-1 == resultOfOperation() || die();

这段代码会发生些什么诡异事呢?

  1. 报 JavaScript 错误 – 例子1上的语句会解释成, 一个函数带一匿名函数作为参数而被调用, 返回42后, 又一次被”调用”, 这就导致了错误.
  2. 例子2中, 你很可能会在运行时遇到 ‘no such property in undefined’ 错误, 原因是代码试图这样 x[ffVersion][isIE]() 执行.
  3. 当 resultOfOperation() 返回非 NaN 时, 就会调用die, 其结果也会赋给 THINGS_TO_EAT.

为什么?

JavaScript 的语句以分号作为结束符, 除非可以非常准确推断某结束位置才会省略分号. 上面的几个例子产出错误, 均是在语句中声明了函数/对象/数组直接量, 但 闭括号(‘}’或’]’)并不足以表示该语句的结束. 在 JavaScript 中, 只有当语句后的下一个符号是后缀或括号运算符时, 才会认为该语句的结束.

遗漏分号有时会出现很奇怪的结果, 所以确保语句以分号结束.

嵌套函数

可以使用

Decision:

嵌套函数很有用, 比如,减少重复代码, 隐藏帮助函数, 等. 没什么其他需要注意的地方, 随意使用.

块内函数声明

不要在块内声明一个函数

不要写成:

1
2
3
if (x) {
  function foo() {}
}

虽然很多 JS 引擎都支持块内声明函数, 但它不属于 ECMAScript 规范 (见 ECMA-262, 第13和14条). 各个浏览器糟糕的实现相互不兼容, 有些也与未来 ECMAScript 草案相违背. ECMAScript 只允许在脚本的根语句或函数中声明函数. 如果确实需要在块中定义函数, 建议使用函数表达式来初始化变量:

1
2
3
if (x) {
  var foo = function() {}
}

异常

可以.

Decision:

你在写一个比较复杂的应用时, 不可能完全避免不会发生任何异常. 大胆去用吧.

自定义异常

可以

Decision:

有时发生异常了, 但返回的错误信息比较奇怪, 也不易读. 虽然可以将含错误信息的引用对象或者可能产生错误的完整对象传递过来, 但这样做都不是很好, 最好还是自定义异常类, 其实这些基本上都是最原始的异常处理技巧. 所以在适当的时候使用自定义异常.

标准特性

总是优于非标准特性.

Decision:

最大化可移植性和兼容性, 尽量使用标准方法而不是用非标准方法, (比如, 优先用string.charAt(3) 而不用 string[3] , 通过 DOM 原生函数访问元素, 而不是使用应用封装好的快速接口.

封装基本类型

不要

Decision:

没有任何理由去封装基本类型, 另外还存在一些风险:

1
2
3
4
var x = new Boolean(false);
if (x) {
  alert('hi');  // Shows 'hi'.
}

除非明确用于类型转换, 其他情况请千万不要这样做!

1
2
3
4
5
6
var x = Boolean(0);
if (x) {
  alert('hi');  // This will never be alerted.
}
typeof Boolean(0) == 'boolean';
typeof new Boolean(0) == 'object';

有时用作 numberstring 或 boolean时, 类型的转换会非常实用.

多级原型结构

不是首选

Decision:

多级原型结构是指 JavaScript 中的继承关系. 当你自定义一个D类, 且把B类作为其原型, 那么这就获得了一个多级原型结构. 这些原型结构会变得越来越复杂!

使用 the Closure 库 中的 goog.inherits() 或其他类似的用于继承的函数, 会是更好的选择.

1
2
3
4
5
6
7
8
function D() {
  goog.base(this)
}
goog.inherits(D, B);
 
D.prototype.method = function() {
  ...
};

方法定义

Foo.prototype.bar = function() { ... };

Decision:

有很多方法可以给构造器添加方法或成员, 我们更倾向于使用如下的形式:

1
2
3
Foo.prototype.bar = function() {
  /* ... */
};

闭包

可以, 但小心使用.

Decision:

闭包也许是 JS 中最有用的特性了. 有一份比较好的介绍闭包原理的文档.

有一点需要牢记, 闭包保留了一个指向它封闭作用域的指针, 所以, 在给 DOM 元素附加闭包时, 很可能会产生循环引用, 进一步导致内存泄漏. 比如下面的代码:

1
2
3
function foo(element, a, b) {
  element.onclick = function() { /* uses a and b */ };
}

这里, 即使没有使用 element, 闭包也保留了 elementa 和 b 的引用, . 由于 element 也保留了对闭包的引用, 这就产生了循环引用, 这就不能被 GC 回收. 这种情况下, 可将代码重构为:

1
2
3
4
5
6
7
function foo(element, a, b) {
  element.onclick = bar(a, b);
}
 
function bar(a, b) {
  return function() { /* uses a and b */ }
}

eval()

只用于解析序列化串 (如: 解析 RPC 响应)

Decision:

eval() 会让程序执行的比较混乱, 当 eval() 里面包含用户输入的话就更加危险. 可以用其他更佳的, 更清晰, 更安全的方式写你的代码, 所以一般情况下请不要使用 eval(). 当碰到一些需要解析序列化串的情况下(如, 计算 RPC 响应), 使用 eval 很容易实现.

解析序列化串是指将字节流转换成内存中的数据结构. 比如, 你可能会将一个对象输出成文件形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
users = [
  {
    name: 'Eric',
    id: 37824,
    email: 'jellyvore@myway.com'
  },
  {
    name: 'xtof',
    id: 31337,
    email: 'b4d455h4x0r@google.com'
  },
  ...
];

很简单地调用 eval 后, 把表示成文件的数据读取回内存中.

类似的, eval() 对 RPC 响应值进行解码. 例如, 你在使用 XMLHttpRequest 发出一个 RPC 请求后, 通过 eval () 将服务端的响应文本转成 JavaScript 对象:

1
2
3
4
5
6
7
8
9
10
11
var userOnline = false;
var user = 'nusrat';
var xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', 'http://chat.google.com/isUserOnline?user=' + user, false);
xmlhttp.send('');
// Server returns:
// userOnline = true;
if (xmlhttp.status == 200) {
  eval(xmlhttp.responseText);
}
// userOnline is now true.

with() {}

不要使用

Decision:

使用 with 让你的代码在语义上变得不清晰. 因为 with 的对象, 可能会与局部变量产生冲突, 从而改变你程序原本的用义. 下面的代码是干嘛的?

1
2
3
4
with (foo) {
  var x = 3;
  return x;
}

答案: 任何事. 局部变量 x 可能被 foo 的属性覆盖, 当它定义一个 setter 时, 在赋值 3 后会执行很多其他代码. 所以不要使用 with 语句.

this

仅在对象构造器, 方法, 闭包中使用.

Decision:

this 的语义很特别. 有时它引用一个全局对象(大多数情况下), 调用者的作用域(使用 eval时), DOM 树中的节点(添加事件处理函数时), 新创建的对象(使用一个构造器), 或者其他对象(如果函数被 call() 或 apply()).

使用时很容易出错, 所以只有在下面两个情况时才能使用:

  • 在构造器中
  • 对象的方法(包括创建的闭包)中

for-in 循环

只用于 object/map/hash 的遍历

Decision:

对 Array 用 for-in 循环有时会出错. 因为它并不是从 0 到 length - 1 进行遍历, 而是所有出现在对象及其原型链的键值. 下面就是一些失败的使用案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function printArray(arr) {
  for (var key in arr) {
    print(arr[key]);
  }
}
 
printArray([0,1,2,3]);  // This works.
 
var a = new Array(10);
printArray(a);  // This is wrong.
 
a = document.getElementsByTagName('*');
printArray(a);  // This is wrong.
 
a = [0,1,2,3];
a.buhu = 'wine';
printArray(a);  // This is wrong again.
 
a = new Array;
a[3] = 3;
printArray(a);  // This is wrong again.

而遍历数组通常用最普通的 for 循环.

1
2
3
4
5
6
function printArray(arr) {
  var l = arr.length;
  for (var i = 0; i < l; i++) {
    print(arr[i]);
  }
}

关联数组

永远不要使用 Array 作为 map/hash/associative 数组.

Decision:

数组中不允许使用非整型作为索引值, 所以也就不允许用关联数组. 而取代它使用 Object 来表示 map/hash 对象. Array 仅仅是扩展自Object (类似于其他 JS 中的对象, 就像 DateRegExp 和 String)一样来使用.

多行字符串

不要使用

Decision:

不要这样写长字符串:

1
2
3
4
5
6
var myString = 'A rather long string of English text, an error message \
                actually that just keeps going and going -- an error \
                message to make the Energizer bunny blush (right through \
                those Schwarzenegger shades)! Where was I? Oh yes, \
                you\'ve got an error and all the extraneous whitespace is \
                just gravy.  Have a nice day.';

在编译时, 不能忽略行起始位置的空白字符; “\” 后的空白字符会产生奇怪的错误; 虽然大多数脚本引擎支持这种写法, 但它不是 ECMAScript 的标准规范.

Array 和 Object 直接量

使用

Decision:

使用 Array 和 Object 语法, 而不使用 Array 和 Object 构造器.

使用 Array 构造器很容易因为传参不恰当导致错误.

/*不推荐*/

1
2
3
4
5
6
7
8
9
10
11
12
13
// Length is 3.
var a1 = new Array(x1, x2, x3);
 
// Length is 2.
var a2 = new Array(x1, x2);
 
// If x1 is a number and it is a natural number the length will be x1.
// If x1 is a number but not a natural number this will throw an exception.
// Otherwise the array will have one element with x1 as its value.
var a3 = new Array(x1);
 
// Length is 0.
var a4 = new Array();

如果传入一个参数而不是2个参数, 数组的长度很有可能就不是你期望的数值了.

为了避免这些歧义, 我们应该使用更易读的直接量来声明.

/*推荐*/

1
2
3
4
var a = [x1, x2, x3];
var a2 = [x1, x2];
var a3 = [x1];
var a4 = [];

虽然 Object 构造器没有上述类似的问题, 但鉴于可读性和一致性考虑, 最好还是在字面上更清晰地指明.

/* 不推荐*/

1
2
3
4
5
6
7
var o = new Object();
 
var o2 = new Object();
o2.a = 0;
o2.b = 1;
o2.c = 2;
o2['strange key'] = 3;

/*推荐*/

1
2
3
4
5
6
7
8
var o = {};
 
var o2 = {
  a: 0,
  b: 1,
  c: 2,
  'strange key': 3
};

修改内置对象的原型

不要

Decision:

千万不要修改内置对象, 如 Object.prototype 和 Array.prototype 的原型. 而修改内置对象, 如 Function.prototype 的原型, 虽然少危险些, 但仍会导致调试时的诡异现象. 所以也要避免修改其原型.

IE下的条件注释

不要使用

Decision:

不要这样子写:

1
2
3
var f = function () {
    /*@cc_on if (@_jscript) { return 2* @*/  3; /*@ } @*/
};

条件注释妨碍自动化工具的执行, 因为在运行时, 它们会改变 JavaScript 语法树.

posted @ 2015-06-10 17:18  szchenrong  阅读(219)  评论(0)    收藏  举报