guild-book

编码风格

命名

通常,使用functionNamesLikeThisvariableNamesLikeThisClassNamesLikeThisEnumNamesLikeThismethodNamesLikeThisSYMBOLIC_CONSTANTS_LIKE_THIS

属性和方法

  • 文件或类中的私有属性,变量和方法名应该以下划线 "_" 开头。
  • 保护变量和方法名不需要下划线开头,和公共变量名一样。

更多有关privateprotected的信息见visibility。

方法和函数参数

可选参数以opt_开头。 函数的参数个数不固定时,应该添加最后一个参数 var_args 为参数的个数。你也可以不设置var_args而取代使用arguments。 可选和可变参数应该在@param标记中说明清楚。虽然这两个规定对编译器没有任何影响,但还是请尽量遵守。

Getters和Setters

EcmaScript 5中属性的Getterssetters并不鼓励使用。如果使用了,请不要在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()方法

应该总是成功调用且不要抛异常。

可自定义 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;
};

JavaScript类型

强烈建议使用编译器。

如果使用 JSDoc,那么尽量具体地,准确地根据它的规则来书写类型说明。支持的类型给予EcmaScript 4 spec

JavaScript 类型语言

ES4提案中包含指定的JavaScript类型的语言。我们在JsDoc中使用此语言表达函数参数和返回值。由于ES4提案的演变,这种语言发生了变化,编译器仍然支持旧语法类型,但是这些语法已过时。

表格参看:The JavaScript Type Language

JavaScript中的类型

表格参看:The JavaScript Type Language

明确类型

可能出现类型检查并不能准确判断表达式的类型的情况,可以在注释里添加类型标注,并在中括号内写出表达式的类型,如果有对该类型的注解就更好了。

/** @type {number} */ (x)

可空 vs. 可选参数和属性

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.
 */

JSDoc 缩进

如果你不得不换行块标签,那就应该缩进四个空格以保持注释内容的结构清晰。

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

JSDoc的HTML

就像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标签参考

表格参看:JSDoc Tag Reference

在第三方代码中, 你还会见到其他一些 JSDoc 标记. 这些标记在 JSDoc Toolkit Tag Reference 都有介绍到,但在 Google 的代码中,目前不推荐使用。你可以认为这些是将来会用到的 "保留" 名。它们包含:

  • @augments
  • @argument
  • @borrows
  • @class
  • @constant
  • @constructs
  • @default
  • @event
  • @example
  • @field
  • @function
  • @ignore
  • @inner
  • @link
  • @memberOf
  • @name
  • @namespace
  • @property
  • @public
  • @requires
  • @returns
  • @since
  • @static
  • @version

小提示和技巧

推荐使用

建议您去使用 JS 编译器,如 Closure Compiler.

True和False布尔表达式

以下的表达式都返回false:

  • null
  • undefined
  • '' 空字符串
  • 0 数字0

注意,以下的都返回true:

  • '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

条件(三元)操作符(?:)

下面这段代码可以被三元操作符所替换:

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