Google JavaScript规范

霍建柏
2023-12-01

一、JavaSrcipt语言规则

1、Variables 变量

所有变量都要使用var来声明。当你没有写 var, 变量就会暴露在全局上下文中, 这样很可能会和现有变量冲突.另外, 如果没有加上, 很难明确该变量的作用域是什么, 变量也很可能像在局部作用域中, 很轻易地泄漏到 Document 或者 Window 中,所以务必用 var 去声明变量.

2、Constants 常量

常量的形式如: NAMES_LIKE_THIS, 即使用大写字符, 并用下划线分隔.你也可用 @const 标记来指明它是一个常量.不要使用const 关键字, IE不支持这个关键字.对于基本类型的常量, 只需转换命名.

/**
 * The number of seconds in a minute.
 * @type {number}
 */
goog.example.SECONDS_IN_A_MINUTE = 60;

对于非基本类型, 使用 @const 标记.

/**
 * The number of seconds in each of the given units.
 * @type {Object.<number>}
 * @const
 */
goog.example.SECONDS_TABLE = {
  minute: 60,
  hour: 60 * 60,
  day: 60 * 60 * 24
}
 

这标记告诉编译器它是常量.

3、Semicolons 分号

总是使用分号来结束语句。如果仅依靠语句间的隐式分隔, 有时会很麻烦. 你自己更能清楚哪里是语句的起止, 而且有些情况下, 漏掉分号会很危险.

下面的例子会展示缺失分号会导致的危险

// 1.
MyClass.prototype.myMethod = function() {
  return 42;
}  // No semicolon here.
(function() {
  // Some initialization code wrapped in a function to create a scope for locals.
})();
var x = {
  'i': 1,
  'j': 2
}  // No semicolon here.
// 2.  Trying to do one thing on Internet Explorer and another on Firefox.
// I know you'd never write code like this, but throw me a bone.
[ffVersion, ieVersion][isIE]();
var THINGS_TO_EAT = [apples, oysters, sprayOnCheese]  // No semicolon here.
// 3. conditional execution a la bash
-1 == resultOfOperation() || die();

上面的语句会发生什么?

1 报 JavaScript 错误 – 例子1上的语句会解释成, 一个函数带一匿名函数作为参数而被调用, 返回42后, 又一次被”调用”, 这就导致了错误.

2 例子2中, 你很可能会在运行时遇到 ‘no such property in undefined’ 错误, 原因是代码试图这样 x[ffVersion][isIE]() 执行.

3 当 resultOfOperation() 返回非 NaN 时, 就会调用die, 其结果也会赋给 THINGS_TO_EAT.

Why?

JavaScript 的语句以分号作为结束符, 除非可以非常准确推断某结束位置才会省略分号. 上面的几个例子产出错误, 均是在语句中声明了函数/对象/数组直接量, 但闭括号(‘}’或’]’)并不足以表示该语句的结束. 在 JavaScript 中, 只有当语句后的下一个符号是后缀或括号运算符时, 才会认为该语句的结束.遗漏分号有时会出现很奇怪的结果, 所以确保语句以分号结束.

4、分号和function

分号应该包含在function表达式的末尾但是不应该出现在function声明的结尾

var foo = function() {
  return true;
};  // semicolon here.
function foo() {
  return true;
}  // no semicolon here.

5、Function Declarations Within Blocks块内函数声明

不要在块内声明一个函数,不要写成:

if (x) {
  function foo() {}
}

虽然很多 JS 引擎都支持块内声明函数, 但它不属于 ECMAScript 规范 (见 ECMA-262, 第13和14条).各个浏览器糟糕的实现相互不兼容, 有些也与 ECMAScript 草案相违背. ECMAScript 只允许在脚本的根语句或函数中声明函数.如果确实需要在块中定义函数, 建议使用函数表达式来初始化变量:

if (x) {
  var foo = function() {}
}

6、Exceptions 异常

推荐使用。你在写一个比较复杂的应用时, 不可能完全避免不会发生任何异常.

7、Custom exceptions 自定义异常

推荐使用。有时发生异常了, 但返回的错误信息比较奇怪, 也不易读. 虽然可以将含错误信息的引用对象或者可能产生错误的完整对象传递过来, 但这样做都不是很好, 最好还是自定义异常类, 其实这些基本上都是最原始的异常处理技巧.所以在适当的时候使用自定义异常.

8、Custom exceptions 嵌套函数

推荐使用。嵌套函数很有用, 比如,减少重复代码, 隐藏帮助函数, 等. 没什么其他需要注意的地方, 随意使用.

