Google JavaScript Style Guide 中文简要翻译 – Style Rules部分

JavaScript Style Rules

2.1 Naming 命名
通常地,像下面一样命名:

functionNamesLikeThis
variableNamesLikeThis
ClassNamesLikeThis
EnumNamesLikeThis
methodNamesLikeThis
SYMBOLIC_CONSTANTS_LIKE_THIS

函数名、变量名、方法名:首字母小写
类名、枚举名:首字母大写
常量:恒大写,下划线分割

2.1.1 属性和方法
私有属性、变量和方法应该以下划线结尾
受保护属性、变量和方法不应当以下划线结尾(和公有一样)
关于私有、受保护,参考visibility一节

2.1.2 方法和函数的参数
可选的参数以「opt_」开头
如果函数接受不定参数,应当将此参数放在参数列表的最后,并且以var_args命名
但在函数体内不应使用var_args。使用arguments数组来代替。

2.1.3 属性的Getters和Setters
ECMAScript5的Getters和Setters不建议使用。
如果使用了,那么Getters中不应该修改可见的状态
错误的用法:

/**
 * WRONG -- Do NOT do this.
 */
var foo = { get next() { return this.nextId++; } };
};

2.1.4 存取函数
属性的Getters和Setters不是必需的。
但是,如果要写,那么Getters必须写成getFoo()、Setters必须写成setFoo(value)的形式。
布尔型的Getters可以写成isFoo(),并且通常看起来更自然。

2.1.5 命名空间
JavaScript没有原生的包或命名空间机制。
全局的命名冲突很难debug,而且在两个工程合并时会导致棘手的问题。
我们采取约定俗成的方式来防止冲突来实现代码重用。

2.1.5.1 为全局的代码使用命名空间
总是使用和工程名或类库名相同的词作为(伪)命名空间的前缀。
例如,如果你正在写一个叫”Sloth”的工程,合理的(伪)命名空间应该是sloth.* :

var sloth = {};

sloth.sleep = function() {
  ...
};

许多JavaScript类库提供创建命名空间的高级函数,如the Closure LibraryDojo toolkit

goog.provide('sloth');

sloth.sleep = function() {
  ...
};

2.1.5.2 明晰命名空间的所有权
当创建子命名空间时,确保父命名空间的所有人知道你在干什么。
例如,当你开始一个为sloths创建hats的工程时,确保Sloth团队知道你在使用sloth.hats命名空间

2.1.5.3 内部代码不能使用和外部代码相同的命名空间
外部代码是指不在你的代码库中的代码,它们独立地编译(通常是引入的第三方类库,但也有可能是别的团队写的类库)。
内部代码和外部代码的命名空间要严格分离。
比如,一个外部库定义了foo.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);

2.1.5.4 别名以增加可读性
使用本地别名替代全修饰名,以增加可读性。
本地别名必须和全修饰名的最后部分吻合。
例如:

