通常,使用functionNamesLikeThis
,variableNamesLikeThis
,ClassNamesLikeThis
,
EnumNamesLikeThis
,methodNamesLikeThis
和SYMBOLIC_CONSTANTS_LIKE_THIS
。
更多有关private
和protected
的信息见visibility。
可选参数以opt_
开头。
函数的参数个数不固定时,应该添加最后一个参数 var_args
为参数的个数。你也可以不设置var_args
而取代使用arguments
。
可选和可变参数应该在@param
标记中说明清楚。虽然这两个规定对编译器没有任何影响,但还是请尽量遵守。
EcmaScript 5中属性的Getters
和setters
并不鼓励使用。如果使用了,请不要在getters
中修改变量状态。
/**
* WRONG -- Do NOT do this.
*/
var foo = { get next() { return this.nextId++; } };
函数对象中的 Getters 和 Setters 也不是必须的。但如果你用了,你最好以类似 getFoo() 和 setFoo(value)的形式来命名。(如果返回布尔型的值,可以用 isFoo() 或者其他更自然的名字)
JavaScript不支持包和命名空间。
全局命名的冲突很难调试,多个系统集成时还可能因为命名冲突导致很严重的问题。为了提高 JavaScript 代码复用率,我们遵循下面的约定以避免冲突。
在全局作用域上,使用一个唯一的与工程/库相关的名字作为前缀标识。比如,你的工程是 "Project Sloth",那么命名空间前缀可取为sloth.*。
var sloth = {};
sloth.sleep = function() {
...
};
许多JavaScript库,包括the Closure Library和Dojo toolkit为你提供了声明你自己的命名空间的函数。比如:
goog.provide('sloth');
sloth.sleep = function() {
...
};
当选择了一个子命名空间,请确保父命名空间的负责人知道你在用哪个子命名空间,比如你为工程sloths创建一个hats子命名空间,那确保Sloth团队人员知道你在使用 sloth.hats。
"外部代码"是指来自于你代码体系的外部, 可以独立编译。内外部命名应该严格保持独立。如果你使用了外部库,他的所有对象都在foo.hats.下,那么你自己的代码不能在 foo.hats.下命名,因为很有可能其他团队也在其中命名。
foo.require('foo.hats');
/**
* WRONG -- Do NOT do this.
* @constructor
* @extend {foo.hats.RoundHat}
*/
foo.hats.BowlerHat = function() {
};
如果你需要在外部命名空间中定义新的 API,那么你应该直接导出一份外部库,然后在这份代码中修改。在你的内部代码中,应该通过他们的内部名字来调用内部API,这样保持一致性可让编译器更好的优化你的代码。
foo.provide('googleyhats.BowlerHat');
foo.require('foo.hats');
/**
* @constructor
* @extend {foo.hats.RoundHat}
*/
googleyhats.BowlerHat = function() {
...
};
goog.exportSymbol('foo.hats.BowlerHat', googleyhats.BowlerHat);
主要是为了提高可读性。局部空间中的变量别名只需要取原名字的最后部分。
/**
* @constructor
*/
some.long.namespace.MyClass = function() {
};
/**
* @param {some.long.namespace.MyClass} a
*/
some.long.namespace.MyClass.staticHelper = function(a) {
...
};
myapp.main = function() {
var MyClass = some.long.namespace.MyClass;
var staticHelper = some.long.namespace.MyClass.staticHelper;
staticHelper(new MyClass());
};
不要对命名空间创建别名。
myapp.main = function() {
var namespace = some.long.namespace;
namespace.MyClass.staticHelper(new namespace.MyClass());
};
除非是枚举类型,不然不要访问别名变量的属性。
/** @enum {string} */
some.long.namespace.Fruit = {
APPLE: 'a',
BANANA: 'b'
};
myapp.main = function() {
var Fruit = some.long.namespace.Fruit;
switch (fruit) {
case Fruit.APPLE:
...
case Fruit.BANANA:
...
}
};
myapp.main = function() {
var MyClass = some.long.namespace.MyClass;
MyClass.staticHelper(null);
};
不要在全局范围内创建别名,而仅在函数块作用域中使用。
文件名应该使用小写字符,以避免在有些系统平台上不识别大小写的命名方式。文件名以.js结尾,不要包含除 - 和 外的标点符号(使用 - 优于)。
应该总是成功调用且不要抛异常。
可自定义 toString()
方法,但确保你的实现方法满足:(1) 总是成功 (2) 没有其他负面影响。如果不满足这两个条件,那么可能会导致严重的问题,比如如果toString()
调用了包含assert
的函数,assert
输出导致失败的对象,这在toString()
也会被调用。
可以
没必要在每次声明变量时就将其初始化。
任何时候都需要
任何时候都要明确作用域 - 提高可移植性和清晰度。例如,不要依赖于作用域链中的 window对象。可能在其他应用中,你函数中的 window 不是指之前的那个窗口对象。
主要依照C++ 格式规范,针对 JavaScript,还有下面一些附加说明。
分号会被隐式插入到代码中,所以你务必在同一行上插入大括号。例如:
if (something) {
// ...
} else {
// ...
}
如果初始值不是很长,就保持写在单行上:
var arr = [1, 2, 3]; // No space after [ or before ].
var obj = {a: 1, b: 2, c: 3}; // No space after { or before }.
初始值占用多行时,缩进2个空格。
// Object initializer.
var inset = {
top: 10,
right: 20,
bottom: 15,
left: 12
};
// Array initializer.
this.rows_ = [
'"Slartibartfast" <[email protected]>',
'"Zaphod Beeblebrox" <[email protected]>',
'"Ford Prefect" <[email protected]>',
'"Arthur Dent" <[email protected]>',
'"Marvin the Paranoid Android" <[email protected]>',
'[email protected]'
];
// Used in a method call.
goog.dom.createDom(goog.dom.TagName.DIV, {
id: 'foo',
className: 'some-css-class',
style: 'display:none'
}, 'Hello, world!');
长的标识符或者数值,不要为了让代码好看些而手工对齐。如:
CORRECT_Object.prototype = {
a: 0,
b: 1,
lengthyName: 2
};
不要这样做:
WRONG_Object.prototype = {
a : 0,
b : 1,
lengthyName: 2
};
尽量让函数参数在同一行上。如果一行超过 80
字符,每个参数独占一行, 并以4
个空格缩进,或者与括号对齐,以提高可读性。尽可能不要让每行超过80
个字符。比如下面这样:
// Four-space, wrap at 80. Works with very long function names, survives
// renaming without reindenting, low on space.
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
// ...
};
// Four-space, one argument per line. Works with long function names,
// survives renaming, and emphasizes each argument.
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
veryDescriptiveArgumentNumberOne,
veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy,
artichokeDescriptorAdapterIterator) {
// ...
};
// Parenthesis-aligned indentation, wrap at 80. Visually groups arguments,
// low on space.
function foo(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
// ...
}
// Parenthesis-aligned, one argument per line. Emphasizes each
// individual argument.
function bar(veryDescriptiveArgumentNumberOne,
veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy,
artichokeDescriptorAdapterIterator) {
// ...
}
如果是函数被调用时缩进,你可以相对于原始语句的开始或相对于当前函数调用的开始启动4个空格。如下:
if (veryLongFunctionNameA(
veryLongArgumentName) ||
veryLongFunctionNameB(
veryLongArgumentName)) {
veryLongFunctionNameC(veryLongFunctionNameD(
veryLongFunctioNameE(
veryLongFunctionNameF)));
}
如果参数中有匿名函数,函数体从调用该函数的左边开始缩进2个空格,而不是从 function 这个关键字开始。这让匿名函数更加易读(不要增加很多没必要的缩进让函数体显示在屏幕的右侧)。
prefix.something.reallyLongFunctionName('whatever', function(a1, a2) {
if (a1.equals(a2)) {
someOtherLongFunctionName(a1);
} else {
andNowForSomethingCompletelyDifferent(a2.parrot);
}
});
var names = prefix.something.myExcellentMapFunction(
verboselyNamedCollectionOfItems,
function(item) {
return item.name;
});
事实上,除了初始化数组、对象和传递匿名函数外,所有被拆开的多行文本要么选择与之前的表达式左对齐,要么以4个(而不是2个)空格作为一缩进层次。
someWonderfulHtml = '' +
getEvenMoreHtml(someReallyInterestingValues, moreValues,
evenMoreParams, 'a duck', true, 72,
slightlyMoreMonkeys(0xfff)) +
'';
thisIsAVeryLongVariableName =
hereIsAnEvenLongerOtherFunctionNameThatWillNotFitOnPrevLine();
thisIsAVeryLongVariableName = siblingOne + siblingTwo + siblingThree +
siblingFour + siblingFive + siblingSix + siblingSeven +
moreSiblingExpressions + allAtTheSameIndentationLevel;
thisIsAVeryLongVariableName = operandOne + operandTwo + operandThree +
operandFour + operandFive * (
aNestedChildExpression + shouldBeIndentedMore);
someValue = this.foo(
shortArg,
'Some really long string arg - this is a pretty common case, actually.',
shorty2,
this.bar());
if (searchableCollection(allYourStuff).contains(theStuffYouWant) &&
!ambientNotification.isActive() && (client.isAmbientSupported() ||
client.alwaysTryAmbientAnyways())) {
ambientNotification.activate();
}
使用空行来划分一组逻辑上相关联的代码片段。如下:
doSomethingTo(x);
doSomethingElseTo(x);
andThen(x);
nowDoSomethingWith(y);
andNowWith(z);
操作符始终跟随着前行,这样就不用顾虑分号的隐式插入问题。如果一行实在放不下,还是按照上述的缩进风格来换行。
var x = a ? b : c; // All on one line if it will fit.
// Indentation +4 is OK.
var y = a ?
longButSimpleOperandB : longButSimpleOperandC;
// Indenting to the line position of the first operand is also OK.
var z = a ?
moreComplicatedB :
moreComplicatedC;
下面包含点操作符:
var x = foo.bar().
doSomething().
doSomethingElse();
只在需要的时候使用
不要滥用括号,只在必要的时候使用它。 对于一元操作符(如delete, typeof 和 void),或是在某些关键词(如 return, throw, case, new)之后,不要使用括号。
使用单引号优于双引号
单引号 (') 优于双引号 (")。当你创建一个包含 HTML 代码的字符串时就知道它的好处了。
var msg = 'This is some HTML';
推荐使用 JSDoc 中的两个注解:@private
和 @protected
JSDoc 的两个注解@private
和@protected
用来指明类,函数,属性的可见性域。
--jscomp_warning=visibility
选项可以打开编译器的警告功能,详见Closure Compiler Warnings。
标记为@private
的属性和方法,只有在当前文件中可访问它;
标记为@private
的构造函数表示该类只能在当前文件或是其静态/普通成员中实例化。私有构造器的公共静态属性在当前文件的任何地方都可访问,通过 instanceof 操作符也可。
永远不要为全局变量、函数、构造器加@protected
标记。
// File 1.
// AA_PrivateClass_ and AA_init_ are accessible because they are global
// and in the same file.
/**
* @private
* @constructor
*/
AA_PrivateClass_ = function() {
};
/** @private */
function AA_init_() {
return new AA_PrivateClass_();
}
AA_init_();
标记为@private
的属性,在当前文件中可访问它。如果类属性私有的,"拥有"该属性的类的所有静态/普通成员也可访问,但它们不能被不同文件中的子类访问或覆盖。
标记为@protected
的属性,在当前文件中可访问它,如果是类属性保护,那么"拥有"该属性的类及其子类中的所有静态/普通成员也可访问。
注意:这与 C++,Java中的私有和保护不同,它们是在当前文件中,检查是否具有访问私有/保护属性的权限,有权限即可访问,而不是只能在同一个类或类层次上。而 C++ 中的私有属性不能被子类覆盖。(C++/Java 中的私有/保护是指作用域上的可访问性,在可访问性上的限制。JS 中是在限制在作用域上。PS: 可见性是与作用域对应)
// File 1.
/** @constructor */
AA_PublicClass = function() {
/** @private */
this.privateProp_ = 2;
/** @protected */
this.protectedProp = 4;
};
/** @private */
AA_PublicClass.staticPrivateProp_ = 1;
/** @protected */
AA_PublicClass.staticProtectedProp = 31;
/** @private */
AA_PublicClass.prototype.privateMethod_ = function() {};
/** @protected */
AA_PublicClass.prototype.protectedMethod = function() {};
// File 2.
/**
* @return {number} The number of ducks we've arranged in a row.
*/
AA_PublicClass.prototype.method = function() {
// Legal accesses of these two properties.
return this.privateProp_ + AA_PublicClass.staticPrivateProp_;
};
// File 3.
/**
* @constructor
* @extends {AA_PublicClass}
*/
AA_SubClass = function() {
// Legal access of a protected static property.
AA_PublicClass.staticProtectedProp = this.method();
};
goog.inherits(AA_SubClass, AA_PublicClass);
/**
* @return {number} The number of ducks we've arranged in a row.
*/
AA_SubClass.prototype.method = function() {
// Legal access of a protected instance property.
return this.protectedProp;
};
强烈建议使用编译器。
如果使用 JSDoc,那么尽量具体地,准确地根据它的规则来书写类型说明。支持的类型给予EcmaScript 4 spec。
ES4提案中包含指定的JavaScript类型的语言。我们在JsDoc中使用此语言表达函数参数和返回值。由于ES4提案的演变,这种语言发生了变化,编译器仍然支持旧语法类型,但是这些语法已过时。
表格参看:The JavaScript Type Language
表格参看:The JavaScript Type Language
可能出现类型检查并不能准确判断表达式的类型的情况,可以在注释里添加类型标注,并在中括号内写出表达式的类型,如果有对该类型的注解就更好了。
/** @type {number} */ (x)
JavaScript 是一种弱类型语言,明白可选,非空和未定义参数或属性之间的细微差别还是很重要的。对象类型(引用类型)默认非空。注意:函数类型默认不能为空. 除了字符串、整型、布尔、undefined 和 null 外,对象可以是任何类型。
/**
* Some class, initialized with a value.
* @param {Object} value Some value.
* @constructor
*/
function MyClass(value) {
/**
* Some value.
* @type {Object}
* @private
*/
this.myValue_ = value;
}
告诉编译器 myValue 属性为一对象或null。如果 myValue 永远都不会为 null,就应该如下声明:
/**
* Some class, initialized with a non-null value.
* @param {!Object} value Some value.
* @constructor
*/
function MyClass(value) {
/**
* Some value.
* @type {!Object}
* @private
*/
this.myValue_ = value;
}
这样,当编译器在代码中碰到 MyClass 为 null 时,就会给出警告。 函数的可选参数可能在运行时没有定义,所以如果他们又被赋给类属性, 需要声明成:
/**
* Some class, initialized with an optional value.
* @param {Object=} opt_value Some value (optional).
* @constructor
*/
function MyClass(opt_value) {
/**
* Some value.
* @type {Object|undefined}
* @private
*/
this.myValue_ = opt_value;
}
这告诉编译器 myValue_ 可能是一个对象、null或undefined。 注意:可选参数 opt_value 被声明成 {Object=},而不是 {Object|undefined}。这是因为可选参数可能是undefined。虽然直接写undefined 也并无害处,但鉴于可阅读性还是写成上述的样子。 最后,属性的非空和可选并不矛盾,属性既可是非空,也可是可选的。下面的四种声明各不相同:
/**
* Takes four arguments, two of which are nullable, and two of which are
* optional.
* @param {!Object} nonNull Mandatory (must not be undefined), must not be null.
* @param {Object} mayBeNull Mandatory (must not be undefined), may be null.
* @param {!Object=} opt_nonNull Optional (may be undefined), but if present,
* must not be null!
* @param {Object=} opt_mayBeNull Optional (may be undefined), may be null.
*/
function strangeButTrue(nonNull, mayBeNull, opt_nonNull, opt_mayBeNull) {
// ...
};
类型定义也可以复杂化,一个函数可以接受元素节点的内容:
/**
* @param {string} tagName
* @param {(string|Element|Text|Array.<Element>|Array.<Text>)} contents
* @return {!Element}
*/
goog.createElement = function(tagName, contents) {
...
};
你可以定义@typedef 标记的常用类型表达式,例如:
/** @typedef {(string|Element|Text|Array.<Element>|Array.<Text>)} */
goog.ElementContent;
/** @typedef {(string|Element|Text|Array.<Element>|Array.<Text>)} */
goog.ElementContent;
/**
* @param {string} tagName
* @param {goog.ElementContent} contents
* @return {!Element}
*/
goog.createElement = function(tagName, contents) {
...
};
编译器已经有限的支持模板类型。它只能推断的类型,这在一个匿名函数字面量从类型的这个论点,是否这个论点是缺失的。
/**
* @param {function(this:T, ...)} fn
* @param {T} thisObj
* @param {...*} var_args
* @template T
*/
goog.bind = function(fn, thisObj, var_args) {
...
};
// Possibly generates a missing property warning.
goog.bind(function() { this.someProperty; }, new SomeClass());
// Generates an undefined this warning.
goog.bind(function() { this.someProperty; });
我们鼓励依照 C++ style for comments 的风格。所有的文件、类、方法和属性都应该以 JSDoc 风格来进行注释。 行内注释使用 // 。避免出现句式片段,如果是英文首字母大写,记得加标点符号。
JSDoc的语法基于JavaDoc 。 许多工具可以从JSDoc注释中提取元数据来执行代码的验证和优化。当然,前提是这些注释都是符合语法规则的。
/**
* A JSDoc comment should begin with a slash and 2 asterisks.
* Inline tags should be enclosed in braces like {@code this}.
* @desc Block tags should always start on their own line.
*/
如果你不得不换行块标签,那就应该缩进四个空格以保持注释内容的结构清晰。
/**
* Illustrates line wrapping for long param/return descriptions.
* @param {string} foo This is a param with a description too long to fit in
* one line.
* @return {number} This returns something that has a description too long to
* fit in one line.
*/
project.MyClass.prototype.method = function(foo) {
return 5;
};
你不应该缩进 @fileoverview 命令。
尽管缩进至与上排注释同列并不怎么好,但也是可以接受的。
/**
* This is NOT the preferred indentation method.
* @param {string} foo This is a param with a description too long to fit in
* one line.
* @return {number} This returns something that has a description too long to
* fit in one line.
*/
project.MyClass.prototype.method = function(foo) {
return 5;
};
就像JavaDoc一样,JSDoc支持好多HTML标签,如 <code>, <pre>, <tt>, <strong>, <ul>, <ol>, <li>, <a>等等。
所以纯文本状态并不会被格式化,比如换行和空格什么的都会被忽略掉:
/**
* Computes weight based on three factors:
* items sent
* items received
* last timestamp
*/
上面的结果其实是:
Computes weight based on three factors: items sent items received last timestamp
替而换之,可以用这种方式:
/**
* Computes weight based on three factors:
* <ul>
* <li>items sent
* <li>items received
* <li>last timestamp
* </ul>
*/
关于写注释更多信息可以去看一下JavaDoc风格指南。
顶部注释是为了让读者能够很快的明了这个文件里的代码是干嘛的,描述应包括作者,年代,依赖关系或兼容信息等相关,下面的例子:
/**
* @fileoverview Description of file, its uses and information
* about its dependencies.
*/
对于类的注释,肯定要写功能和类型的描述,还有一些参数和原型描述等信息。
/**
* Class making something fun and easy.
* @param {string} arg1 An argument that makes this more interesting.
* @param {Array.<number>} arg2 List of numbers to be processed.
* @constructor
* @extends {goog.Disposable}
*/
project.MyClass = function(arg1, arg2) {
// ...
};
goog.inherits(project.MyClass, goog.Disposable);
应该有参数和返回值的描述,方法描述要以使用者的身份去写。
/**
* Operates on an instance of MyClass and returns something.
* @param {project.MyClass} obj Instance of MyClass which leads to a long
* comment that needs to be wrapped to two lines.
* @return {boolean} Whether something occured.
*/
function PR_someMethod(obj) {
// ...
}
一些简单的get方法没有参数和其他影响的,可以忽略描述。
/** @constructor */
project.MyClass = function() {
/**
* Maximum number of things per pane.
* @type {number}
*/
this.someProperty = 4;
}
表格参看:JSDoc Tag Reference
在第三方代码中, 你还会见到其他一些 JSDoc 标记. 这些标记在 JSDoc Toolkit Tag Reference 都有介绍到,但在 Google 的代码中,目前不推荐使用。你可以认为这些是将来会用到的 "保留" 名。它们包含:
推荐使用
建议您去使用 JS 编译器,如 Closure Compiler.
以下的表达式都返回false:
注意,以下的都返回true:
你可能会写下面这段代码:
while (x != null) {
其实还可以写的更短些(只要你也不希望x是0、空字符串和false):
while (x) {
如果想检查字符串是否为null或空,你可能会这样写:
if (y != null && y != '') {
当然可以写的更短些:
if (y) {
注意: 还有很多非直观的布尔表达式,如下:
下面这段代码可以被三元操作符所替换:
if (val != 0) {
return foo();
} else {
return bar();
}
你可以写成:
return val ? foo() : bar();
在生成HTML的时候也很有用噢:
var html = '<input type="checkbox"' +
(isChecked ? ' checked' : '') +
(isEnabled ? '' : ' disabled') +
' name="foo">';
这俩二元布尔操作符可以根据前面的代码判断后面的代码是否执行,也就是说只有在必要的时候才会执行后面的代码。
"||" 可以被称为默认操作符,因为它可以代替下面的情况:
/** @param {*=} opt_win */
function foo(opt_win) {
var win;
if (opt_win) {
win = opt_win;
} else {
win = window;
}
// ...
}
其实可以直接这样写:
/** @param {*=} opt_win */
function foo(opt_win) {
var win = opt_win || window;
// ...
}
"&&" 也可以缩减代码量,比如:
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);
}
但如果这样的话就有点过了:
node && node.kids && node.kids[index] && foo(node.kids[index]);
节点列表是通过节点迭代器和一个过滤器来实现的,这表示它的一个属性例如length的时间复杂度是O(n),通过length来遍历整个列表则需要O(n^2)。
var paragraphs = document.getElementsByTagName('p');
for (var i = 0; i < paragraphs.length; i++) {
doSomething(paragraphs[i]);
}
这样写会更好些:
var paragraphs = document.getElementsByTagName('p');
for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) {
doSomething(paragraph);
}
这种方式对所有集合和数组都适用,当然只要里面没有布尔值false。
你也可以通过 firstChild 和 nextSibling 属性来遍历子节点。
var parentNode = document.getElementById('foo');
for (var child = parentNode.firstChild; child; child = child.nextSibling) {
doSomething(child);
}