9、Standards features 标准特性

总是优于非标准特性.最大化可移植性和兼容性, 尽量使用标准方法而不是用非标准方法, (比如,优先用string.charAt(3) 而不用 string[3] )

10、Wrapper objects for primitive types 封装基本类型

不要使用。没有任何理由去封装基本类型, 另外还存在一些风险:

var x = new Boolean(false);
if (x) {
  alert('hi');  // Shows 'hi'.
}

除非明确用于类型转换, 其他情况请千万不要这样做!

var x = Boolean(0);
if (x) {
  alert('hi');  // This will never be alerted.
}
typeof Boolean(0) == 'boolean';
typeof new Boolean(0) == 'object';

有时用作 number, string 或 boolean时, 类型的转换会非常实用.

11、Multi-level prototype hierarchies 多级原型结构

不推荐使用。多级原型结构是指 JavaScript 中的继承关系. 当你自定义一个D类, 且把B类作为其原型, 那么这就获得了一个多级原型结构. 这些原型结构会变得越来越复杂!使用the Closure 库中的 goog.inherits() 或其他类似的用于继承的函数, 会是更好的选择.

function D() {
  goog.base(this)
}
goog.inherits(D, B);
D.prototype.method = function() {
  ...
};

12、Method and property definitions 方法定义

推荐使用如下的形式:

Foo.prototype.bar = function() {
  /* ... */
};

另一种推荐的写法是在构造函数中初始化

/** @constructor */
function Foo() {
  this.bar = value;
}

13、delete 属性删除

使用

Foo.prototype.dispose = function() {
  this.property_ = null;
 
};

不要使用:

Foo.prototype.dispose = function() {
  delete this.property_;
};

现在大部分JavaScript引擎中, 改变属性的数量比给属性重新赋值要慢得多.delete关键字应该避免使用除非必须要在Obejct迭代Keys时删除一个属性,或者需要改变if (key in obj) 的结果时.

14、Closures 闭包

可以, 但小心使用.闭包也许是 JS 中最有用的特性了.有一点需要牢记, 闭包保留了一个指向它封闭作用域的指针, 所以, 在给 DOM 元素附加闭包时, 很可能会产生循环引用, 进一步导致内存泄漏. 比如下面的代码:

function foo(element, a, b) {
  element.onclick = function() { /* uses a and b */ };
}

这里, 即使没有使用 element, 闭包也保留了element,a和b的引用, 由于 element 也保留了对闭包的引用, 这就产生了循环引用, 这就不能被 GC 回收.

这种情况下, 可将代码重构为:

function foo(element, a, b) {
  element.onclick = bar(a, b);
}
function bar(a, b) {
  return function() { /* uses a and b */ }
}

15、eval()

只用于解析序列化串和REPL(Read–eval–print loop)( “读取-求值-输出”循环)。eval() 会让程序执行的比较混乱, 当 eval() 里面包含用户输入的话就更加危险.可以用其他更佳的, 更清晰, 更安全的方式写你的代码, 所以一般情况下请不要使用 eval().

当碰到一些需要解析序列化串的情况下(如, 计算 RPC 响应), 使用JSON的 JSON.parse()来替代 eval().

我们来假设收到了服务器的返回结果类似:

{
  "name": "Alice",
  "id": 31502,
  "email": "looking_glass@example.com"
}
var userInfo = eval(feed);
var email = userInfo['email'];

可以使用JSON.parse()来替代eval()

var userInfo = JSON.parse(feed);
var email = userInfo['email'];

16、with() {}

不要使用。使用 with 让你的代码在语义上变得不清晰. 因为 with 的对象, 可能会与局部变量产生冲突, 从而改变你程序原本的用义.下面的代码会发生什么?

with (foo) {
  var x = 3;
  return x;
}

答案: 任何时候. 局部变量 x 可能被 foo 的属性覆盖, 当它定义一个 setter 时, 在赋值 3 后会执行很多其他代码.所以不要使用 with 语句.

17、this

仅在对象构造器, 方法, 闭包中使用.this 的语义很特别. 有时它引用一个全局对象(大多数情况下), 调用者的作用域(使用eval时), DOM 树中的节点(添加事件处理函数时), 新创建的对象(使用一个构造器), 或者其他对象(如果函数被call() 或 apply()).使用时很容易出错, 所以只有在下面两个情况时才能使用:

    在构造器中

    对象的方法(包括创建的闭包)中

18、for-in loop

只用于 object/map/hash 的遍历。对 Array 用 for-in 循环有时会出错. 因为它并不是从 0 到length - 1 进行遍历, 而是所有出现在对象及其原型链的键值. 下面就是一些失败的使用案例:

function printArray(arr) {
  for (var key in arr) {
    print(arr[key]);
  }
}
printArray([0,1,2,3]);  // This works.
var a = new Array(10);
printArray(a);  // This is wrong.
a = document.getElementsByTagName('*');
printArray(a);  // This is wrong.
a = [0,1,2,3];
a.buhu = 'wine';
printArray(a);  // This is wrong again.
a = new Array;
a[3] = 3;
printArray(a);  // This is wrong again.

而遍历数组通常用最普通的 for 循环.

function printArray(arr) {
  var l = arr.length;
  for (var i = 0; i < l; i++) {
    print(arr[i]);
  }
}

19、Associative Arrays 关联数组

永远不要使用 Array 作为 map/hash/associative 数组.关联数组是不允许的…或者更准确的说不允许使用非数字索引数组。如果你需要使用 map/hash 请使用object来替代Array,因为实际上你需要的是对象的特征而不是数组。

Array仅仅是Object的扩展 (类似于其他 JS 中的对象, 就像Date, RegExp 和 String).

20、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.';

在编译时, 不能忽略行起始位置的空白字符; “/” 后的空白字符会产生奇怪的错误; 虽然大多数脚本引擎支持这种写法, 但它不是 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.';

21、Array and Object literals

使用 Array 和 Object 语法, 而不使用Array 和 Object 构造器.使用 Array 构造器很容易因为传参不恰当导致错误.

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

如果传入一个参数而不是2个参数, 数组的长度很有可能就不是你期望的数值了.为了避免这些歧义,我们应该使用更易读的直接量来声明.

var a = [x1, x2, x3];
var a2 = [x1, x2];
var a3 = [x1];
var a4 = [];

虽然 Object 构造器没有上述类似的问题, 但鉴于可读性和一致性考虑, 最好还是在字面上更清晰地指明.

var o = new Object();
var o2 = new Object();
o2.a = 0;
o2.b = 1;
o2.c = 2;
o2['strange key'] = 3;

应该写成:

var o = {};
var o2 = {
  a: 0,
  b: 1,
  c: 2,
  'strange key': 3
};

22、Modifying prototypes of builtin objects 修改内置对象的原型

千万不要修改内置对象, 如 Object.prototype 和Array.prototype 的原型. 而修改内置对象, 如 Function.prototype 的原型, 虽然少危险些, 但仍会导致调试时的诡异现象.所以也要避免修改其原型.

23、Internet Explorer's Conditional Comments  IE下的条件注释

不要使用。不要这样子写:

var f = function () {
    /*@cc_on if (@_jscript) { return 2* @*/  3; /*@ } @*/
};

条件注释妨碍自动化工具的执行, 因为在运行时, 它们会改变 JavaScript 语法树.

 

二、JavaSrcipt编码风格

1、Naming命名

通常, 使用

functionNamesLikeThis( 函数命名 ),

variableNamesLikeThis( 变量命名 ),

ClassNamesLikeThis( 类名命名 ),

EnumNamesLikeThis( 枚举命名 ),

methodNamesLikeThis( 方法命名 ),

SYMBOLIC_CONSTANTS_LIKE_THIS( 常量命名 ).

foo.namespaceNamesLikeThis.bar( 命名空间命名 ),

filenameslikethis.js( 文件命名 ).

2、属性和方法

私有属性,变量和方法名应该以下划线 “_” 开头.

保护属性, 变量和方法名不需要下划线开头, 和公共变量名一样.

3、方法和函数参数

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

4、Getters 和 Setters

EcmaScript 5 中不推荐使用属性的 getters 和 setters,但是如果要使用getters, 千万别让 getters 方法改变当前对象的各属性状态

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

5、Accessor functions 函数的访问器

函数对象中的 Getters 和 Setters 也不是必须的。但如果你用了,你最好以类似 getFoo() 和 setFoo(value).的形式来命名。(如果返回布尔型的值,可以用 isFoo() 或者其他更自然的名字)

6、命名空间

JavaScript 不支持包和命名空间.不容易发现和调试全局命名的冲突, 多个系统集成时还可能因为命名冲突导致很严重的问题.为了提高 JavaScript 代码复用率, 我们遵循下面的约定以避免冲突.

  为全局代码使用命名空间。在全局作用域上, 使用一个唯一的, 与工程/库相关的名字作为前缀标识. 比如, 你的工程是 “Project Sloth”, 那么命名空间前缀可取为 sloth.*.

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