/**
 * @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);
};

永远不要在全局环境创建别名。请仅在函数内使用它们。

2.1.6 文件名
文件名保持全小写,以避免在大小写敏感的平台上发生混乱。
文件名的扩展名应该是js,除在分隔时使用「-」或「_」外,不应包括其他的标点符号。
(优选使用「-」)

2.2 自定义的toString()方法
必须总是能成功调用并且没有任何副作用。
自定义toString()是好的,但必须保证:
(1)总是能成功调用
(2)没有任何副作用
如果你的toString没有满足这些要求,很容易就会导致严重问题。
例如,如果toString()里调用了一个做断言的方法,断言失败时可能会尝试输出发生失败的类的名称(跟踪失败发生在哪里),而输出类名当然又要调用到toString()

2.3 延迟初始化
可以。
不总是可能在声明时就初始化变量,所以延迟初始化时可以的。

2.4 Explicit scope
总是使用显式范围
(待译)
Always use explicit scope – doing so increases portability and clarity. For example, don’t rely on window being in the scope chain. You might want to use your function in another application for which window is not the content window.

2.5 代码格式化
基本上使用C++的格式化规则
除了下面几点以外。

2.5.1 大括弧
为了避免解析器隐含的插入分号,总是在入口的那一行开始大括弧。
例如:

if (something) {
  // ...
} else {
  // ...
}

2.5.2 数组和对象的初始化
当一行能写得下时,可以全部写在一行:

var arr = [1, 2, 3];  // 「[」后面和「]」前面没有空格!
var obj = {a: 1, b: 2, c: 3};  // 「{」后面和「}」前面没有空格!

多行数组的初始化则缩进2个空格,和块的方式类似,例如:

// Object initializer.
var inset = {
  top: 10,
  right: 20,
  bottom: 15,
  left: 12
};

// Array initializer.
this.rows_ = [
  '"Slartibartfast" <fjordmaster@magrathea.com>',
  '"Zaphod Beeblebrox" <theprez@universe.gov>',
  '"Ford Prefect" <ford@theguide.com>',
  '"Arthur Dent" <has.no.tea@gmail.com>',
  '"Marvin the Paranoid Android" <marv@googlemail.com>',
  'the.mice@magrathea.com'
];

// 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
};

2.5.3 函数参数列表
如果可能,全部的参数都写在同一行。
但当一行超过80个字符位,为了更加容易阅读,必须换行。
为了节省空间,可以尽量写满80字符,也可以为了更加易读,每行只放一个参数。
每行缩进4个字符位,或者对齐圆括弧。

一些例子:

// 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.  Visually groups and
// emphasizes each individual argument.
function bar(veryDescriptiveArgumentNumberOne,
             veryDescriptiveArgumentTwo,
             tableModelEventHandlerProxy,
             artichokeDescriptorAdapterIterator) {
  // ...
}

当调用的函数本身已经缩进时,参数列可以选择再缩进4个字符也可以选择对齐函数
例如,下面的几种写法都是可以的:

if (veryLongFunctionNameA(
        veryLongArgumentName) ||
    veryLongFunctionNameB(
    veryLongArgumentName)) {
  veryLongFunctionNameC(veryLongFunctionNameD(
      veryLongFunctioNameE(
          veryLongFunctionNameF)));
}

2.5.4 匿名函数的传入
当在参数列表中声明匿名函数时,为了使匿名函数更可读(避免整个函数块显示在右半屏幕),
匿名函数体应当从调用语句的位置起再缩进2个字符位,
或者从函数的声明位置起缩进2个字符位。
例如:

// 匿名函数在调用语句的同一行中声明
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;
    });

2.5.5 更多的缩进
实际上除了数组/对象的初始化以及传递匿名函数外,所有的换行都应该:
要不左对齐上一行的表达式,
要不缩进4个字符。而不是缩进2个字符。

例如:

someWonderfulHtml = '' +
                    getEvenMoreHtml(someReallyInterestingValues, moreValues,
                                    evenMoreParams, 'a duck', true, 72,
                                    slightlyMoreMonkeys(0xfff)) +
                    '';

thisIsAVeryLongVariableName =
    hereIsAnEvenLongerOtherFunctionNameThatWillNotFitOnPrevLine();

thisIsAVeryLongVariableName = 'expressionPartOne' + someMethodThatIsLong() +
    thisIsAnEvenLongerOtherFunctionNameThatCannotBeIndentedMore();

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();
}

2.5.6 空白行
使用空白行将逻辑上相关的代码分组。
例如:

doSomethingTo(x);
doSomethingElseTo(x);
andThen(x);

nowDoSomethingWith(y);

andNowWith(z);

2.5.7 二元和三元运算符
总是将运算符集中在一行,这样就不用考虑解析器隐含插入分号的问题。
否则换行和缩进就要遵守上面的规则。

var x = a ? b : c;  // 如果写得下,都写在一行

// 缩进4个字符位也可以
var y = a ?
    longButSimpleOperandB : longButSimpleOperandC;

// 或者缩进时对齐第一个操作数
var z = a ?
        moreComplicatedB :
        moreComplicatedC;

2.6 圆括号
仅在语法或语义需要时候使用。

永远不要为一元运算符例如delete、typeof、void使用圆括号。
也不要在关键字例如return、throw、case、in、new后面使用圆括号。

2.7 字符串
选择使用单引号「’」。
为了保持兼容,推荐选择「’」而不是「”」,在创建包含HTML的字符串时更容易:

var msg = 'This is some HTML';

2.8 可见性(私有域和受保护域)
推荐使用JSDoc的@private和@protected标签,以指示类、函数和属性的可见性。

在使用Closure Compiler编译时,参数「–jscomp_warning=visibility」可以让编译器检测到违反可见性时发出警告。

标记为@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的属性可被同一文件的任何代码访问,同时也可被派生子类的静态方法或实例方法所访问。

注意JavaScript的这些标签和C++/Java不同。
JavaScript的private和protected允许从同一文件中的任何地方访问,不仅仅是同一个类内。
同样,和C++不一样的是,private的属性不允许被子类复写。

// File 1.

/** @constructor */
  AA_PublicClass = function() {
};

/** @private */
AA_PublicClass.staticPrivateProp_ = 1;

/** @private */
AA_PublicClass.prototype.privateProp_ = 2;

/** @protected */
AA_PublicClass.staticProtectedProp = 31;

