前言 总所周知,javascript
是一种语法极其灵活的语言。变量随时用随时可以声明;语句结束符可以不要;字符串和数字也可以相加;参数多一个少一个也不会报错。 没错,当你从 C/C++
和 Java
严格的语法规定之下,转向 JavaScript
语言,会觉得自由了很多,轻松了很多。
语法松散是 JavaScript
重要的特征。它灵活易懂,给开发人员带来了很多方便,但如果编写过程中不注意,代码的调试成本和维护成本则会无形地增加。JavaScript
编码会随应被直接发送到客户端的浏览器,代码规范不只是代码质量的保证,也影响到产品的长期信誉。
本文档的目标是使 JavaScript
代码风格保持一致,良好的编程风格有助于写出质量更高、错误更少、更易于维护的程序。
JavaScript 语言规范 1
2
变量 常量 保留字 数组 字符串 函数 块内函数声明 闭包 Array和Object直接量
对象原型 True与False 类型分配&强制转换 浮点数精度 命名规范
JavaScript 编码风格 1
2
文件编码 分号 逗号 空格 大括号 单引号、双引号 空行 二元和三元操作符 语句块
注释 全局变量 全等
JavaScript 语言规范 变量
声明变量必须加上 var
关键字
当你没有写 var, 变量就会暴露在全局上下文中, 这样很可能会和现有变量冲突. 另外, 如果没有加上, 很难明确该变量的作用域是什么,
变量也很可能像在局部作用域中, 很轻易地泄漏到 Document 或者 Window 中, 所以务必用 var 去声明变量.
常量
常量使用大写字符并用下划线分隔,如:PAGE_CONFIG
保留字
1
2
3
4
5
6
7
8
9
10
11
var superman = {
defaults : { clark : 'kent' },
hidden : true
};
var superman = {
default : { clark : 'kent' },
private : true
};
数组
1
2
3
4
5
6
7
var someStack = [];
someStack.push('abracadabra' );
someStack[someStack.length] = 'abracadabra' ;
1
2
3
4
5
6
7
8
9
10
11
var len = items.length;
var itemsCopy = [];
var i;
itemsCopy = items.slice();
for (i = 0 ; i < len; i++) {
itemsCopy[i] = items[i];
}
1
2
3
4
function trigger ( ) {
var args = Array .prototype.slice.call(arguments );
...
}
数组对象可能存在数字以外的属性, 这种情况下 for in 不会得到正确结果.
1
2
3
4
5
6
7
8
9
10
11
12
var arr = ['a' , 'b' , 'c' ];
arr.other = 'other things' ;
for (var i = 0 , len = arr.length; i < len; i++) {
console .log(i);
}
for (i in arr) {
console .log(i);
}
清空数组使用 .length = 0
字符串
超过80个字符的字符串应该使用字符串连接符进行跨行(对长字符串过度使用连接符将会影响性能)
1
2
3
4
5
6
7
8
9
10
11
12
13
var errorMessage = 'This is a super long error that was thrown because ' +
'of Batman. When you stop to think about how Batman had anything to do ' +
'with this, you would get nowhere fast.' ;
var errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.' ;
var errorMessage = 'This is a super long error that was thrown because \
of Batman. When you stop to think about how Batman had anything to do \
with this, you would get nowhere \
fast.';
通常写法:
1
2
3
4
5
6
7
8
9
10
11
function listHtml (items ) {
var html = '<div class="foo">' ;
for (var i = 0 ; i < items.length; ++i) {
if (i > 0 ) {
html += ', ' ;
}
html += itemHtml(items[i]);
}
html += '</div>' ;
return html;
}
但这样在 IE 下非常慢, 可以用下面的方式
1
2
3
4
5
6
7
function listHtml (items ) {
var html = [];
for (var i = 0 ; i < items.length; ++i) {
html[i] = itemHtml(items[i]);
}
return '<div class="foo">' + html.join(', ' ) + '</div>' ;
}
也可以是用数组作为字符串构造器, 然后通过 myArray.join(‘’) 转换成字符串. 不过由于赋值操作快于数组的 push(), 所以尽量使用赋值操作.
函数
不要在非函数块中(if, while, etc)声明函数,尽管浏览器允许你分配函数给一个变量,但坏消息是,不同的浏览器用不同的方式解析它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var test;
if (currentUser) {
test = function test ( ) {
console .log('Yup.' );
};
}
if (currentUser) {
function test ( ) {
console .log('Nope.' );
}
}
不要命名一个参数为arguments,否则它将优先于传递给每个函数作用域中的arguments对象
1
2
3
4
5
6
7
8
9
function yup (name, options, args ) {
}
function nope (name, options, arguments ) {
}
在作用域顶端对变量赋值,这有助于避免变量声明问题和与声明提升相关的问题
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
42
43
44
45
46
47
48
49
50
51
52
53
54
function ( ) {
var name = getName();
test();
console .log('doing stuff..' );
if (name === 'test' ) {
return false ;
}
return name;
}
function ( ) {
test();
console .log('doing stuff..' );
var name = getName();
if (name === 'test' ) {
return false ;
}
return name;
}
function ( ) {
if (!arguments .length) {
return false ;
}
var name = getName();
return true ;
}
function ( ) {
var name = getName();
if (!arguments .length) {
return false ;
}
return true ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function example ( ) {
superPower();
function superPower ( ) {
console .log('Flying' );
}
}
function example ( ) {
superPower();
var superPower = function ( ) {
console .log('Flying' );
}
}
块内函数声明
不要在块内声明函数
1
2
3
4
5
6
7
8
9
if (x) {
var foo = function ( ) {}
}
if (x) {
function foo ( ) {}
}
块内声明函数, 但它不属于 ECMAScript 规范, 各个浏览器糟糕的实现相互不兼容, 有些也与未来 ECMAScript 草案相违背 ECMAScript 只允许在脚本的根语句或函数中声明函数. 如果确实需要在块中定义函数, 建议使用函数表达式来初始化变量:
闭包
闭包保留了一个指向它封闭作用域的指针, 所以, 在给 DOM 元素附加闭包时, 很可能会产生循环引用, 进一步导致内存泄漏. 比如下面的代码:
1
2
3
function foo (element, a, b ) {
element.onclick = function ( ) { };
}
这里, 即使没有使用 element, 闭包也保留了 element, a 和 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 ( ) { }
}
Array 和 Object 直接量
使用 Array
和 Object
语法, 而不使用 Array
和 Object
构造器.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = [1 , 2 , 3 ];
var o2 = {
a : 0 ,
b : 1 ,
c : 2 ,
'strange key' : 3
};
var a1 = new Array (1 , 2 , 3 );
var o2 = new Object ();
o2.a = 0 ;
o2.b = 1 ;
o2.c = 2 ;
o2['strange key' ] = 3 ;
对象原型
不要 修改内置对象的原型,如 Object.prototype 和 Array.prototype 的原型,给添加内置原型方法很容易和其它库冲突或者可能与将来ES升级不兼容。
True 与 False 下面的布尔表达式都返回 false
:
null
undefined
''
空字符串
0
数字0
但小心下面的, 可都返回 true
:
如果你想判断是一个变量是否为null/''/0/false
1
2
3
4
5
if (x){ ... }
if (x != null ){ ... }
还有很多需要注意的地方 以下都为true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Boolean ('0' ) == true
'0' != true
0 != null
0 == []
0 == false
Boolean (null ) == false
null != true
null != false
Boolean (undefined ) == false
undefined != true
undefined != false
Boolean ([]) == true
[] != true
[] == false
Boolean ({}) == true
{} != true
{} != false
遍历 Node List Node lists 是通过给节点迭代器加一个过滤器来实现的. 这表示获取他的属性, 如 length 的时间复杂度为 O(n), 通过 length 来遍历整个列表需要 O(n^2).
1
2
3
4
var paragraphs = document .getElementsByTagName('p' );
for (var i = 0 ; i < paragraphs.length; i++) {
doSomething(paragraphs[i]);
}
这样做会更好:
1
2
3
4
var paragraphs = document .getElementsByTagName('p' );
for (var i = 0 , paragraph; paragraph = paragraphs[i]; i++) {
doSomething(paragraph);
}
这种方法对所有的 collections 和数组(只要数组不包含 falsy 值) 都适用.
在上面的例子中, 也可以通过 firstChild 和 nextSibling 来遍历孩子节点.
1
2
3
4
var parentNode = document .getElementById('foo' );
for (var child = parentNode.firstChild; child; child = child.nextSibling) {
doSomething(child);
}
类型分配&强制转换
1
2
3
4
5
6
7
8
9
10
11
12
13
var totalScore = this .reviewScore + ' total score' ;
var totalScore = this .reviewScore + '' ;
var totalScore = '' + this .reviewScore;
var totalScore = '' + this .reviewScore + ' total score' ;
使用parseInt对Numbers进行转换,并带一个进制作为参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var inputValue = '4' ;
var val = new Number (inputValue);
var val = +inputValue;
var val = inputValue >> 0 ;
var val = parseInt (inputValue);
var val = Number (inputValue);
var val = parseInt (inputValue, 10 );
注意: 当使用位运算时,Numbers被视为64位值,但是位运算总是返回32位整型。 对于整型值大于32位的进行位运算将导致不可预见的行为。 最大的有符号32位整数是2,147,483,647
1
2
3
4
5
6
7
2147483647 >> 0
2147483648 >> 0
2147483649 >> 0
~~2147483647
~~2147483648
~~2147483649
浮点数精度 使用了IEEE 754 浮点数格式来存储浮点类型的任何编程语言(C/C++/C#/Java 等等)都存在精度丢失问题。
在 C#、Java 中,提供了 Decimal、BigDecimal 封装类来进行相应的处理,才避开了精度丢失。
原生JS并没有提供相应的API, 寻么就会出现以下类似怪异情况:
1
2
3
4
0.1 + 0.2 == 0.30000000000000004
9999999999999999 == 10000000000000000 ;
0.05 + 0.2 == 0.25
0.05 + 0.9 == 0.95
对于这人问题这里不做展开,只提供解决方案,可使用 math.js 对数据进行运算
详情解释见 玉伯的JavaScript 中小数和大整数的精度丢失
命名规范
1
2
3
4
5
6
7
8
9
function q ( ) {
}
function query ( ) {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var OBJEcttsssss = {};
var this_is_my_object = {};
function c ( ) {}
var u = new user({
name : 'Bob Parr'
});
var thisIsMyObject = {};
function thisIsMyFunction ( ) {}
var user = new User({
name : 'Bob Parr'
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function user (options ) {
this .name = options.name;
}
var bad = new user({
name : 'nope'
});
function User (options ) {
this .name = options.name;
}
var good = new User({
name : 'yup'
});
1
2
3
4
5
6
this .__firstName__ = 'Panda' ;
this .firstName_ = 'Panda' ;
this ._firstName = 'Panda' ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function ( ) {
var self = this ;
return function ( ) {
console .log(self);
};
}
function ( ) {
var that = this ;
return function ( ) {
console .log(that);
};
}
function ( ) {
var _this = this ;
return function ( ) {
console .log(_this);
};
}
JavaScript 编码风格 文件编码
JavaScript 文件使用无 BOM
的 UTF-8
编码。
分号
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 myMethod = function ( ) {
return 42 ;
};
(function ( ) {
})();
var myMethod = function ( ) {
return 42 ;
}
(function ( ) {
})();
(function ( ) {
var name = 'Skywalker' ;
return name;
})();
或
;(function ( ) {
var name = 'Skywalker' ;
return name;
})()
(function ( ) {
var name = 'Skywalker'
return name
})()
JavaScript 的语句以分号作为结束符, 除非可以非常准确推断某结束位置才会省略分号. 语句中声明了函数/对象/数组直接量, 但 闭括号('}'或']')并不足以表示该语句的结束.
在 JavaScript 中, 只有当语句后的下一个符号是后缀或括号运算符时, 才会认为该语句的结束.
遗漏分号有时会出现很奇怪的结果, 所以确保语句以分号结束.
逗号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var foo = 1 ,
bar = 2 ,
baz = 3 ;
var obj = {
foo : 1 ,
bar : 2 ,
baz : 3
};
var foo = 1
, bar = 2
, baz = 3 ;
var obj = {
foo : 1
, bar : 2
, baz : 3
};
空格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function foo ( ) {
return "bar" ;
}
if (true ) {
}
function foo ( ) {
return "bar" ;
}
if (true ) {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
function fn (arg1, arg2 ) {
if (true ) {
function fn ( arg1, arg2 ) {
}
if ( true ) {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
{
foo : 1 ,
bar : 2 ,
baz : 3
}
{
foo : 1 ,
bar : 2 ,
baz : 3
}
1
2
3
4
5
6
7
8
9
function test ( ) {
console .log('test' );
}
function test ( ) {
console .log('test' );
}
1
2
3
4
5
var x = y + 5 ;
var x=y+5 ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function ( ) {
∙∙∙∙var name;
}
function ( ) {
∙var name;
}
function ( ) {
∙∙var name;
}
当调用很长的方法链时使用缩进,可以强调这行是方法调用,不是新的语句
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
$('#items' ).find('.selected' ).highlight().end().find('.open' ).updateCount();
$('#items' ).
find('.selected' ).
highlight().
end().
find('.open' ).
updateCount();
$('#items' )
.find('.selected' )
.highlight()
.end()
.find('.open' )
.updateCount();
var leds = stage.selectAll('.led' ).data(data).enter().append('svg:svg' ).classed('led' , true )
.attr('width' , (radius + margin) * 2 ).append('svg:g' )
.attr('transform' , 'translate(' + (radius + margin) + ',' + (radius + margin) + ')' )
.call(tron.led);
var leds = stage.selectAll('.led' )
.data(data)
.enter().append('svg:svg' )
.classed('led' , true )
.attr('width' , (radius + margin) * 2 )
.append('svg:g' )
.attr('transform' , 'translate(' + (radius + margin) + ',' + (radius + margin) + ')' )
.call(tron.led);
大括号
1
2
3
4
5
6
7
8
9
10
return {
key : value;
};
return
{
key :value;
};
代码的原意,是要返回一个对象,但实际上返回的是undefined,因为Javascript自动在return语句后面添加了分号。
单引号、双引号
1
var msg = 'This is some HTML' ;
单引号 (') 优于双引号 ("). 当你创建一个包含 HTML 代码的字符串时就知道它的好处了.
空行
1
2
3
4
5
6
7
doSomethingTo(x);
doSomethingElseTo(x);
andThen(x);
nowDoSomethingWith(y);
andNowWith(z);
二元和三元操作符
操作符始终跟随着前行, 这样就不用顾虑分号的隐式插入问题. 如果一行实在放不下, 还是按照下面的缩进风格来换行.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var x = a ? b : c;
var y = a ?
longButSimpleOperandB : longButSimpleOperandC;
var z = a ?
moreComplicatedB :
moreComplicatedC;
var isShow = true , x1;
isShow ? x1=2 ;
var isShow = true , x1;
isShow ? (x1=2 );
二元布尔操作符是可短路的, 只有在必要时才会计算到最后一项."||"
被称作为 default
操作符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo (opt_win ) {
var win;
if (opt_win) {
win = opt_win;
} else {
win = window ;
}
}
function foo (opt_win ) {
var win = opt_win || window ;
}
“&&” 也可简短代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (node) {
if (node.kids) {
if (node.kids[index]) {
foo(node.kids[index]);
}
}
}
if (node && node.kids && node.kids[index]) {
foo(node.kids[index]);
}
var kid = node && node.kids && node.kids[index];
if (kid) {
foo(kid);
}
语句块
对于使用if和else的多行语句块,把else和if语句块的右大括号放在同一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (test) {
thing1();
thing2();
} else {
thing3();
}
if (test) {
thing1();
thing2();
}
else {
thing3();
}
注释
多行注释使用/* … /,需包含一个描述、所有参数的具体类型和值以及返回值
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
* make() returns a new element
* based on the passed in tag name
*
* @param {String} tag
* @return {Element} element
*/
function make (tag ) {
return element;
}
function make (tag ) {
return element;
}
单行注释使用//
,把单行注释放在语句的上一行,并且在注释之前空一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getType ( ) {
console .log('fetching type...' );
var type = this ._type || 'no type' ;
return type;
}
function getType ( ) {
console .log('fetching type...' );
var type = this ._type || 'no type' ;
return type;
}
1
2
3
4
5
6
7
function Calculator ( ) {
this .total = 0 ;
return this ;
}
全局变量
避免使用全局变量;如果不得不使用,用大写字母表示变量名,比如UPPER_CASE。
==
和 ===
尽量使用’===’来进行逻辑等的判断,用’!==’进行逻辑不等的判断
==
作逻辑等判断时,会先进行类型转换后再进行比较。===
则不会。因而,==
进行的判断结果可能产生偏差。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var valueA = "1" ;
var valueB = 1 ;
if ( valueA == valueB) {
alert("Equal" );
}
else {
alert("Not equal" )
}
if ( valueA === valueB) {
alert("Equal" );
}
else {
alert("Not equal" )
}