许多 JavaScript 库, 包括 the Closure Library and 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);
};

不要在全局范围内创建别名, 而仅在函数块作用域中使用.

7、文件名

文件名应该全部字母小写,以避免在某些区分大小写的系统平台产生文件名混淆。文件名应该以 .js 结束,且不要包含除 - 和 _ 外的标点符号(使用 - 优于 _).。

8、Custom toString() methods 自定义toString()方法

应该总是成功调用且不要抛异常.可自定义 toString() 方法, 但确保你的实现方法满足: (1) 总是成功 (2) 没有其他负面影响.如果不满足这两个条件, 那么可能会导致严重的问题, 比如, 如果 toString() 调用了包含 assert 的函数,assert 输出导致失败的对象, 这在 toString() 也会被调用.

9、Deferred initialization 延迟初始化

可以使用。没必要在每次声明变量时就将其初始化.

10、Explicit scope 明确作用域

任何时候都需要。任何时候都要明确作用域 – 提高可移植性和清晰度. 例如, 不要依赖于作用域链中的 window 对象.可能在其他应用中, 你函数中的 window 不是指之前的那个窗口对象.

11、Code formatting 代码格式化

主要依照C++ formatting rules , 针对 JavaScript, 还有下面一些附加说明.

大括号:分号会被隐式插入到代码中, 所以你务必在同一行上插入大括号. 例如:

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

12、数组和对象的初始化

如果初始值不是很长, 就保持写在单行上:

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

13、函数参数

尽量让函数参数在同一行上.如果一行超过 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.  Visually groups and
// emphasizes each individual argument.
function bar(veryDescriptiveArgumentNumberOne,
             veryDescriptiveArgumentTwo,
             tableModelEventHandlerProxy,
             artichokeDescriptorAdapterIterator) {
  // ...
}

如果调用的函数本身就已经缩进了,可以针对于当前被调用的函数(或之前的原始函数)再向前缩进4个空格。以下这样的风格也可以接受:

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

14、传递匿名函数

如果调用方法传参内有匿名函数的声明,函数体应相对于该调用方法(或变量名)缩进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;
    });

15、更多的缩进

事实上, 除了初始化数组和对象, 和传递匿名函数外, 所有被拆开的多行文本要么选择与之前的表达式左对齐, 要么以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();
}
 

16、空行

使用空行来划分一组逻辑上相关联的代码片段.

doSomethingTo(x);
 
doSomethingElseTo(x);
 
andThen(x);
 
nowDoSomethingWith(y);
 
andNowWith(z);

17、二元和三元操作符

操作符始终放在前行, 这样就不用顾虑分号的隐式插入问题. 如果一行实在放不下, 还是按照上述的缩进风格来换行.

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

18、Parentheses 括号

只在需要的时候使用,不要滥用括号, 只在必要的时候使用它.对于一元操作符(如delete, typeof 和 void ),或是在某些关键词(如 return, throw, case, new )之后, 不要使用括号.

19、Strings 字符串

单引号 (‘) 优于双引号 (“).特别是当创建一个HTML代码的字符串时候.

var msg = 'This is some HTML';

20、Visibility (private and protected fields) 可见性(私有域和保护域)

推荐使用 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++中,私有属性是不能被子类重载的。

// 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,那就尽量按照它的规范去写,目前支持 JS2  和 JS1.x 两种规范。

四、JavaScript类型语言

这个JS2提议为JavaScript类型定制了一种语言。当我们在JsDoc中注释函数参数和返回值类型的时候会用到它。

JSDoc 的类型语言, 按照 JS2 规范, 也进行了适当改变, 但编译器仍然支持旧语法.,只是不建议使用而已。

五、JavaScript的对象类型

类型示例

值示例

描述

number

1

1.0

-5

1e5

Math.PI

数字,整型,浮点型,科学计算型,数字常量

Number

new Number(true)

Number 对象

string

'Hello'

"World"

String(42)

字符串值

String

new String('Hello')

new String(42)

String 对象

boolean

true

false

Boolean(0)

布尔值

Boolean

new Boolean(true)

Boolean 对象

RegExp

new RegExp('hello')

/world/g

正则表达式

Date

new Date

new Date()

日期

null

null

 

undefined

undefined

 

void

function f() {

  return;

}

返回undefined

Array

['foo', 0.3, null]

[]

混型数组

Array.<number>

[11, 22, 33]

数字型数组

Array.<Array.<string>>

[['one', 'two', 'three'], ['foo', 'bar']]

字符串型数组嵌套数组

Object

{}

{foo: 'abc', bar: 123, baz: null}