/** @protected */
AA_PublicClass.prototype.protectedProp = 4;

// 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;
};

注意在JavaScript中,没法分别一个类型(例如AA_PrivateClass_)和这个类型的构造函数。
也没有方法表明一个类型是公有的,而同时它的构造函数是私有的。
(因为给构造函数起个别名就可以轻易绕过编译器的安全性检查)

注:
JavaScript本身不支持私有、保护、公有机制,如果不是利用闭包来实现,类的成员都是直接可达的。
在注释中添加JSDoc中约定的标签后,一些编译器可以实现安全性检查,以使得代码更加安全稳定。
但事实上浏览器的JS解析器仍然会忽略所有注释。添加约束标签是为了让开发者遵守约定。
这种保护私有域的书写方式和闭包方式是完全不同的。

2.9 数据类型(JavaScript Types)
使用编译器建议的类型。
(待译)

2.10 注释
(待译)

2.11 内置类和枚举
类的内置类/枚举应该在同一个文件当中定义,并且是顶级成员。

2.12 编译
鼓励使用Closure Compiler来编译。

注:JavaScript的编译不同于C++/Java的编译。
这里的编译指的是使用所谓的编译工具检测拼写错误,语法错误(包括使用标签后的限制),优化代码结构,最后得到压缩的JS文件(仍然是可读文本)。

2.13 一些技巧

2.13.1 布尔表达式
在布尔表达式中,下面的值都是false

null
undefined
'' // (空字符串)
0  // (数字)

要注意的是,这些是真值

'0' // (字符串)
[] // (空数组)
{} // (空对象)

这意味着,如果你写了这么一句语句:

while (x != null) {

实际上可以用更短的代码来代替(同时,如果你不期望x是数字0,空字符串或false时也可以这样写):

while (x) {

同时,如果你希望判断一个字符串非null和非空,可以这么写:

if (y != null && y != '') {

但下面的写法更短更好:

if (y) {

注意:布尔表达式有许多看起来匪夷所思的地方,下面是一些例子:

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

2.13.2 三元条件运算符?:
或许你会这样写:

if (val != 0) {
  return foo();
} else {
  return bar();
}

其实可以用三元运算符?: :

return val ? foo() : bar();

在写包含HTML时候很有用

var html = '<input type="checkbox"' +
    (isChecked ? ' checked' : '') +
    (isEnabled ? '' : ' disabled') +
    ' name="foo">';

2.13.3 && 和 ||
这些运算符在仅有必要时才会计算第二个操作数(短路计算)
“||”有时候又被称为“默认运算子”,因为如其这样写:

/** @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]);

2.13.4 使用join()创建字符串
经常可以看到:

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中会运行得比较慢。
下面的写法更好:

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>';
}

你同样可以将数组当成StringBuilder来用,使用myArray.join(”)将它们转换为字符串。
注意:直接对数组赋值要比push()快,因此在需要时你应当直接赋值。

2.13.5 枚举DOM节点列表
节点列表经常被实现成带过滤器的节点枚举器。这意味着获取节点列表的某个属性比如length的时间复杂度是O(n),
枚举列表的时候每次都重新检查length,时间复杂度将会是O(n^2)。
时间复杂度O(n^2)的写法(paragraphs.length的时间复杂度是O(n))

var paragraphs = document.getElementsByTagName('p');
for (var i = 0; i < paragraphs.length; i++) {
  doSomething(paragraphs[i]);
}

下面的方法更好,时间复杂度O(n):

var paragraphs = document.getElementsByTagName('p');
for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) {
  doSomething(paragraph);
}

这会工作得很好,因为对于集合或数组而言,如果下标越界,返回的将是undefined值,赋值表达式的返回值将会被转换成false。
注:可以在循环前将paragraphs.length的值缓存,代价是多一次遍历(时间复杂度O(2n))

当你枚举子节点时,可以使用firstChild或者nextSibling属性:

var parentNode = document.getElementById('foo');
for (var child = parentNode.firstChild; child; child = child.nextSibling) {
  doSomething(child);
}

最后的话:
保持一致。
如果你在敲代码,花几分钟看看你身边的代码,确定它们的格式。如果他们在数学运算时使用了空格,那么你也应该这样。
如果他们的注释环绕着虚线的盒子,那么你的注释也应该这样。

编程规范意义在于写代码时我们拥有共同的词汇,因此人们能聚焦于你在说什么而不是你怎么说。
Google发布了全局的编程规范,因此人们知道了这些词汇。但是局部的规范同样很重要。如果你添加的代码和原风格迥异,
这会打乱阅读代码的人的节奏。避免这个。

2012年2月27日 | 归档于 程序
本文目前尚无任何评论.

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: