最前端

我的工作,我的生活,点点滴滴...

关于‘ Script ’ 目录的归档

Code Conventions for the JavaScript Programming Language

crockford

再转载一篇来自Crockford的JS规范,原文是Code Conventions for the JavaScript Programming Language,然后找个时间,把它和Google JavaScript Style Guide统一翻译一下。

发现已经有人在译言上已经翻译过了,原文是JavaScript程序编码规范哈哈不错,转载过来,不用翻译了。7.2.2011更新

这是一套适用于JavaScript程序的编码规范。它基于SunJava程序编码规范,但进行了大幅度的修改, 因为 JavaScript不是Java

软件的长期价值直接源于其编码质量。在它的整个生命周期里,一个程序可能会被许多人阅读或修改。如果一个程序可以清晰的展现出它的结构和特征,那就能减少在以后对其进行修改时出错的可能性。

编程规范可以帮助程序员们增加程序的健壮性。

所有的JavaScript代码都是暴露给公众的。所以我们更应该保证其质量。

保持整洁很重要。

JavaScript文件

JavaScript程序应独立保存在后缀名为.js 的文件中。

JavaScript代码不应该被包含在HTML文件中,除非这是段特定只属于此部分的代码。在HTML中的JavaScript代码会明显增加文件大小,而且也不能对其进行缓存和压缩。

<script src=filename.js> 应尽量放到body的后面。这样可以减少因为载入script而造成其他页面内容载入也被延迟的问题。也没有必要使用language 或者 type 属性。MIME类型是由服务器而非scripttag来决定的。

缩进

缩进的单位为四个空格。避免使用Tab键来缩进(即使现在已经是21世纪了),也始终没有个统一的Tab长短标准。虽然使用空格会增加文件的大小,但在局域网中几乎可以忽略,且在 最小化过程中也可被消除掉。

每行长度

避免每行超过80个字符。当一条语句一行写不下时,请考虑折行。在运算符号,最好是逗号后换行。在运算符后换行可以减少因为复制粘贴产生的错误被分号掩盖的几率。下一行应该缩进8个空格。

注释

不要吝啬注释。给以后需要理解你的代码的人们(或许就是你自己)留下信息是非常有用的。注释应该和它们所注释的代码一样是书写良好且清晰明了。偶尔的小幽默就更不错了。记得要避免冗长或者情绪化。

及时地更新注释也很重要。错误的注释会让程序更加难以阅读和理解。

让注释有意义。重点在解释那些不容易立即明白的逻辑上。不要把读者的时间浪费在阅读类似于:

    i = 0; // Set i to zero.

使用单行注释。块注释用于注释正式文档和无用代码。

变量声明

所有的变量必须在使用前进行声明。JavaScript并不强制必须这么做,但是这么做可以让程序易于阅读,且也容易发现那些没声明的变量(它们会被编译成全局变量)。

var 语句放在函数的首部。

最好把每个变量的声明语句单独放到一行,并加上注释说明。所有变量按照字母排序。

    var currentEntry; // currently selected table entry
    var level;        // indentation level
    var size;         // size of table

JavaScript没有块范围,所以在块里面定义变量很容易引起C/C++/Java程序员们的误解。在函数的首部定义所有的变量。

尽量减少全局变量的使用。不要让局部变量覆盖全局变量。

函数声明

所有的函数在使用前进行声明。 内函数的声明跟在var语句的后面。这样可以帮助判断哪些变量是在函数范围内的。

函数名与((左括号)之间不应该有空格。)(右括号)与 开始程序体的{(左大括号)之间应插入一个空格。函数程序体应缩进四个空格。}(右大括号)与声明函数的那一行代码头部对齐。

    function outer(c, d) {
        var e = c * d;

        function inner(a, b) {
            return (e * a) + b;
        }

        return inner(0, 1);
    }

下面这种书写方式可以在JavaScript中正常使用,因为在JavaScript中,函数和对象的声明可以放到任何表达式允许的地方。且它让内联函数和混合结构具有最好的可读性。

    function getElementsByClassName(className) {
        var results = [];
        walkTheDOM(document.body, function (node) {
            var a;                  // array of class names
            var c = node.className; // the node's classname
            var i;                  // loop counter

// If the node has a class name, then split it into a list of simple names.
// If any of them match the requested name, then append the node to the set of results.

            if (c) {
                a = c.split(' ');
                for (i = 0; i < a.length; i += 1) {
                    if (a[i] === className) {
                        results.push(node);
                        break;
                    }
                }
            }
        });
        return results;
    }

如果函数是匿名函数,则在function((左括号)之间应有一个空格。如果省略了空格,否则会让人感觉函数名叫作 function

    div.onclick = function (e) {
        return false;
    };

    that = {
        method: function () {
            return this.datum;
        },
        datum: 0
    };

尽量不使用全局函数。

当函数在声明时立即调用,应该用括号把整个调用语句括起来,这样可以清楚表明这个变量的值是这个函数执行后返回的结果,而不是这个函数本身。
var keys = [], values = [];

return {
get: function (key) {
var at = keys.indexOf(key);
if (at >= 0) {
return values[at];
}
},
set: function (key, value) {
var at = keys.indexOf(key);
if (at < 0) {
at = keys.length;
}
keys[at] = key;
values[at] = value;
},
remove: function (key) {
var at = keys.indexOf(key);
if (at >= 0) {
keys.splice(at, 1);
values.splice(at, 1);
}
}
};
}());

命名

变量名应由26个大小写字母(A..Z,a..z),10个数字(0..9),和_(下划线)组成。避免使用国际化字符(如中文),因为它们不是在任何地方都可以被方便的阅读和理解。不要在命名中使用$(美元符号)或者(反斜杠)

不要把_(下划线)作为变量名的第一个字符。它有时用来表示私有变量,但实际上JavaScript并没提供私有变量的功能。如果私有变量很重要, 那么使用私有成员的形式。应避免使用这种容易让人误解的命名习惯。

必须与new共同使用的构造函数名应以大写字母开头。当new被省略时JavaScript不会有任何编译错误或运行错误抛出。忘记加new时会让不好的事情发生(比如被当成一般的函数),所以大写构造函数名是我们来尽量避免这种情况发生的唯一办法。

全局变量应该全部大写。(JavaScript没有宏或者常量,所以不会因此造成误会)

语句

简单语句

每一行最多只包含一条语句。把;(分号)放到每条简单语句的结尾处。注意一个函数赋值或对象赋值语句也是赋值语句,应该以分号结尾。

JavaScript可以把任何表达式当作一条语句。这很容易隐藏一些错误,特别是误加分号的错误。只有在赋值和调用时,表达式才应被当作一条单独的语句。

复合语句

复合语句是被包含在{ }(大括号)的语句序列。

  • 被括起的语句必须多缩进四个空格。
  • {(左大括号)应在复合语句其实行的结尾处。
  • }(右大括号)应与{(左大括号)的那一行的开头对齐
  • 大括号应该在所有复合语句中使用,即使只有一条语句,当它们是控制结构的一部分时, 比如一个if或者for语句。这样做可以避免以后添加语句时造成的错误。

标示

语句标示是可选的,只有以下语句必须被标示:while, do,for,switch

return 语句

一条有返回值的return语句不要使用( )(括号)来括住返回值。如果返回表达式,则表达式应与return 关键字在同一行,以避免误加分号错误。

if语句

if语句应如以下格式:

    if (condition)
{
        
statements
    }
    
    if (
condition) {
        
statements
    } else {
        
statements
    }
    
    if (
condition) {
        
statements
    } else if (
condition)
{
        
statements
    } else {
        
statements
    }

for语句

for语句应如以下格式:

    for (initialization;
condition; update) {
        
statements
    }

    for (variable in object)
{
        if (
filter) {
            
statements
        }

    }

第一种形式的循环用于已经知道相关参数的数组循环

第二种形式应用于对象中。object原型中的成员将会被包含在迭代器中。通过预先定义hasOwnProperty方法来区分真正的object成员是个不错方法:

    for (variable
in
object) {
        if
(object.hasOwnProperty(variable))
{
            
statements
        }
    }

while语句

while 语句应如以下格式:

    while (condition)
{
        
statements
    }

do语句

do语句应如以下格式:

    do {
        
statements
    } while (
condition);

不像别的复合语句,do语句总是以;(分号)结尾。

switch语句应如以下格式:

switch语句应如以下格式:

    switch (expression) {
    case
expression:
        
statements
    default:
        
statements
    }

每个 caseswitch对齐。这可避免过分缩进。

每一组statements(除了default应以 break,return,或者throw结尾。不要让它顺次往下执行。

try Statement

try语句应如以下格式:

    try {
        statements
    } catch (variable) {
        statements
    }

    try {
        statements
    } catch (variable) {
        statements
   } finally {
        statements
    }

continue语句

避免使用continue语句。它很容易使得程序的逻辑过程晦涩难懂。

with语句

不要使用with语句。

空白

用空行来将逻辑相关的代码块分割开可以提高程序的可读性。

空格应在以下情况时使用:

  • 跟在((左括号)后面的关键字应被一个空格隔开。
        while (true) {
  • 函数参数与((左括号)之间不应该有空格。这能帮助区分关键字和函数调用。
  • 所有的二元操作符,除了.(点)((左括号)[(左方括号)应用空格将其与操作数隔开。
  • 一元操作符与其操作数之间不应有空格,除非操作符是个单词,比如typeof
  • 每个在控制部分,比如for 语句中的; (分号)后须跟一个空格。
  • 每个,(逗号)后应跟一个空格。

另外的建议

{}[]

使用{}代替new Object()。使用[]代替new Array()

当成员名是一组有序的数字时使用数组来保存数据。当成员名是无规律的字符串或其他时使用对象来保存数据。

,(逗号)操作符

避免使用逗号操作符,除非在特定的for 语句的控制部分。(这不包括那些被用在对象定义,数组定义,var语句,和参数列表中的逗号分隔符。)

作用域

在JavaScript中块没有域。只有函数有域。不要使用块,除非在复合语句中。

赋值表达式

避免在if和while语句的条件部分进行赋值。

Is

    if (a = b) {

是一条正确语句?或者

    if (a == b) {

才是对的?避免这种不容易判断对错的结构。

===!==操作符。

使用===!==操作符会相对好点。==!=操作符会进行类型强制转换。 特别是, 不要将==用于与错值比较( false,null,undefined,“”,0,NaN)。

令人迷惑的加号和减号

小心在+后紧跟+++。这种形式很容易仍人迷惑。应插入括号以便于理解。

    total = subtotal + +myInput.value;

最好能写成

    total = subtotal + (+myInput.value);

这样+ +不会被误认为是++

eval 是恶魔

eval是JavaScript中最容易被滥用的方法。避免使用它。

eval有别名。不要使用Function构造器。不要给setTimeout或者setInterval传递字符串参数。

Google JavaScript Style Guide

javascript guide

转载一下Google JavaScript Style Guide,文章不但指出每条规范,还解释了为什么这样写的原因,同时给出了对与错的实例,写得非常的详细,很值参考,正在考虑应该不应该翻译一下,然后借鉴到项目团队中去,站在巨人的肩膀上会看得更远。

修订版: 2.9

Aaron Whyte
Bob Jervis
Dan Pupius
Eric Arvidsson
Fritz Schneider
Robby Walker

每个条目都有概述信息, 点击
查看详细的内容.
你也可以点击下面的按钮


展开全部

重要注意事项

显示被隐藏的内容


link

这份指南中, 可以点击旁边的按钮来显示更多的细节.

Hooray! 这里是更多详细的内容, 你也可以点击最上面的”显示/隐藏全部按钮”来切换显示更多内容.

背景

JavaScript 是一种客户端脚本语言, Google 的许多开源工程中都有用到它. 这份指南列出了编写 JavaScript 时需要遵守的规则.

JavaScript 语言规范

变量


link

声明变量必须加上 var 关键字.

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

常量


link

常量的形式如: NAMES_LIKE_THIS, 即使用大写字符, 并用下划线分隔.
你也可用 @const 标记来指明它是一个常量.
但请永远不要使用 const 关键词.

Decision:

对于基本类型的常量, 只需转换命名.

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

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

至于关键词 const, 因为 IE 不能识别, 所以不要使用.

分号


link

总是使用分号.

如果仅依靠语句间的隐式分隔, 有时会很麻烦. 你自己更能清楚哪里是语句的起止.

而且有些情况下, 漏掉分号会很危险:

// 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.
[normalVersion, ffVersion][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.

为什么?

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

遗漏分号有时会出现很奇怪的结果, 所以确保语句以分号结束.

嵌套函数


link

可以使用

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

块内函数声明


link

不要在块内声明一个函数

不要写成:

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

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

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

异常


link

可以

你在写一个比较复杂的应用时, 不可能完全避免不会发生任何异常.
大胆去用吧.

自定义异常


link

可以

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

标准特性


link

总是优于非标准特性.

最大化可移植性和兼容性, 尽量使用标准方法而不是用非标准方法, (比如,
优先用string.charAt(3) 而不用 string[3] ,
通过 DOM 原生函数访问元素, 而不是使用应用封装好的快速接口.

封装基本类型


link

不要

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

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, stringboolean时, 类型的转换会非常实用.

多级原型结构


link

不是首选

多级原型结构是指 JavaScript 中的继承关系. 当你自定义一个D类, 且把B类作为其原型, 那么这就获得了一个多级原型结构. 这些原型结构会变得越来越复杂!

使用

the Closure 库
中的 goog.inherits() 或其他类似的用于继承的函数, 会是更好的选择.

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

D.prototype.method = function() {
  ...
};

方法定义


link

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

有很多方法可以给构造器添加方法或成员, 我们更倾向于使用如下的形式:

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

闭包


link

可以, 但小心使用.

闭包也许是 JS 中最有用的特性了.
有一份比较好的介绍闭包原理的文档.

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

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

这里, 即使没有使用 element, 闭包也保留了 element,
ab 的引用, . 由于 element 也保留了对闭包的引用, 这就产生了循环引用, 这就不能被 GC 回收.
这种情况下, 可将代码重构为:

function foo(element, a, b) {
  element.onclick = bar(a, b);
}

function bar(a, b) {
  return function() { /* uses a and b */ }
}

eval()


link

只用于解析序列化串 (如: 解析 RPC 响应)

eval() 会让程序执行的比较混乱, 当 eval() 里面包含用户输入的话就更加危险.
可以用其他更佳的, 更清晰, 更安全的方式写你的代码, 所以一般情况下请不要使用 eval().
当碰到一些需要解析序列化串的情况下(如, 计算 RPC 响应), 使用 eval 很容易实现.

解析序列化串是指将字节流转换成内存中的数据结构. 比如, 你可能会将一个对象输出成文件形式:

users = [
  {
    name: 'Eric',
    id: 37824,
    email: 'jellyvore@myway.com'
  },
  {
    name: 'xtof',
    id: 31337,
    email: 'b4d455h4x0r@google.com'
  },
  ...
];

很简单地调用 eval 后, 把表示成文件的数据读取回内存中.

类似的, eval() 对 RPC 响应值进行解码. 例如, 你在使用 XMLHttpRequest
发出一个 RPC 请求后, 通过 eval () 将服务端的响应文本转成 JavaScript 对象:

var userOnline = false;
var user = 'nusrat';
var xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', 'http://chat.google.com/isUserOnline?user=' + user, false);
xmlhttp.send('');
// Server returns:
// userOnline = true;
if (xmlhttp.status == 200) {
  eval(xmlhttp.responseText);
}
// userOnline is now true.

with() {}


link

不要使用

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

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

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

this


link

仅在对象构造器, 方法, 闭包中使用.

this 的语义很特别. 有时它引用一个全局对象(大多数情况下), 调用者的作用域(使用
eval时), DOM 树中的节点(添加事件处理函数时), 新创建的对象(使用一个构造器), 或者其他对象(如果函数被
call()apply()).

使用时很容易出错, 所以只有在下面两个情况时才能使用:

  • 在构造器中
  • 对象的方法(包括创建的闭包)中

for-in 循环


link

只用于 object/map/hash 的遍历

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

关联数组


link

永远不要使用 Array 作为 map/hash/associative 数组.

数组中不允许使用非整型作为索引值, 所以也就不允许用关联数组. 而取代它使用 Object 来表示 map/hash 对象.
Array 仅仅是扩展自 Object (类似于其他 JS 中的对象, 就像
Date, RegExpString)一样来使用.

多行字符串


link

不要使用

不要这样写长字符串:

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 的标准规范.

Array 和 Object 直接量


link

使用

使用 ArrayObject 语法, 而不使用
ArrayObject 构造器.

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

修改内置对象的原型


link

不要

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

IE下的条件注释


link

不要使用

不要这样子写:

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

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

JavaScript 编码风格

命名


link

通常, 使用 functionNamesLikeThis,
variableNamesLikeThis, ClassNamesLikeThis,
EnumNamesLikeThis, methodNamesLikeThis,
SYMBOLIC_CONSTANTS_LIKE_THIS.

展开见细节.

属性和方法

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

更多有关 私有保护的信息见,
visibility.

方法和函数参数

可选参数以 opt_ 开头.

函数的参数个数不固定时, 应该添加最后一个参数 var_args 为参数的个数. 你也可以不设置
var_args而取代使用 arguments.

可选和可变参数应该在
@param 标记中说明清楚. 虽然这两个规定对编译器没有任何影响, 但还是请尽量遵守

Getters 和 Setters

Getters 和 setters 并不是必要的. 但只要使用它们了, 就请将 getters 命名成 getFoo() 形式,
将 setters 命名成 setFoo(value) 形式. (对于布尔类型的 getters, 使用 isFoo() 也可.)

命名空间

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

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

文件名

文件名应该使用小写字符, 以避免在有些系统平台上不识别大小写的命名方式. 文件名以.js结尾,
不要包含除 -_ 外的标点符号(使用 - 优于 _).

自定义 toString() 方法


link

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

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

延迟初始化


link

可以

没必要在每次声明变量时就将其初始化.

明确作用域


link

任何时候都需要

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

代码格式化


link

展开见详细描述.

主要依照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" <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
};

函数参数

尽量让函数参数在同一行上.
如果一行超过 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) {
  // ...
}

传递匿名函数

如果参数中有匿名函数, 函数体从调用该函数的左边开始缩进2个空格, 而不是从 function 这个关键字开始. 这让匿名函数更加易读
(不要增加很多没必要的缩进让函数体显示在屏幕的右侧).

var names = items.map(function(item) {
                        return item.name;
                      });

prefix.something.reallyLongFunctionName('whatever', function(a1, a2) {
  if (a1.equals(a2)) {
    someOtherLongFunctionName(a1);
  } else {
    andNowForSomethingCompletelyDifferent(a2.parrot);
  }
});

更多的缩进

事实上, 除了

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

空行

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

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;

括号


link

只在需要的时候使用

不要滥用括号, 只在必要的时候使用它.

对于一元操作符(如delete, typeofvoid ),
或是在某些关键词(如 return, throw, case, new )之后, 不要使用括号.

字符串


link

使用 ‘ 优于 “

单引号 (‘) 优于双引号 (“).
当你创建一个包含 HTML 代码的字符串时就知道它的好处了.

var msg = 'This is some HTML';

可见性 (私有域和保护域)


link

推荐使用 JSDoc 中的两个标记: @private
@protected

JSDoc 的两个标记 @private
@protected 用来指明类, 函数, 属性的可见性域.

标记为 @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 */
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 类型


link

强烈建议你去使用编译器.

如果使用 JSDoc, 那么尽量具体地, 准确地根据它的规则来书写类型说明. 目前支持两种

JS2

和 JS1.x 类型规范.

JavaScript 类型语言

JS2 提议中包含了一种描述 JavaScript 类型的规范语法, 这里我们在 JSDoc 中采用其来描述函数参数和返回值的类型.

JSDoc 的类型语言, 按照 JS2 规范, 也进行了适当改变, 但编译器仍然支持旧语法.

名称 语法 描述 弃用语法
普通类型 {boolean}, {Window},
{goog.ui.Menu}
普通类型的描述方法.
复杂类型 {Array.<string>}
字符串数组.

{Object.<string, number>}

键为字符串, 值为整数的对象类型.

参数化类型, 即指定了该类型中包含的一系列”类型参数”. 类似于 Java 中的泛型.
联合类型 {(number|boolean)}
一个整数或者布尔值.
表示其值可能是 A 类型, 也可能是 B 类型 {(number,boolean)},
{number|boolean},
{(number||boolean)}
记录类型 {{myNum: number, myObject}}

由现有类型组成的类型.

表示包含指定成员及类型的值. 这个例子中, myNumnumber 类型, myObject 为任意类型.

注意大括号为类型语法的一部分. 比如, Array.<{length}>,
表示一具有 length 属性的 Array 对象.

可为空类型 {?number}
一个整型数或者为 NULL
表示一个值可能是 A 类型或者 null.
默认, 每个对象都是可为空的. 注意: 函数类型不可为空.
{number?}
非空类型 {!Object}
一个对象, 但绝不会是 null 值.
说明一个值是类型 A 且肯定不是 null.
默认情况下, 所有值类型 (boolean, number, string, 和 undefined) 不可为空.
{Object!}
函数类型 {function(string, boolean)}
具有两个参数 ( string 和 boolean) 的函数类型, 返回值未知.
说明一个函数.
函数返回类型 {function(): number}
函数返回一个整数.
说明函数的返回类型.
函数的 this 类型 {function(this:goog.ui.Menu, string)}
函数只带一个参数 (string), 并且在上下文 goog.ui.Menu 中执行.
说明函数类型的上下文类型.
可变参数 {function(string, ...[number]): number}
带一个参数 (字符类型) 的函数类型, 并且函数的参数个数可变, 但参数类型必须为 number.
说明函数的可变长参数.

可变长的参数 (使用 @param 标记)
@param {...number} var_args
函数参数个数可变.
使用标记, 说明函数具有不定长参数.
函数的 缺省参数 {function(?string=, number=)}
函数带一个可空且可选的字符串型参数, 一个可选整型参数. = 语法只针对 function 类型有效.
说明函数的可选参数.

函数 可选参数
(使用 @param 标记)
@param {number=} opt_argument
number类型的可选参数.
使用标记, 说明函数具有可选参数.
所有类型 {*} 表示变量可以是任何类型.

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)

字符串对象
boolean
true
false
Boolean(0)
布尔值
Boolean
new Boolean(true)

布尔对象
RegExp
new RegExp('hello')
/world/g
Date
new Date
new Date()

null

null

undefined

undefined
void
function f() {
  return;
}
没有返回值
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(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 中的节点.
HTMLInputElement
htmlDocument.getElementsByTagName('input')[0]
DOM 中, 特定类型的元素.

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

注释


link

使用 JSDoc

我们使用

JSDoc

中的注释风格. 行内注释使用 // 变量 的形式. 另外, 我们也遵循

C++ 代码注释风格
. 这也就是说你需要:

  • 版权和著作权的信息,
  • 文件注释中应该写明该文件的基本信息(如, 这段代码的功能摘要, 如何使用, 与哪些东西相关), 来告诉那些不熟悉代码的读者.
  • 类, 函数, 变量和必要的注释,
  • 期望在哪些浏览器中执行,
  • 正确的大小写, 标点和拼写.

为了避免出现句子片段, 请以合适的大/小写单词开头, 并以合适的标点符号结束这个句子.

现在假设维护这段代码的是一位初学者. 这可能正好是这样的!

目前很多编译器可从 JSDoc 中提取类型信息, 来对代码进行验证, 删除和压缩. 因此, 你很有必要去熟悉正确完整的 JSDoc .

顶层/文件注释

顶层注释用于告诉不熟悉这段代码的读者这个文件中包含哪些东西.
应该提供文件的大体内容, 它的作者, 依赖关系和兼容性信息. 如下:

// Copyright 2009 Google Inc. All Rights Reserved.

/**
 * @fileoverview Description of file, its uses and information
 * about its dependencies.
 * @author user@google.com (Firstname Lastname)
 */

类注释

每个类的定义都要附带一份注释, 描述类的功能和用法.
也需要说明构造器参数.
如果该类继承自其它类, 应该使用 @extends 标记.
如果该类是对接口的实现, 应该使用 @implements 标记.

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

方法与函数的注释

提供参数的说明. 使用完整的句子, 并用第三人称来书写方法说明.

/**
 * Converts text to some completely different text.
 * @param {string} arg1 An argument that makes this more interesting.
 * @return {string} Some return value.
 */
project.MyClass.prototype.someMethod = function(arg1) {
  // ...
};

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

对于一些简单的, 不带参数的 getters, 说明可以忽略.

/**
 * @return {Element} The element for the component.
 */
goog.ui.Component.prototype.getElement = function() {
  return this.element_;
};

属性注释

也需要对属性进行注释.

/**
 * Maximum number of things per pane.
 * @type {number}
 */
project.MyClass.prototype.someProperty = 4;

类型转换的注释

有时, 类型检查不能很准确地推断出表达式的类型, 所以应该给它添加类型标记注释来明确之, 并且必须在表达式和类型标签外面包裹括号.

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

JSDoc 缩进

如果你在 @param,
@return, @supported, @this
@deprecated 中断行, 需要像在代码中一样, 使用4个空格作为一个缩进层次.

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

枚举

/**
 * Enum for tri-state values.
 * @enum {number}
 */
project.TriState = {
  TRUE: 1,
  FALSE: -1,
  MAYBE: 0
};

注意一下, 枚举也具有有效类型, 所以可以当成参数类型来用.

/**
 * Sets project state.
 * @param {project.TriState} state New project state.
 */
project.setState = function(state) {
  // ...
};

Typedefs

有时类型会很复杂. 比如下面的函数, 接收 Element 参数:

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

JSDoc 标记表

标记
@param 模板 & 例子 @param {Type} 变量名 描述

如:

/**
* Queries a Baz for items.
* @param {number} groupNum Subgroup id to query.
* @param {string|number|null} term An itemName,
*     or itemId, or null to search everything.
*/
goog.Baz.prototype.query = function(groupNum, term) {
// ...
};
				

描述 给方法, 函数, 构造器中的参数添加说明.
类型检测支持 完全支持.
@return 模板 & 例子 @return {Type} 描述

如:

/**
* @return {string} The hex ID of the last item.
*/
goog.Baz.prototype.getLastId = function() {
// ...
return id;
};
				

描述 给方法, 函数的返回值添加说明. 在描述布尔型参数时,

用 “Whether the component is visible” 这种描述优于 “True if the component is visible, false otherwise”.

如果函数没有返回值, 就不需要添加

@return
标记.
类型检测支持 完全支持.
@author

模板 & 例子 @author username@google.com (first last)

如:

/**
* @fileoverview Utilities for handling textareas.
* @author kuth@google.com (Uthur Pendragon)
*/
				

描述 表明文件的作者,

通常仅会在

@fileoverview
注释中使用到它.
类型检测支持 不需要.
@see 模板 & 例子 @see Link

如:

/**
* Adds a single item, recklessly.
* @see #addSafely
* @see goog.Collect
* @see goog.RecklessAdder#add
...
				

描述 给出引用链接, 用于进一步查看函数/方法的相关细节.
类型检测支持 不需要.
@fileoverview 模板 & 例子 @fileoverview 描述

如:

/**
* @fileoverview Utilities for doing things that require this very long
* but not indented comment.
* @author kuth@google.com (Uthur Pendragon)
*/
				

描述 文件通览.
类型检测支持 不需要.
@constructor 模板 & 例子 @constructor

如:

/**
* A rectangle.
* @constructor
*/
function GM_Rect() {
...
}
				

描述 指明类中的构造器.
类型检测支持 会检查. 如果省略了, 编译器将禁止实例化.
@interface 模板 & 例子 @interface

如:

/**
* A shape.
* @interface
*/
function Shape() {};
Shape.prototype.draw = function() {};

/**
* A polygon.
* @interface
* @extends {Shape}
*/
function Polygon() {};
Polygon.prototype.getSides = function() {};
				

描述 指明这个函数是一个接口.
类型检测支持 会检查. 如果实例化一个接口, 编译器会警告.
@type 模板 & 例子

@type Type

@type {Type}

如:

/**
* The message hex ID.
* @type {string}
*/
var hexId = hexId;
				

描述 标识变量, 属性或表达式的类型.

大多数类型是不需要加大括号的, 但为了保持一致, 建议统一加大括号.
类型检测支持 会检查
@extends 模板 & 例子

@extends Type

@extends {Type}

如:

/**
* Immutable empty node list.
* @constructor
* @extends goog.ds.BasicNodeList
*/
goog.ds.EmptyNodeList = function() {
...
};
				

描述 与 @constructor 一起使用, 用来表明该类是扩展自其它类的. 类型外的大括号可写可不写.
类型检测支持 会检查
@implements 模板 & 例子

@implements Type

@implements {Type}

如:

/**
* A shape.
* @interface
*/
function Shape() {};
Shape.prototype.draw = function() {};

/**
* @constructor
* @implements {Shape}
*/
function Square() {};
Square.prototype.draw = function() {
...
};
				

描述 与 @constructor 一起使用, 用来表明该类实现自一个接口. 类型外的大括号可写可不写.
类型检测支持 会检查. 如果接口不完整, 编译器会警告.
@lends 模板 & 例子 @lends objectName

@lends {objectName}

如:

goog.object.extend(
Button.prototype,
/** @lends {Button.prototype} */ {
isButton: function() { return true; }
});
				

描述 表示把对象的键看成是其他对象的属性. 该标记只能出现在对象语法中.

注意, 括号中的名称和其他标记中的类型名称不一样, 它是一个对象名, 以”借过来”的属性名命名.

如, @type {Foo} 表示 “Foo 的一个实例”,

but @lends {Foo} 表示 “Foo 构造器”.

更多有关此标记的内容见


JSDoc Toolkit docs
.

类型检测支持 会检查
@private 模板 & 例子 @private

如:

/**
* Handlers that are listening to this logger.
* @type Array.<Function>
* @private
*/
this.handlers_ = [];
				

描述 指明那些以下划线结尾的方法和属性是

私有的.

不推荐使用后缀下划线, 而应改用 @private.
类型检测支持 需要指定标志来开启.
@protected 模板 & 例子 @protected

如:

/**
* Sets the component's root element to the given element.  Considered
* protected and final.
* @param {Element} element Root element for the component.
* @protected
*/
goog.ui.Component.prototype.setElementInternal = function(element) {
// ...
};
				

描述 指明接下来的方法和属性是

被保护的.

被保护的方法和属性的命名不需要以下划线结尾, 和普通变量名没区别.
类型检测支持 需要指定标志来开启.
@this 模板 & 例子

@this Type

@this {Type}

如:

pinto.chat.RosterWidget.extern('getRosterElement',
/**
* Returns the roster widget element.
* @this pinto.chat.RosterWidget
* @return {Element}
*/
function() {
return this.getWrappedComponent_().getElement();
});
				

描述 指明调用这个方法时, 需要在哪个上下文中. 当 this 指向的不是原型方法的函数时必须使用这个标记.
类型检测支持 会检查
@supported 模板 & 例子 @supported 描述

如:

/**
* @fileoverview Event Manager
* Provides an abstracted interface to the
* browsers' event systems.
* @supported So far tested in IE6 and FF1.5
*/
				

描述 在文件概述中用到, 表明支持哪些浏览器.
类型检测支持 不需要.
@enum 模板 & 例子 @enum {Type}

如:

/**
* Enum for tri-state values.
* @enum {number}
*/
project.TriState = {
TRUE: 1,
FALSE: -1,
MAYBE: 0
};
				

描述 用于枚举类型.
类型检测支持 完全支持. 如果省略, 会认为是整型.
@deprecated 模板 & 例子 @deprecated 描述

如:

/**
* Determines whether a node is a field.
* @return {boolean} True if the contents of
*     the element are editable, but the element
*     itself is not.
* @deprecated Use isField().
*/
BN_EditUtil.isTopEditableField = function(node) {
// ...
};
				

描述 告诉其他开发人员, 此方法, 函数已经过时, 不要再使用. 同时也会给出替代方法或函数.
类型检测支持 不需要
@override 模板 & 例子 @override

如:

/**
* @return {string} Human-readable representation of project.SubClass.
* @override
*/
project.SubClass.prototype.toString() {
// ...
};
				

描述 指明子类的方法和属性是故意隐藏了父类的方法和属性. 如果子类的方法和属性没有自己的文档, 就会继承父类的.
类型检测支持 会检查
@inheritDoc 模板 & 例子 @inheritDoc

如:

/** @inheritDoc */
project.SubClass.prototype.toString() {
// ...
};
				

描述 指明子类的方法和属性是故意隐藏了父类的方法和属性, 它们具有相同的文档. 注意: 使用

@inheritDoc 意味着也同时使用了 @override.
类型检测支持 会检查
@code 模板 & 例子 {@code …}

如:

/**
* Moves to the next position in the selection.
* Throws {@code goog.iter.StopIteration} when it
* passes the end of the range.
* @return {Node} The node at the next position.
*/
goog.dom.RangeIterator.prototype.next = function() {
// ...
};
				

描述 说明这是一段代码, 让它能在生成的文档中正确的格式化.
类型检测支持 不适用.
@license or

@preserve
模板 & 例子 @license 描述

如:

/**
* @preserve Copyright 2009 SomeThirdParty.
* Here is the full license text and copyright
* notice for this file. Note that the notice can span several
* lines and is only terminated by the closing star and slash:
*/
				

描述 所有被标记为 @license 或 @preserve 的, 会被编译器保留不做任何修改而直接输出到最终文挡中.

这个标记让一些重要的信息(如法律许可或版权信息)原样保留, 同样, 文本中的换行也会被保留.
类型检测支持 不需要.
@noalias 模板 & 例子 @noalias

如:

/** @noalias */
function Range() {}
				

描述 在外部文件中使用, 告诉编译器不要为这个变量或函数重命名.
类型检测支持 不需要.
@define 模板 & 例子 @define {Type} 描述

如:

/** @define {boolean} */
var TR_FLAGS_ENABLE_DEBUG = true;

/** @define {boolean} */
goog.userAgent.ASSUME_IE = false;
				

描述 表示该变量可在编译时被编译器重新赋值.

在上面例子中, BUILD 文件中指定了

–define=’goog.userAgent.ASSUME_IE=true’

这个编译之后, 常量 goog.userAgent.ASSUME_IE 将被全部直接替换为 true.
类型检测支持 不需要.
@export 模板 & 例子 @export

如:

/** @export */
foo.MyPublicClass.prototype.myPublicMethod = function() {
// ...
};
				

描述

上面的例子代码, 当编译器运行时指定 –generate_exports 标志, 会生成下面的代码:

goog.exportSymbol('foo.MyPublicClass.prototype.myPublicMethod',
foo.MyPublicClass.prototype.myPublicMethod);
				

编译后, 将源代码中的名字原样导出.

使用 @export 标记时, 应该

  1. 包含 //javascript/closure/base.js, 或者
  2. 在代码库中自定义 goog.exportSymbol 和

    goog.exportProperty 两个方法, 并保证有相同的调用方式.
类型检测支持 不需要.
@const 模板 & 例子 @const

如:

/** @const */ var MY_BEER = 'stout';

/**
* My namespace's favorite kind of beer.
* @const
* @type {string}
*/
mynamespace.MY_BEER = 'stout';

/** @const */ MyClass.MY_BEER = 'stout';
				

描述

声明变量为只读, 直接写在一行上.

如果其他代码中重写该变量值, 编译器会警告.

常量应全部用大写字符, 不过使用这个标记, 可以帮你消除命名上依赖.

虽然 jsdoc.org 上列出的 @final 标记作用等价于 @const , 但不建议使用.

@const 与 JS1.5 中的 const 关键字一致.

注意, 编译器不禁止修改常量对象的属性(这与 C++ 中的常量定义不一样).

如果可以准确推测出常量类型的话,那么类型申明可以忽略. 如果指定了类型, 应该也写在同一行上.

变量的额外注释可写可不写.

类型检测支持 支持.
@nosideeffects 模板 & 例子 @nosideeffects

如:

/** @nosideeffects */
function noSideEffectsFn1() {
// ...
};

/** @nosideeffects */
var noSideEffectsFn2 = function() {
// ...
};

/** @nosideeffects */
a.prototype.noSideEffectsFn3 = function() {
// ...
};
				

描述 用于对函数或构造器声明, 说明调用此函数不会有副作用. 编译器遇到此标记时, 如果调用函数的返回值没有其他地方使用到, 则会将这个函数整个删除.
类型检测支持 不需要检查.
@typedef 模板 & 例子 @typedef

如:

/** @typedef {(string|number)} */
goog.NumberLike;

/** @param {goog.NumberLike} x A number or a string. */
goog.readNumber = function(x) {
...
}
				

描述 这个标记用于给一个复杂的类型取一个别名.
类型检测支持 会检查
@externs 模板 & 例子 @externs

如:

/**
* @fileoverview This is an externs file.
* @externs
*/

var document;
				

描述

指明一个外部文件.

类型检测支持 不会检查

在第三方代码中, 你还会见到其他一些 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

JSDoc 中的 HTML

类似于 JavaDoc, JSDoc 支持许多 HTML 标签, 如 <code>,
<pre>, <tt>, <strong>, <ul>, <ol>,
<li>, <a>, 等等.

这就是说 JSDoc 不会完全依照纯文本中书写的格式. 所以, 不要在 JSDoc 中, 使用空白字符来做格式化:

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

另外, 也不要包含任何 HTML 或类 HTML 标签, 除非你就想让它们解析成 HTML 标签.

/**
 * Changes <b> tags to <span> tags.
 */

出来的结果是:

Changes tags to tags.

另外, 也应该在源代码文件中让其他人更可读, 所以不要过于使用 HTML 标签:

/**
 * Changes &lt;b&gt; tags to &lt;span&gt; tags.
 */

上面的代码中, 其他人就很难知道你想干嘛, 直接改成下面的样子就清楚多了:

/**
* Changes 'b' tags to 'span' tags.
*/

编译


link

推荐使用

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

Tips and Tricks


link

JavaScript 小技巧

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

&& 和 ||

二元布尔操作符是可短路的, 只有在必要时才会计算到最后一项.

“||” 被称作为 ‘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]);

使用 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(), 所以尽量使用赋值操作.

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

这种方法对所有的 collections 和数组(只要数组不包含 falsy 值) 都适用.

在上面的例子中, 也可以通过 firstChild 和 nextSibling 来遍历孩子节点.

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

Parting Words

保持一致性.

当你在编辑代码之前, 先花一些时间查看一下现有代码的风格. 如果他们给算术运算符添加了空格, 你也应该添加.
如果他们的注释使用一个个星号盒子, 那么也请你使用这种方式.

代码风格中一个关键点是整理一份常用词汇表, 开发者认同它并且遵循, 这样在代码中就能统一表述.
我们在这提出了一些全局上的风格规则, 但也要考虑自身情况形成自己的代码风格.
但如果你添加的代码和现有的代码有很大的区别, 这就让阅读者感到很不和谐.
所以, 避免这种情况的发生.

修订版 2.9

Aaron Whyte
Bob Jervis
Dan Pupius
Erik Arvidsson
Fritz Schneider
Robby Walker

jQuery uploadify中文按钮乱码的原因及解决方法

jQuery uploadify

uploadify是一款基于jQuery库的上传插件,但很可惜的是无论你怎么设置参数buttonText ,它的中文按钮都会出现乱码的情况,现把出现原因及解决方法总结如下:

原因:

出现乱码,很明显是在对字符编码和解码的时候出现问题了,打开该插件的脚本,发现源码是这样对按钮文字buttonText 进行编码的:
if (settings.buttonText) data.buttonText = escape(settings.buttonText);对编码方式了解的朋友,应该知道js中的escape()方法是采用ISO Latin字符集对指定的字符串进行编码的,很明显咱们的中文,包括日文,韩文(CJK)都不包括在ISO Latin字符集里面的。而且,该插件的解码方式应该用了unescpe()。这样不乱码才怪呢。

解决方法:

1.改变字符编码解码方式

既然知道乱码是编码解码的原因,最好的解决方法当然是更改编码解码方式了。encodeURI() 方法,可以把URI字符串采用UTF-8编码格式转化成escape格式的字符串,而且咱们的中文也属于UTF-8字符集里面的,再用decodeURI()进行解码。具体修改代码如下:

A. jquery.uploadify.js 中的
if (settings.buttonText) data.buttonText = escape(settings.buttonText);
改为:
if (settings.buttonText) data.buttonText = encodeURI(settings.buttonText);

B. uploadify.fla中的
browseBtn.empty.buttonText.text = unescape(param.buttonText);
改为:
browseBtn.empty.buttonText.text = decodeURI(param.buttonText);

2.使用参数buttonImg

显然buttonImg是有来设置图片按钮的,自己做的图片,上面写中文,英文就随便你了,没编码当然不会乱码了,不过这个方法,按钮显示会有点慢,还是比较推荐方法1.

修复中文按钮乱码的jQuery uploadify (2095)