对象表达式

Object.<string>

{'foo': 'bar'}

一个对象,键和值都是字符串。

Object.<number, string>

var obj = {};

obj[1] = 'bar';

一个对象,键是数字,值是字符串。

注意,在JavaScript中,对象的键总是会被隐式的转换成字符串,所以obj['1'] == obj[1]。所以键总是以一个字符串的形式在for...in循环中,但如果键在索引时,编译器会验证类型。

Function

function(x, y) {

  return x * y;

}

Function 对象

function(number, number): number

function(x, y) {

  return x * y;

}

函数值

SomeClass

/** @constructor */

function SomeClass() {}

new SomeClass();

伪类 - 类实例

SomeInterface

/** @interface */

function SomeInterface() {}

SomeInterface.prototype.draw = function() {};

原型类

project.MyClass

/** @constructor */

project.MyClass = function () {}

new project.MyClass()

构造内部类 - 内部类实例

project.MyEnum

/** @enum {string} */

project.MyEnum = {

  BLUE: '#0000dd',

  RED: '#dd0000'

};

枚举

Element

document.createElement('div')

在DOM中创建一个元素节点

Node

document.body.firstChild

返回在DOM中的节点body的第一个子节点

HTMLInputElement

htmlDocument.getElementsByTagName('input')[0]

查找一组特定标签类型的DOM元素,返回第一个



1、明确类型  

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

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

2、可空与可选的参数和属性

因为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也没关系,但读起来前边的更好。

最后,注意可空和可选都是正交属性,下面的四个声明都是不同的:

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

3、类型定义

类型定义也可以复杂化,一个函数可以接受元素节点的内容:

/**
 * @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;
/**
 * @param {string} tagName
 * @param {goog.ElementContent} contents
 * @return {!Element}
 */
goog.createElement = function(tagName, contents) {
...
};

4、模板属性

编译器已经有限的支持模板类型。它只能推断的类型,这在一个匿名函数字面量从类型的这个论点,是否这个论点是缺失的。(以下为原文 不太明白意思)

The compiler has limited support for template types. It can only infer the type of this inside an anonymous function literal from the type of the this argument and whether the this argument is missing.
/**
 * @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; });

5、Comments 注释

推荐使用JSDoc,我们鼓励依照 C++ style for comments 的风格。所有的文件,类,方法和属性都应该以 JSDoc 风格来进行注释。包括属性,方法,该方法参数和方法返回值,除非从方法,属性或参数的名称中就可以明确的理解其意思.

行内注释使用 // 。

避免出现句式片段,如果是英文首字母大写,记得加标点符号。

6、注释语法

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

7、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 。

没有必要缩进 @desc

尽管不推荐,但缩进至与上排注释同列也是可以接受的。

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

8、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 items received

所以,可以用这种方式:

/**
 * Computes weight based on three factors:
 * <ul>
 * <li>items sent
 * <li>items received
 * <li>last timestamp
 * </ul>
 */

关于写注释更多信息可以去看一下 JavaDoc 风格指南。

9、Compiling压缩编译(JS最小化)

使用被编译压缩后的JS代码可以更快下载和使用,推荐 Closure Compiler。

10、Tips and Tricks小技巧

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


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

三元操作符用于替代下面的代码:

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

你可以写成:

return val ? foo() : bar();

在生成 HTML 代码时也是很有用的:

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

12、&& 和 ||

二元布尔操作符可以根据前面的代码判断后面的代码是否执行,也就是说只有在必要的时候才会执行后面的代码。.

“||” 被称作为 ‘default’ 操作符, 因为可以这样:

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

13、使用 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>';
}

你也可以是用数组作为字符串构造器, 然后通过 myArray.join('') 转换成字符串. 不过由于赋值操作快于数组的push(), 所以尽量使用赋值操作.

14、遍历 Node List

Node lists 是通过给节点迭代器加一个过滤器来实现的.这表示获取他的属性, 如 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);
}

15、Parting Words 最后的话

坚持一致原则。当你在编辑代码之前, 先花一些时间查看一下现有代码的风格. 如果他们给算术运算符添加了空格, 你也应该添加.如果他们的注释使用一个个星号盒子, 那么也请你使用这种方式.代码风格中一个关键点是整理一份常用词汇表, 开发者认同它并且遵循, 这样在代码中就能统一表述.我们在这提出了一些全局上的风格规则, 但也要考虑自身情况形成自己的代码风格.但如果你添加的代码和现有的代码有很大的区别, 这就让阅读者感到很不和谐.所以, 避免这种情况的发生

 类似资料: