前端编程规范
编程规范的制定很大程度上是为了弥补语言的不足。Javascript作为一门弱类型动态原型的语言,如果在团队开发中没有编程规范,后果将不堪设想。各花入各眼,欢迎交流。
目录
前言
目标:提高项目的可维护性和可扩展性
约定
代号
编程风格的制定参考了以下业界文档:
- (代号①)jQuery核心风格指南(jQuery Core Style Guide)
- (代号②)Dauglas Crockford的JavaScript代码规范(Code Conventions for the JavaScript Programming Language)
- (代号③)Google的JavaScript风格指南(Google JavaScript Style Guide)
- (代号④)Dojo编程风格指南(Dojo Style Guide)
在编程风格章节,部分条目是参照以上的文档制定,在说明的最后会有如下注释,即代表该条目是参照《Google的JavaScript风格指南》
参考:③
编程风格
“程序是写给人读的,只是偶尔让计算机执行一下。” —— Donald Knuth
在团队开发中,所有的代码看起来一致是极其重要的,原因有以下几点:
- 任何开发者都不会在乎某个文件的作者是谁,也没有必要花费额外精力去理解代码逻辑并重新排版,因为所有代码排版格式看起来非常一致。
- 我能很容易地识别出问题代码并发现错误。
语言规范
总是开启严格模式,即在各模块顶部添加 'use strict';
声明。
关于严格模式参考 MDN - Strict mode。
define(function (require, exports, module) {
'use strict';
// ...
});
function doSomething () {
'use strict';
//...
}
<script>
'use strict';
//...
</script>
变量
声明
总是使用 var
来声明变量
var name = 'alvin';
变量声明总是提前。
将所有的var语句合并为一个语句,每个变量的初始化独占一行。对于那些没有初始值的变量来说,它们应当出席在var语句的尾部。
// Good
function doSometingWithItems (items, count) {
var value = 10,
num = value + count,
item,
result,
i,
len;
if (num > 0) {
for (i = 0, len = items.length; i < len; i += 1) {
item = items[i];
result += item - num;
}
}
return result;
}
// Bad
function doSometingWithItems (items, count) {
var value = 10;
var num = value + count;
if (num > 0) {
var result;
for (var i = 0, len = items.length; i < len; i += 1) {
var item = items[i];
result += item - num;
}
return result;
}
}
赋值
总是使用直接量
// Good
var name = 'alvin';
var count = 100;
var forever = true;
var numbers = [1, 2, 3, 4];
var book = {
title: 'Javascript',
author: 'Brendan Eich'
};
// Bad
var name = new String('alvin');
var count = new Number(100);
var forever = new Boolean(true);
var numbers = new Array(1, 2, 3, 4);
var book = new Object();
book.title = 'Javascript';
book.author = 'Brendan Eich';
分号
总是使用分号。
如果不加分号JS解释器也会按隐式分隔的标准去执行,但那样调试、压缩、合并的时候都很不方便。
而且在某些情况下,不写分号可是很危险的:
MyClass.prototype.myMethod = function() {
return 42;
} // 这个缺德的没写分号
(function() {
// 匿名函数的执行
})();
上段代码会发生什么事情?
会报错(number is not a function)-第一个方法返回了42,因为没分号啊,后面就直接跟括号,所以第二个方法就很杯具的被当成一个参数传进来给42执行了(效果等同于 42(func)()
),可42并不是一个方法,报错。
括号
if...else...
,while
,for
,do...while...
,try...catch..finally...
总是使用括号
// Good
if (condition) {
doSomething();
} else if (otherCondition) {
doOtherThing();
} else {
doSomethigElse()
}
// Bad
if (condition)
doSomething();
else if (otherCondition)
doOtherThing();
else
doSomethingElse();
// Good
var i;
for (i in object) {
doSomething();
}
// Bad
var i;
for (i in object)
doSomething();
Switch
- 禁止出现连续执行(fall through)。每一个case代码块内都应当使用
break
; -
当
default
什么都不做时,省略dafault
,但必须写上注释。switch () { case ‘first’: //代码 break; case ‘second’: //代码 break; case ‘third’: //代码 break; default: //代码 } switch () { case ‘first’: //代码 break; case ‘second’: //代码 break; case ‘third’: //代码 break;
//没有default }
for-in循环
for-in循环是用来遍历对象属性的。不用定义任何控制条件,循环将会有条不紊地遍历每一个对象属性,并返回属性名。
for-in循环有一个问题,就是它不仅遍历对象的实例属性,同意还遍历原型继承来的属性。出于这个原因,最好使用 hasOwnProperty()
方法来为for-in循环过滤出实例属性。
var person = {name: 'alvin'};
var student = Object.create(person);
student.age = 12;
var i;
for (i in student) {
console.log(i);
if (student.hasOwnProperty(i)) {
console.log(student[i]);
}
}
相等
使用 ===
和 !==
而不是 ==
和 !=
,除非你百分百确定等式两边的类型是相等的。
eval()
只用于反序列化。(反序列化的意思是从字节流中重构对象,这里指的应该是JSON字符串重构成对象,或是执行服务器返回的JS语句)
eval()
很不稳定,会造成语义混乱,如果代码里还包含用户输入的话就更危险了,因为你无法确切得知用户会输入什么。
然而 eval 很容易解析被序列化的对象,所以反序列化的任务还是可以交给它做的。
arguments
arguments.callee
和 arguments.caller
将在未来的 JavaScript 版本中被禁用,因此在代码中禁止使用。
this
仅在构造函数,方法,闭包中去使用它。
this
语义很特别。它大多数情况下会指向全局对象,有的时候却是指向调用函数的作用域的(使用 eval
时),还可能会指向DOM树的某个节点(绑定事件时),新创建的对象(构造函数中),也可能是其他的一些什么乱七八糟的玩意(如果函数被 call()
或者被 apply()
)。
很容易出错的,所以最好是以下这两种情况的时候再选择使用:
- 在构造函数中(原型对象)
- 在对象的方法中(包括创建的闭包)
多级原型结构
不是怎么推荐使用。
多级原型结构指的是 JavaScript 实现继承。
比如自定义类D,并把自定义类B作为D的原型,那就是一个多级原型结构了。
原型结构越来越复杂了就越难维护,所以无非必要,或许你非常确定你在做些什么,不要使用继承。
多行字符串字面量
不要这样写:
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标准的一部分。
可以用 +
号运算符来连接每一行:
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.';
修改内置对象的原型
永远不要修改原生对象及其原型中已存在的方法,如需增加方法要先做判断。
var aProto = Array.prototype;
aProto.isArray = aProto.isArray || function () {
// ...
};
代码风格
缩进
使用四空格字符为一个缩进层级
// Good
function doSomething () {
var name = 'alvin';
if (name = 'alvin') {
for () {
}
}
}
// Not good
function doSomething () {
var name = 'alvin';
if (name = 'alvin') {
for () {
}
}
}
引号
使用单引号(’)比双引号(”)更好,特别是当创建一个HTML代码的字符串时候:
var msg = 'This is some HTML<a href="">link</a>';
介于此,我们字符串的字面量以单引号为准。
空白
-
运算符两边总是留有一个空格
// Good var count = max + min; var result = condition ? goodOne : badOne;
for (i = 0, l = o.leng; l > i; i++) { //… }
// Bad var count=max+min; var result=condition?goodOne:badOne;
for (i=0,l=o.leng;l>i;i++) { //… }
-
块语句的间隔
在左圆括号之前和右圆括号之后添加一个空格
// Good if (condition) { //… }
switch (condition) { //.. }
// Bad if(condition){ //… }
switch(condition){ //.. }
括号的对齐方式
将左花括号放置在块语句中第一句代码的末尾
// Good
if () {
//...
} else if () {
//...
} else {
//...
}
// Bad
if ()
{
//...
}
else if ()
{
//...
}
else
{
//...
}
// Good
switch () {
}
while () {
}
for () {
}
do {
} while () {
}
try {
} catch () {
} finally {
}
行的长度
行的长度应限定在80个字符
换行
当一行长度达到了单行最大字符数限制时,就需要手动将一行拆成两行。__通常__我们会在运算符后换行,下一行会增加两个层级的缩进。
// Good
callAFunction(document, element, window, 'some string value', true, 123,
navigator);
// Bad
callAFunction(document, element, window, 'some string value', true, 123,
navigator);
// Very bad
callAFunction(document, element, window, 'some string value', true, 123
, navigator);
// 语句换行
if (isLeapYear && isFebruary && day === 29 && itsYourBirthday &&
noPlans) {
waitAnotherFourYears();
}
// 变量赋值时:第二行的位置应当和赋值运算符的位置保持对齐
var result = somethig + anotherThing + yetAnotherThing + someThingElse +
anotherSomeThingElse;
空行
在下列场景中添加空行:
-
在方法之间
// Good function doSometing() { //… }
function doOtherThing () { //… }
// Bad function doSometing() { //… } function doOtherThing () { //… }
-
在方法中的局部变量和第一条语句之间
// Good function doSomething () { var name = ‘Alvin’, age = 23;
if (condition) { } }
// Bad function doSomething () { var name = ‘Alvin’, age = 23; if (condition) {
} }
-
在多行或单行注释之前
// Good function doSomething() { var name = ‘Alvin’;
// 如果代码执行到这里,则表明通过了所有安全性检查 if (condition) { } }
// Bad function doSomething() { var name = ‘Alvin’; // 如果代码执行到这里,则表明通过了所有安全性检查 if (condition) {
} }
-
在方法内的逻辑片段之间插入空行,提高可读性。
// Good if (w1 && w1.length) {
for (i = 0, l = w1.length; i < l; i += 1) { p = w1[i]; type = Y.Lang.type(r[p]); if (s.hasOwnProperty(p)) { if (merge && type =='object') { Y.mix(r[p], s[p]); } else if (ov || ! (p in r)) { r[p] = s[p]; } } } }
// Bad if (w1 && w1.length) { for (i = 0, l = w1.length; i < l; i += 1) { p = w1[i]; type = Y.Lang.type(r[p]); if (s.hasOwnProperty(p)) { if (merge && type ==’object’) { Y.mix(r[p], s[p]); } else if (ov || ! (p in r)) { r[p] = s[p]; } } } }
命名
采用驼峰式大小写命名法。
var thisIsMyName;
var anotherVariable;
var aVeryLongVariableName;
变量
变量命名应总是遵守小驼峰命名法。
-
变量命名前缀应当是名词
以名词作为前缀可以让变量和函数区分开来,因为函数名前缀应当是动词。
-
命名长度应该尽可能短,并且抓住要点(有意义)。
foo、bar和thisIsBannerAndBodyWidth之类的命名应当避免。
-
尽量在变量名中体现出值的数据类型。
比如:命名count、length、size表示数据类型是数字,而命名name、title和message表明数据类型是字符串。
// Good var count = 10; var myName = ‘Alvin’; var found = true;
// Bad var getCount = 10; var isFound = true;
属性和方法
私有的属性,变量和方法(在文件或类中)都应该改以下划线开头。
受保护的属性,变量和方法不需要用下划线(和公开的一样)。
函数
函数名的第一个单词应该是动词。这里有一些使用动词常见的约定。
- can => 函数返回一个布尔值
- has => 函数返回一个布尔值
- is => 函数返回一个布尔值
- get => 函数返回一个非布尔值
- set => 函数用来保存一个值
常量
使用大写字母和下划线来命名,下划线用以分隔单词,比如:
var MAX_COUNT = 10;
var URL = 'http://m.quecai.com';
构造函数
构造函数命名应总是遵守大驼峰命名法。
function Person (name) {
this.name = name;
}
Person.prototype.sayName = function() {
alert(this.name);
};
var me = new Person('Alvin');
构造函数命名也常常是名词,因为它们是用来创建某个类型的实例的。
注释
单行注释
以两个斜线开始,双斜线后敲入一个空格。
// 这是一个单行注释
使用方法:
-
独占一行的注释,用来解释下一行代码。这行注释之前总是有一个空行,且缩进层级和下一行代码保持一致。
// Good if (condition) {
//如果代码执行到这里,则表明通过了所有安全性检查 allowed(); }
// Bad if (condition) { //如果代码执行到这里,则表明通过了所有安全性检查 allowed(); }
-
在代码行的尾部的注释。代码结束到注释之间至少有一个空格。
// Good var result = something + somethingElse; // somethingElse不应当取值为null
// Bad var result = something + somethingElse;// somethingElse不应当取值为null
-
注释掉一大段代码
// if (condition) { // allowed(); // } // var result = something + somethingElse; // var result = something + somethingElse;
多行注释
范例:
/*
* 另一段注释
* 这段注释包含两行文本
*/
多行注释总是出现在将要描述的代码段之前,注释和代码之间没有空行间隔。多行注释之前应当有一个空行,且缩进层级和其描述的代码保持一致。
// Good
if (condition) {
/*
* 另一段注释
* 这段注释包含两行文本
*/
allowed();
}
文档注释
最流行的文档注释格式来自于JavaDoc文档格式:多行注释以单斜线加双星号(/**)开始,接下来是描述信息,其中使用@符号来表示一个或多个属性。
关于文档注释,请参照:JsDoc
范例:
/**
返回一个对象,这个对象包含被提供对象的所有属性。
后一个对象的属性会覆盖前一个对象的属性。
传入一个单独的对象,会创建一个它的签拷贝。
@method merge
@param {Object} 被合并的一个或多个对象
@return {Object} 一个新的合并后的对象
**/
merge () {
}
文件和目录规划
编程最佳实践
避免使用全局变量
全局变量就是在所有作用域中都可见的变量。
在浏览器中,window对象往往重载并等同于全局对象,因此在全局作用域中声明的变量和函数都是window对象的属性。
var color = 'red';
function sayColor () {
alert(color);
}
console.log(window.color); // 'red'
console.log(typeof window.sayColor); // 'function'
全局变量带来的问题
-
命名冲突
当脚本中的全局变量越来越多时,和浏览器未来的API或其他开发者的代码产生冲突的概率就越高。
-
代码的脆弱性
一个依赖全局变量的函数即是深度耦合于上下文环境之中。如果环境发生改变,函数有可能就失效了。
// 如果全局变量color不存在,sayColor方法将会报错 function sayColor () { alert(color); }
-
难以调试
任何依赖全局变量才能正常工作的函数,只有为其重新创建完整的全局环境才能正确地测试它。
正确地使用parseInt
parseInt
是把字符串转换为整数的函数。它在遇到非数字时会停止解析,所以 parseInt('16')
和 parseInt('16 coins')
会产生一样的结果。
如果该字符串第一个字符是0,那么该字符串会基于八进制二不是十进制来求值。在八进制中,8和9不是数字,所以 parseInt('08')
和 parseInt('09')
都产生0的结果。
parseInt
可以接受一个基数作为参数,如此一来, parseInt('08', 10)
结果为8。请总是带上基数参数。
+ 运算符
+运算符可以用于加法运算或字符串连接。它究竟会如何执行取决于其参数的类型。
- 如果其中一个运算数是一个空字符串,它会把另一个运算符转换成字符串并返回。
- 如果两个运算数都是数字,返回两者之和。
- 其他情况,它把两个运算符都转换成字符串并连接起来。
假值
Javascript的众多假值:
- 值:0;类型:Number
- 值:NaN(非数字);类型:Number
- 值:’‘(空字符串);类型:String
- 值:false;类型:Boolean
- 值:null;类型:Object
- 值:undefined;类型:Undefined
但是这些值是不可以互换的。
感谢
最后,感谢两本书和它们的作者。
本规范中很多条目都是直接引用或总结了两本书中的观点。 同时,两位作者的其他书籍或框架对学习Javascript可以提供很多帮助。