导读:最近一直苦恼各个项目已有代码(PHP、JS、HTML、CSS等)的维护成本——可读性实在是太差了,所以,在一边完成工作的时候,就想着规范下书写格式。那就找前辈们的心得吧。
详细:
1 原文地址
Google JavaScript Style Guide Revision 2.64 | Aaron Whyte | Bob Jervis | Dan Pupius | Eric Arvidsson | Fritz Schneider | Robby Walker
其他那些说明部分就不翻译了。
2 边读边译(忠于原文格式)
变量(var)
声明时使用 var:总是
缘由:若不使用 var,那么声明的变量将会进入全局上下文,这样有可能重写已存在的变量。而且,当不使用var声明变量时,很难确认变量生存的作用域。例如:它有可能是在DOM或者window里,就如同在局部作用域那样。所以,请一直使用var声明变量。
常量(Constants)
· 使用 NAME_LIKE_THIS 这样的形式去定义常量;
· (翻译到这里的时候,才想起,肯定已经有人翻译过了吧。于是,果然,sina的以为哥们干过了。不过,他的版本是2.28,现在已经是2.64,我要做的,就是认真读一次,看看那些不同的地方。为了防止他的站点消失,俺复制了一份到本地。嘿嘿。经常这么干。
译文地址:http://chajn.org/jsguide/javascriptguide.html 作者:chajn (sina weibo)
)
这里有一个新的例子,说明变量是个常量,但是这个常量的值不是一个定值,而是可变的。
/**
* Map of URL to response string.
* @const
*/
MyClass.fetchedUrlCache_ = new goog.structs.Map();
在这个例子里,标识(的变量)是永远也不会被重写的,但是,这个变量的值确实高度可变的,切不是常量(所以使用了驼峰命名法,而不是全部大写)。
分号(Semicolon)
这里基本没什么变化,但是chajn说的“加上分号也会在某些情况下增进代码的性能,因为这样解析器就不必再花时间推测应该在哪里插入分号了(取自《Javascript高级程序设计》)”这是值得更进一步知道的,性能很重要。
最后在,2.64中增加了一段:
说明:分号和函数的关系
分号,应该是书写在【函数表达式】语句后的,而不是【函数声明】后。例子:
var foo = function() {
return true;
}; // 这里应该有分号
function foo() {
return true;
} // 这里不必有分号(我:有也不会报错)
函数嵌套(Nested Functions)
块内定义函数(Function Declarations Within Blocks)
引用下,告诫自己:虽然很多的JS引擎都支持下面例子那种做法,但是,最好要那么做啊。
为啥?一来不是标准,二来,你有病啊。就像我现在接触的代码(PHP),硬是把1当做假,0当做真,你说你一直用就行,后来的又变成1是真,0是假。这数据通信……尼玛!
if (x) {
var foo = function() {}
}
异常处理(Exceptions)
就一点:对不可与之的情况做异常处理,不要对已知情况去使用这个方式。
自定义异常(Custom exceptions)
这么做的好处是因为各个浏览器的js引擎对相同的错误抛出信息(throw)不一致,为了代码的可维护性,那就自定义异常吧。
标准特性(Standards features)
看完这个说法,觉得应该在时间充裕的情况下,忘记 jq 的存在。
原始类型的实例对象(Wrapper objects for primitive types)
ECMAScript 有 5 种原始类型,即 Undefined、Null、Boolean、Number 和 String。
按照chajn的说法,你是用new进行基本类型的实例化,是做不了高富帅的。(吊死啥时候高富帅过?!==)
所以,在进行类型转换的时候,是可以的——但需要明确转换的值。
类型转换方式:Bollean(),Number(),String() —— 强制类型转换;parseInt(),parseFloat(),toString() —— 对象方法转换。
多级原型层次结构(Multi-level prototype hierarchies)
不推荐使用。closure library。
方法和属性的定义(Method and property definitions)
/** @constructor */ function SomeConstructor() { this.someProperty = 1; } Foo.prototype.someMethod = function() { ... };
虽然有很多方式给使用new新建的对象添加方法和属性,但是,以下这种方式才是推荐的:
A 方法
Foo.prototype.bar = function() {
/* ... */
};
而对于属性的添加,推荐方式是使用构造函数进行初始化,如
B 属性/** @constructor */
function Foo() {
this.bar = value;
}
为何?
应为当前的js引擎优化工作都是基于对象"shape" of an object ,详细的阅读这里。我是没怎么看明白。
删除(delete)
对属性(变量)的删除操作,推荐使用this.foo = null.。使用如下的方式:
Foo.prototype.dispose = function() {
this.property_ = null;
};
来代替下面这种方式:
Foo.prototype.dispose = function() {
delete this.property_;
};
在现代js引擎中,改变一个对象属性的数量要远慢于给这些需要“删除”的变量进行重新赋值。关键字
delete应该尽量避免使用,除非是需要清除对象重复的属性名键值表,或者改变
if(key in obj)的计算结果(如遍历关联数组)。
闭包(Closures)
推荐使用。但是一定要小心。
这里更新一下原文的应用地址,关于 a good description of how closures work 的地址,原文作者已经更换了连接。
eval()
只有在反序列化时候使用。例如RPC相应。
with() {}
不要使用。
this
因为this的语义特殊性,在不用的上下文指向不同,
Because this is so easy to get wrong, limit its use to those places where it is required:
· in constructors
· in methods of objects (including in the creation of closures)
for-in 循环(for-in loop)
除了在对object/map/hash进行遍历时,别使用。遍历数组使用for(){} 就ok了。
chajn的注释有些意思。
关联数组如下:
// 定义空数组
var myMap = {};
// 直接定义数组
var myMap = {"key1": "val1", "key2": "val2"};
// 用Array定义数组
var myMap = new Array();
myMap["key1"] = "val1";
myMap["key2"] = "val2";
多行字符串字面量(Multiline string literals)
不要这么干。多行就用字符串连接符操作吧。
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.';
数组和对象字面量(Array and Object literals)
使用字面量而非构造函数。
使用如下方式定义数组(字面量方式):
var a = [x1, x2, x3];
var a2 = [x1, x2];
var a3 = [x1];
var a4 = [];
而非:
// 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();
var o = {};
var o2 = {
a: 0,
b: 1,
c: 2,
'strange key': 3
};
而非:
var o = new Object();
var o2 = new Object();
o2.a = 0;
o2.b = 1;
o2.c = 2;
o2['strange key'] = 3;
修改内置对象的原型(Modifying prototypes of builtin objects)
不要这么干!(我之前的一篇文字看来需要修改了。)
IE条件注释(Internet Explorer's Conditional Comments)
不要。
var f = function () {
/*@cc_on if (@_jscript) { return 2* @*/ 3; /*@ } @*/
};
命名(Naming)
通常,使用如下方式:
functionNamesLikeThis, variableNamesLikeThis, ClassNamesLikeThis, EnumNamesLikeThis, methodNamesLikeThis,
CONSTANT_VALUES_LIKE_THIS, foo.namespaceNamesLikeThis.bar, filenameslikethis.js
自定义toString()方法(Custom toString() methods)
确保 1总是成功的,2木有副作用。
滞后初始化(Deferred initialization)
没必要定义时候就初始化,滞后进行这一操作也挺好。
明确作用域(Explicit scope)
任何时候都要明确作用域 - 提高可移植性和可读性。
代码格式化(Code formatting)
这一块内容太多了,放到下一篇文字,和C++风格一起阅读了。
括号(Parentheses)
不要使用括号,在一元操作符后,例如 delete,typeof和void ,或者在关键字后,例如 return,throw,就像case, in或者new。
字符串符号
使用单引号而非双引号。' √, ” ×。
(剩下的明儿再补了,真长)
可见性[私有和受保护的字段](Visibility [private and protected fields])
这一部分是要干嘛呢?说的是这些变量的作用域?JSDoc,表示还没用过。道行浅了。
JS类型(JavaScript Types)
说那些类型的。
注释(Comments)
使用JSDoc。看来这玩意得好好学学了。
goog.provide提供的依赖(Providing Dependencies With goog.provide)
值提供顶级标识就行了。
定义一个类的所有成员应该在同一个文件中。所以,只有顶级类应该提供。
编译(Compiling)
必须的。
技巧和陷阱(Tips and Tricks)
这部分内容英文也比较简单了,直接复制过来。如下:
True and False Boolean Expressions
The following are all false in boolean expressions:
null
undefined
''
the empty string0
the numberBut be careful, because these are all true:
'0'
the string[]
the empty array{}
the empty objectThis means that instead of this:
while (x != null) {
you can write this shorter code (as long as you don't expect x to be 0, or the empty string, or false):
while (x) {
And if you want to check a string to see if it is null or empty, you could do this:
if (y != null && y != '') {
But this is shorter and nicer:
if (y) {
Caution: There are many unintuitive things about boolean expressions. Here are some of them:
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
Conditional (Ternary) Operator (?:)
Instead of this:
if (val != 0) { return foo(); } else { return bar(); }
you can write this:
return val ? foo() : bar();
The ternary conditional is also useful when generating HTML:
var html = '<input type="checkbox"' + (isChecked ? ' checked' : '') + (isEnabled ? '' : ' disabled') + ' name="foo">';
&& and ||
These binary boolean operators are short-circuited, and evaluate to the last evaluated term.
"||" has been called the 'default' operator, because instead of writing this:
/** @param {*=} opt_win */ function foo(opt_win) { var win; if (opt_win) { win = opt_win; } else { win = window; } // ... }
you can write this:
/** @param {*=} opt_win */ function foo(opt_win) { var win = opt_win || window; // ... }
"&&" is also useful for shortening code. For instance, instead of this:
if (node) { if (node.kids) { if (node.kids[index]) { foo(node.kids[index]); } } }
you could do this:
if (node && node.kids && node.kids[index]) { foo(node.kids[index]); }
or this:
var kid = node && node.kids && node.kids[index]; if (kid) { foo(kid); }
However, this is going a little too far:
node && node.kids && node.kids[index] && foo(node.kids[index]);
Iterating over Node Lists
Node lists are often implemented as node iterators with a filter. This means that getting a property like length is O(n), and iterating over the list by re-checking the length will be O(n^2).
var paragraphs = document.getElementsByTagName('p'); for (var i = 0; i < paragraphs.length; i++) { doSomething(paragraphs[i]); }
It is better to do this instead:
var paragraphs = document.getElementsByTagName('p'); for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) { doSomething(paragraph); }
This works well for all collections and arrays as long as the array does not contain things that are treated as boolean false.
In cases where you are iterating over the childNodes you can also use the firstChild and nextSibling properties.
var parentNode = document.getElementById('foo'); for (var child = parentNode.firstChild; child; child = child.nextSibling) { doSomething(child); }
看完这个guide,比较开心呢。不过,还有很多看不懂,继续修行。
扩展阅读:
crockford规范 英文版 //javascript.crockford.com/code.html
中文版 //www.yeeyan.org/articles/view/cloudwater/4042
Dojo规范 英文版 //dojotoolkit.org/community/styleGuide
中文版 //code.google.com/p/grace/wiki/DojoStyle