修正版本 2.28
原文:http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
每个风格点都有一个展开/收起按钮以便你可以得到更多的信息:▶. 你可以将全部展开或收起:
JavaScript是一门客户端脚本语言,Google经常用它来晒优越,本文档列出了一些在做JS项目时需要注意的地方。都是高富帅整理的,还望各位屌丝们有时间都好好看看多学学,别对这个世界太消极了!(译者注:所谓的客户端呢就是浏览器或本地软件环境,市面上所有的浏览器都支持JS,JS发展到现在统一WEB端再进军移动互联网后,相信在一段时间内都是不可被轻易替代的语言,所以有兴趣的同学们可以多关注一下)
每个变量声明都要加上var关键字噢。
解释:如果你不指定关键字var
, 该变量就会暴露在全局作用域(window)中,这很可能会覆盖全局作用域中的同名变量,从而引发问题(另外GC也会因此而无法有效回收内存啊),所以务必用var
声明变量。
NAMES_LIKE_THIS
这样的形式。没事干了可以用@const
来标记它是常量,但永远不要用const
关键字来进行常量声明。解释:
对于基本类型的常量,命名简介解释一下作用就可以了。
/** * 一分钟有多少秒呀 * @type {number} */ goog.example.SECONDS_IN_A_MINUTE = 60;
对于非基本类型,用@const
注释一下会更明了。
/** * 已知单位所对应的秒数 * @type {Object.<number>} * @const */ goog.example.SECONDS_TABLE = { minute: 60, hour: 60 * 60 day: 60 * 60 * 24 }
这个标记主要是让编译器知道这变量是常量状态。
至于关键词const
,IE不解析啊会报错的,所以别用啦。
如果不加分号JS解释器也会按隐式分隔的标准去执行,但那样调试、压缩、合并的时候都很不方便。不要那样做嘛你可以做的更好的不是么。
而且在某些情况下,不写分号可是很危险的:
// 1. MyClass.prototype.myMethod = function() { return 42; } // 这个缺德的没写分号 (function() { // 匿名函数的执行 })(); var x = { 'i': 1, 'j': 2 } // 这个天杀的也没写分号 // 2. 在IE和FF的某些版本里试试(译者表示没试出来) // 老子知道你永远也不会写这样2的代码的,但它真的会因为木有写分号而报错噢 [normalVersion, ffVersion][isIE](); var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // 这个屌丝木有写分号 // 3. conditional execution a la bash -1 == resultOfOperation() || die();
上段代码会发生什么事情?
x[ffVersion][isIE]()
。(译者木验证)resultOfOperation()
返回非 NaN
那就会调用 die
方法,并导致 die()
执行后的返回结果会赋给 THINGS_TO_EAT
。为啥会这样呢?
JS语句以分号作为结束符使得JS解释器解析,如果省略分号,就会由JS解释器确定语句的结尾。尼玛,不是在很明确的情况下,它能确定么?上面几个例子里,都是在语句中声明了/函数/对象/数组,但闭括号("}"或"]")并不代表着结束。如果下一个语句开始是一个中缀或括号运算符,那JS就永远不会结束声明。
这真的是很奇怪的事情噢亲,而且真的不知道还会出现什么奇怪的事情噢亲,所以确保你的语句有分号结束吧。
译者注:加上分号也会在某些情况下增进代码的性能,因为这样解析器就不必再花时间推测应该在哪里插入分号了(取自《Javascript高级程序设计》)
只有屌丝才这样做:
if (x) { function foo() {} }
虽然大多数JS引擎都支持块内声明函数,但它并不是 ECMAScript 标准的一部分(详见 ECMA-262, 第13条和第14条)。更糟糕的是各引擎对于块内函数声明的实现都不一样,和 EcmaScript 的建议相违背。 ECMAScript 只允许在根脚本语句或其他函数中进行函数声明,如果一定要用的话可以在块内用变量来定义函数:
if (x) { var foo = function() {} }
不管谁写代码都不能百分百的说自己的程序木有异常的。所以,细心的检查代码是一方面,预先处理一些可能出现的异常也是有必要的(使用try-catch(e)),特别是正在做一些重要项目的时候(框架什么的)。
译者注:如果是预先就知道自己的代码会发生错误时,再使用try-catch语句就不合适了噢,而是应该预先对其进行检查,防止错误的出现(取自《JavaScript高级程序设计》)
没有自定义异常抛出的情况下,代码运行可能会报一些原始的错误信息,但包含的内容和错误描述会因浏览器而不同却都不是特别明确,不易维护,No fashion!所以在感觉适当的时候可以使用自定义异常抛出(这里指的是throw,带有适当信息的自定义错误能够显著提升代码的可维护性)。
为了获得最大的可移植性和兼容性,尽量依赖于标准方法。(比如使用 string.charAt(3)
而不是 string[3]
,再比如通过DOM原生方法去访问节点元素,而不是使用某框架封装好的获取方法或快捷引用)。
木有必要去使用基本包装类型么就不要使用啦,而且它在以下这种情况下很危险:
var x = new Boolean(false); if (x) { alert('hi'); // 显示 hi。因为基本包装类型的实例调用typeof会返回"object",对象么在判断时都会被转换为布尔值true。 }
所以,你懂得!就算不懂,也终有一天会懂的嘛!
唉,可是还是有种情况是可以使用的嘛,比如类型转换:
var x = Boolean(0); if (x) { alert('hi'); // 不会弹框显示 hi } typeof Boolean(0) == 'boolean'; typeof new Boolean(0) == 'object';
它对于 number
, string
和 boolean
之间的类型转换很有帮助。
多级原型结构指的是 JavaScript 实现继承。 比如自定义类D,并把自定义类B作为D的原型,那就是一个多级原型结构了。怎么说呢,结构越来越复杂了就越难维护。
鉴于此,使用 the Closure Library
的 goog.inherits()
或许会是更好的选择。
function D() { goog.base(this) } goog.inherits(D, B); D.prototype.method = function() { ... };
Foo.prototype.bar = function() { ... };
给原型对象的构造函数添加方法和属性有很多种方式,更倾向于使用以下这种风格:
Foo.prototype.bar = function() { /* ... */ };
很有用,却经常被忽略(哪里被忽略了基本哪次面试都有被问到这样的问题有木有!!!)。有耐心的去看看 a good description of how closures work。
有一件需要注意的事情,闭包会保持一个指向它封闭作用域的指针,所以在给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 */ } }
译者注:由于IE9之前的版本对JScript对象和COM对象使用不同的垃圾收集例程,因此闭包在IE的这些版本中会导致如上问题。这个匿名函数作为element元素事件处理程序,形成闭包的状态就会保存对父层函数内活动对象的引用,只要匿名函数存在,element的引用数至少也是1,因此它所占用的内存就永远不会被回收。注意,闭包会引用包含函数的整个活动对象,即使闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用。因此,有必要把使用完的element变量设置为null,解除对DOM对象的引用,确保正常回收其占用的内存。(取自《Javascript高级程序设计》184P)
eval()
很不稳定,会造成语义混乱,如果代码里还包含用户输入的话就更危险了,因为你无法确切得知用户会输入什么!难道就不能用更好更清晰更安全的方式去写你的代码么孩子?
然而 eval
很容易解析被序列化的对象,所以反序列化的任务还是可以交给它做的。(例如:解析RPC响应的时候)
反序列化的过程就是将字节流转换成内存中的数据结构。例如,你可能会将对象输出到文件中:
users = [ { name: 'Eric', id: 37824, email: 'jellyvore@myway.com' }, { name: 'xtof', id: 31337, email: 'b4d455h4x0r@google.com' }, ... ];
用 eval 可以很简单的读取这些数据到内存中。
同样,用 eval()
可以简单的解码RPC返回值。例如,你用 XMLHttpRequest
发出一个RPC请求,服务器端相应返回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
语句会让你的代码行如踏云,毫无安全感。因为用 with
添加过来的对象可能会对当前局部作用域的属性与方法产生冲突,进而影响到整个环境。比如,下面的代码发生了什么:
with (foo) { var x = 3; return x; }
回答: 啥事都可能发生。这个局部变量 x
可能会被 foo
对象里的属性所覆盖,它甚至可能会有setter,导致在赋值3时会执行许多其他的代码,所以太危险了,别用 with
。
this
语义很特别。它大多数情况下会指向全局对象,有的时候却是指向调用函数的作用域的(使用eval时),还可能会指向DOM树的某个节点(绑定事件时),新创建的对象(构造函数中),也可能是其他的一些什么乱七八糟的玩意(如果函数被 call() 或者被
apply()
)。
很容易出错的,所以最好是以下这两种情况的时候再选择使用:
对
循环可能会出错,因为它不是从 Array
类型的对象进行 for-in0
到 length - 1
,而是这个对象包括其原型链上的所有键值,下面是一些失败的例子:
function printArray(arr) { for (var key in arr) { print(arr[key]); } } printArray([0,1,2,3]); // 虽然这没问题 var a = new Array(10); printArray(a); // 这就错了 a = document.getElementsByTagName('*'); printArray(a); // 错 a = [0,1,2,3]; a.buhu = 'wine'; printArray(a); // 错的 a = new Array; a[3] = 3; printArray(a); // 还是错的
遍历数组通常用for就可以了。(译者注:如果木有顺序要求,可以使用for(var i = l;i--;)速度会更高些,但这是10万级效率差,通常是可以被忽略的)
function printArray(arr) { var l = arr.length; for (var i = 0; i < l; i++) { print(arr[i]); } }
Array
去做 map/hash/associative 要做的事情。不允许使用关联 Array
s ……更具体的说是不允许使用非数字索引的数组(Array不都是用数字索引的么,也没办法用吧除非是两个数组同索引)。如果你需要一个map/hash那就用 Object
来代替 Array
吧,实际上你想用的就是 Object
而不是 Array。
Array
只是继承自 Object
(和Date
, RegExp
和 String一样
)。
不要这样写:
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.';
用 Array
和 Object
字面量代替 Array
和 Object
构造函数。
Array 构造函数会因为传参不当而导致理解错误。
// 长度是 3. var a1 = new Array(x1, x2, x3); // 长度是 2. var a2 = new Array(x1, x2); // 如果x1是自然数那数组的长度就变成了x1 // 如果x1是数字但不是自然数就会报错 // 不然数组就会有一个元素x1 var a3 = new Array(x1); // 长度是 0. var a4 = new Array();
如上,如果传入了一个参数而不是两个,那这数组可能就不是预期的长度了。
为了避免这种情况,用更可读的数组字面量吧。
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 };
通常,使用类似于 functionNamesLikeThis
,
variableNamesLikeThis
, ClassNamesLikeThis
,
EnumNamesLikeThis
, methodNamesLikeThis
,
和 SYMBOLIC_CONSTANTS_LIKE_THIS
这样的命名方式(驼峰式)
属性和方法
更多关于 private 和 protected 去看 visibility 这一部分吧。
方法和函数参数
可选参数以 opt_
开头。
函数参数不固定的时候应该有个参数 var_args 以
数组的形式将参数传进来,也许你不喜欢这样做,那你可以使用 arguments
这个伪数组。
可选和可变参数都应该用 @param
注释标记一下,虽然对解释器都没什么影响,但还是尽量遵守吧,以让代码能够更易理解。
属性的访问器
EcmaScript 5 不推荐使用对象属性的 getters 和 setters。你用了就用了吧,但是你得注意,千万别让 getters 方法改变当前对象的各属性状态。
/** * 错 -- 不要这样做 */ var foo = { get next() { return this.nextId++; } }; };
函数的访问器
函数对象中的 Getters 和 Setters 也不是必须的。但如果你用了,你最好以类似 getFoo()
和
setFoo(value)
.的形式来命名。(如果返回布尔型的值,可以用 isFoo()
或者其他更自然的名字)
命名空间
JavaScript并不支持包和命名空间。
这样就导致JS在两个项目以上的集成环境中可能会出现全局作用域下的命名冲突,后果严重且不易调试。为了提高代码的公用性,请大家遵守如下约定以避免冲突。
在全局作用域下使用伪命名空间
在全局作用域下对相关的JS项目或库用唯一的顶级变量标识当作伪命名空间。比如你的项目名是"Project Sloth",那可以写一个伪命名空间 sloth.*
.
var sloth = {}; sloth.sleep = function() { ... };
好多类似 the Closure Library and Dojo toolkit 这样的JS库都会给你一个高阶方法来让你声明命名空间,然后你就可以在这个命名空间下进行各种声明了。
goog.provide('sloth'); sloth.sleep = function() { ... };
明确命名空间所有权
当你选择了一个子命名空间下进行开发,请告知父命名空间的负责人(你懂得)。假设你开始了一个项目是sloths下的hats,请确保这个Sloth项目的负责人知道是你在用sloth.hats。
外部代码和内部代码使用不同的命名空间
“外部代码”指的是你的代码体系以外的可独立编译的代码,比如你用了一个外部库在 foo.hats.*
下,那你自己的内部代码就不应该再在其下面了,否则会导致产生冲突和难以维护。
foo.require('foo.hats'); /** * 错 -- 不要这样做 * @constructor * @extend {foo.hats.RoundHat} */ foo.hats.BowlerHat = function() { };
如果你需要在外部命名空间中定义新的API,那你应该通过显式的方法导出公共API函数。自身内部的代码需要使用这些API时,可以直接通过内部命名来调用,一是为了保持一致性,二是能让编译器更好的优化代码。
foo.provide('googleyhats.BowlerHat'); foo.require('foo.hats'); /** * @constructor * @extend {foo.hats.RoundHat} */ googleyhats.BowlerHat = function() { ... }; goog.exportSymbol('foo.hats.BowlerHat', googleyhats.BowlerHat);
为增强可读性,将长名引用化为短别名
用局部别名引用完整的包名可以增强可读性。局部别名命名应和完整包名的最后一部分相匹配。
/** * @constructor */ some.long.namespace.MyClass = function() { }; /** * @param {some.long.namespace.MyClass} a */ some.long.namespace.MyClass.staticHelper = function(a) { ... }; myapp.main = function() { var MyClass = some.long.namespace.MyClass; var staticHelper = some.long.namespace.MyClass.staticHelper; staticHelper(new MyClass()); };
呃……不要别名引用命名空间。
myapp.main = function() { var namespace = some.long.namespace; namespace.MyClass.staticHelper(new namespace.MyClass()); };
避免访问别名引用对象的属性,除非它是个枚举。
/** @enum {string} */ some.long.namespace.Fruit = { APPLE: 'a', BANANA: 'b' }; myapp.main = function() { var Fruit = some.long.namespace.Fruit; switch (fruit) { case Fruit.APPLE: ... case Fruit.BANANA: ... } };
myapp.main = function() { var MyClass = some.long.namespace.MyClass; MyClass.staticHelper(null); };
不要在全局作用域中创建别名引用,仅在函数块中去使用。
文件名
文件名应该全部字母小写,以避免在某些区分大小写的系统平台产生文件名混淆。文件名应该以 .js
结束,且不要包含 -
或 _
( -
比 _
更好些)。
你可以为自己的对象定义toString()
方法。这是好事呀,但是你要确保你的实现方法满足:(1)总是成功的(2)木有副作用。如果你的方法不满足这两个条件,运行起来将会很危险。例如,如果 toString()
调用了包含 assert
的方法, assert
可能会试着输出失败的对象名,结果又调用了 toString()。
任何时候都要明确作用域 - 提高可移植性和清晰度。例如,不要依赖于作用域链中的 window
对象。有的时候你可能会想让你的其他应用的函数中使用的 window
对象不是之前所指的窗口对象。
我们鼓励使用 C++ formatting rules 里面介绍的代码格式化规范,还有下面要说的:
大括号
因为分号会被隐式插入代码中,所以应将大括号和前面的代码放在一行,以防止误读。例如:
if (something) { // ... } else { // ... }
数组和对象的初始化
合适的话,可以直接单行进行数组和元素的初始化:
var arr = [1, 2, 3]; // 前后木有空格 var obj = {a: 1, b: 2, c: 3}; // 前后木有空格
多行数组和元素初始化时,最好缩进两个空格。
// 对象初始化 var inset = { top: 10, right: 20, bottom: 15, left: 12 }; // 数组初始化 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' ]; // 直接在方法中调用 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,0 lengthyName: 2 };
函数参数
尽可能的将所有的函数参数都写在同一行上,但为了保持增强可读性,如果一行超过了80字符的话可适当的换行,甚至可以每个参数都独立一行,记得格式上的优化,比如缩进4个空格和对齐括号。
以下是几种常见的:
// Four-space, wrap at 80. Works with very long function names, survives // renaming without reindenting, low on space. goog.foo.bar.doThingThatIsVeryDifficultToExplain = function( veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... }; // Four-space, one argument per line. Works with long function names, // survives renaming, and emphasizes each argument. goog.foo.bar.doThingThatIsVeryDifficultToExplain = function( veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... }; // Parenthesis-aligned indentation, wrap at 80. Visually groups arguments, // low on space. function foo(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... } // Parenthesis-aligned, one argument per line. Visually groups and // emphasizes each individual argument. function bar(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... }
如果调用的函数本身就已经缩进了,呃你当然可以针对于当前被调用的函数(或之前的原始函数)再向前缩进4个空格。以下这样的风格勉强可以接受:
if (veryLongFunctionNameA( veryLongArgumentName) || veryLongFunctionNameB( veryLongArgumentName)) { veryLongFunctionNameC(veryLongFunctionNameD( veryLongFunctioNameE( veryLongFunctionNameF))); }
传递匿名函数
如果调用方法传参内有匿名函数的声明,函数体应相对于该调用方法(或变量名)缩进2个空格,这样匿名函数体更易阅读(而不是将代码挤到屏幕的另一半)。
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 = '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; // 能放一行的都放在一行里 // 缩进4个空格木有问题 var y = a ? longButSimpleOperandB : longButSimpleOperandC; // 缩进到第一个判断变量位置木有问题 var z = a ? moreComplicatedB : moreComplicatedC;
括号的事情可大可小,没必要的时候不要用它。
对于一元操作符(如 delete
, typeof
和 void)
或是某些关键字(如return
, throw
, case
, new)后面永远不要使用括号。
@private
和 @protected
我们推荐使用JSDoc的 @private
和 @protected
对类,函数和属性进行可见性权限的标注。
有个写做 --jscomp_warning=visibility 的编译器参数,编辑器会提示违反可见性相关的警告。具体内容请查看 Closure Compiler Warnings.
标注为 @private
的全局变量和函数,只有其自身文件中的代码能进行访问和调用。
构造函数标注了 @private
则代表着只能在自身文件中使用它的公共静态方法或实例化,也可以通过 instanceof
操作符进行访问。
永远不要为全局变量,函数和构造函数标记 @protected
。
// 文件 1。 // AA_PrivateClass_ 和 AA_init_ 可以相互访问,因为他们在同一个文件里。 /** * @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_
)和该类的原型类的可见性是相同的,没有办法实现两个子类是公共的,而他们的构造函数却是私有(因为构造函数很容易别名)。
如果使用JSDoc,那就尽量按照它的规范去写,目前支持 JS2 和 JS1.x 两种规范。
(译者表示表格里的单词太多,要犯懒,想了解详细信息的可以查阅JS2)
JavaScript类型语言
这个JS2提议为JavaScript类型定制了一种语言。当我们在JsDoc中注释函数参数和返回值类型的时候会用到它。
虽然JS2提议在不断的进化,但编译器还是会继续支持老的类型语法,只是不建议使用而已。
Operator Name | Syntax | Description | Deprecated Syntaxes |
---|---|---|---|
Type Name |
{boolean} , {Window} ,
{goog.ui.Menu}
|
Simply the name of a type. | |
Type Application |
{Array.<string>} An array of strings. {Object.<string, number>}
An object in which the keys are strings and the values are numbers. |
Patameterizes a type, by applying a set of type arguments to that type. The idea is analogous to generics in Java. | |
Type Union |
{(number|boolean)} A number or a boolean. |
Indicates that a value might have type A OR type B. |
{(number,boolean)} ,
{number|boolean} ,
{(number||boolean)}
|
Record Type |
{{myNum: number, myObject}}
An anonymous type with the given type members. |
Indicates that the value has the specified members with the
specified types. In this case, Notice that the braces are part of the type syntax. For
example, to denote an |
|
Nullable type |
{?number} A number or NULL. |
Indicates that a value is type A or null .
By default, all object types are nullable.
NOTE: Function types are not nullable.
|
{number?}
|
Non-nullable type |
{!Object} An Object, but never the null value.
|
Indicates that a value is type A and not null. By default, all value types (boolean, number, string, and undefined) are not nullable. |
{Object!}
|
Function Type |
{function(string, boolean)} A function that takes two arguments (a string and a boolean), and has an unknown return value. |
Specifies a function. | |
Function Return Type |
{function(): number} A function that takes no arguments and returns a number. |
Specifies a function return type. | |
Function this Type |
{function(this:goog.ui.Menu, string)} A function that takes one argument (a string), and executes in the context of a goog.ui.Menu. |
Specifies the context type of a function type. | |
Function new Type |
{function(new:goog.ui.Menu, string)} A constructor that takes one argument (a string), and creates a new instance of goog.ui.Menu when called with the 'new' keyword. |
Specifies the constructed type of a constructor. | |
Variable arguments |
{function(string, ...[number]): number} A function that takes one argument (a string), and then a variable number of arguments that must be numbers. |
Specifies variable arguments to a function. | |
Variable arguments (in @param annotations)
|
@param {...number} var_args A variable number of arguments to an annotated function. |
Specifies that the annotated function accepts a variable number of arguments. | |
Function optional arguments |
{function(?string=, number=)} A function that takes one optional, nullable string and one optional number as arguments. The = syntax is
only for function type declarations.
|
Specifies optional arguments to a function. | |
Function optional arguments
(in @param annotations)
|
@param {number=} opt_argument An optional parameter of type number .
|
Specifies that the annotated function accepts an optional argument. | |
The ALL type | {*} |
Indicates that the variable can take on any type. | |
The UNKNOWN type | {?} |
Indicates that the variable can take on any type, and the compiler should not type-check any uses of it. |
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元素,返回第一个 |
明确类型
可能出现类型检查并不能准确判断表达式的类型的情况,可以在注释里添加类型标注,并在中括号内写出表达式的类型,如果有对该类型的注解就更好了。
/** @type {number} */ (x) (/** @type {number} */ x)
因为JavaScript是弱类型的语言,理解函数参数和类属性的可选,可空与未定义之间的区别还是很重要的。
对象类型(也称引用类型)默认是可空的,注意:函数类型默认非空。除了字符串,数字,布尔,undefined或null以外,对象可以是任何类型。例如:
/** * Some class, initialized with a value. * @param {Object} value Some value. * @constructor */ function MyClass(value) { /** * Some value. * @type {Object} * @private */ this.myValue_ = value; }
上段代码是告诉编译器 myValue_
属性为一个对象或null。如果 myValue_
永远也不能是null,那就应该像下面一样声明:
/** * Some class, initialized with a non-null value. * @param {!Object} value Some value. * @constructor */ function MyClass(value) { /** * Some value. * @type {!Object} * @private */ this.myValue_ = value; }
这样,如果编译器在代码中碰到 MyClass
初始化个null值是,就会发出警告。
函数的可选参数可能在运行的时候并没有被定义,如果参数引用至类的属性上,那则需要如下声明:
/** * Some class, initialized with an optional value. * @param {Object=} opt_value Some value (optional). * @constructor */ function MyClass(opt_value) { /** * Some value. * @type {Object|undefined} * @private */ this.myValue_ = opt_value; }
以上代码是告诉编译器说 myValue_
有可能是一个对象,null,还可能是 undefined。
注意:可选参数 opt_value
被声明为 {Object=}
,而并不是 {Object|undefined}
。这是因为可选属性可能是定义的或未定义的,虽然说明确写undefined也没关系,但读起来前边的更爽。
最后,注意可空和可选都是正交属性,下面的四个声明都是不同的:
/** * 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; /** * @param {string} tagName * @param {goog.ElementContent} contents * @return {!Element} */ goog.createElement = function(tagName, contents) { ... };
模板属性
编译器已经有限的支持模板类型。它只能推断的类型,这在一个匿名函数字面量从类型的这个论点,是否这个论点是缺失的。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; });
我们鼓励依照 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 items received
所以,可以用这种方式:
/** * Computes weight based on three factors: * <ul> * <li>items sent * <li>items received * <li>last timestamp * </ul> */
顶部或文件头的注释
顶部注释是为了让读者能够很快的明了这个文件里的代码是干嘛的,描述应包括作者,年代,依赖关系或兼容信息等相关,下面的例子:
// Copyright 2009 Google Inc. All Rights Reserved. /** * @fileoverview Description of file, its uses and information * about its dependencies. * @author user@google.com (Firstname Lastname) */
类的注释
对于类的注释,肯定要写功能和类型的描述,还有一些参数和原型描述等信息。
/** * 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方法没有参数和其他影响的,可以忽略描述。
属性的描述
/** * Maximum number of things per pane. * @type {number} */ project.MyClass.prototype.someProperty = 4;
JSDoc标签参考
(译者注:看到解释有一大段,译者犯懒,有想看的请查阅 JSDoc Toolkit Tag Reference )
Tag | Template & Examples | Description |
---|---|---|
@author |
@author username@google.com (first last)
For example: /** * @fileoverview Utilities for handling textareas. * @author kuth@google.com (Uthur Pendragon) */ |
Document the author of a file or the owner of a test,
generally only used in the @fileoverview comment.
|
@code |
{@code ...}
For example: /** * 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() { // ... }; |
Indicates that a term in a JSDoc description is code so it may be correctly formatted in generated documentation. |
@const |
@const
For example: /** @const */ var MY_BEER = 'stout'; /** * My namespace's favorite kind of beer. * @const * @type {string} */ mynamespace.MY_BEER = 'stout'; /** @const */ MyClass.MY_BEER = 'stout'; |
Marks a variable as read-only and suitable for inlining. Generates warnings if it is rewritten. Constants should also be ALL_CAPS, but the annotation should help eliminate reliance on the naming convention. Although @final is listed at jsdoc.org and is supported as equivalent to @const in the compiler, it is discouraged. @const is consistent with JS1.5's const keyword. Note that changes to properties of const objects are not currently prohibited by the compiler (inconsistent with C++ const semantics). The type declaration can be omitted if it can be clearly inferred. If present, it must be on its own line. An additional comment about the variable is optional. |
@constructor |
@constructor
For example: /** * A rectangle. * @constructor */ function GM_Rect() { ... } |
Used in a class's documentation to indicate the constructor. |
@define |
@define {Type} description
For example: /** @define {boolean} */ var TR_FLAGS_ENABLE_DEBUG = true; /** @define {boolean} */ goog.userAgent.ASSUME_IE = false; |
Indicates a constant that can be overridden by the compiler at
compile-time. In the example, the compiler flag
--define='goog.userAgent.ASSUME_IE=true'
could be specified in the BUILD file to indicate that the
constant goog.userAgent.ASSUME_IE should be replaced
with true .
|
@deprecated |
@deprecated Description
For example: /** * 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) { // ... }; |
Used to tell that a function, method or property should not be used any more. Always provide instructions on what callers should use instead. |
@enum |
@enum {Type}
For example: /** * Enum for tri-state values. * @enum {number} */ project.TriState = { TRUE: 1, FALSE: -1, MAYBE: 0 }; |
|
@export |
@export
For example: /** @export */ foo.MyPublicClass.prototype.myPublicMethod = function() { // ... }; |
Given the code on the left, when the compiler is run with
the goog.exportSymbol('foo.MyPublicClass.prototype.myPublicMethod', foo.MyPublicClass.prototype.myPublicMethod); which will export the symbols to uncompiled code.
Code that uses the
|
@extends |
@extends Type
For example: /** * Immutable empty node list. * @constructor * @extends goog.ds.BasicNodeList */ goog.ds.EmptyNodeList = function() { ... }; |
Used with @constructor to indicate that a class inherits from another class. Curly braces around the type are optional. |
@externs |
@externs
For example: /** * @fileoverview This is an externs file. * @externs */ var document; |
Declares an externs file. |
@fileoverview |
@fileoverview Description
For example: /** * @fileoverview Utilities for doing things that require this very long * but not indented comment. * @author kuth@google.com (Uthur Pendragon) */ |
Makes the comment block provide file level information. |
@implements |
@implements Type
For example: /** * A shape. * @interface */ function Shape() {}; Shape.prototype.draw = function() {}; /** * @constructor * @implements {Shape} */ function Square() {}; Square.prototype.draw = function() { ... }; |
Used with @constructor to indicate that a class implements an interface. Curly braces around the type are optional. |
@inheritDoc |
@inheritDoc
For example: /** @inheritDoc */ project.SubClass.prototype.toString() { // ... }; |
Deprecated. Use @override instead. Indicates that a method or property of a subclass intentionally hides a method or property of the superclass, and has exactly the same documentation. Notice that @inheritDoc implies @override. |
@interface |
@interface
For example: /** * A shape. * @interface */ function Shape() {}; Shape.prototype.draw = function() {}; /** * A polygon. * @interface * @extends {Shape} */ function Polygon() {}; Polygon.prototype.getSides = function() {}; |
Used to indicate that the function defines an inteface. |
@lends |
@lends objectName @lends {objectName}
For example: goog.object.extend( Button.prototype, /** @lends {Button.prototype} */ { isButton: function() { return true; } }); |
Indicates that the keys of an object literal should
be treated as properties of some other object. This annotation
should only appear on object literals.
Notice that the name in braces is not a type name like
in other annotations. It's an object name. It names
the object on which the properties are "lent".
For example, @type {Foo} means "an instance of Foo",
but @lends {Foo} means "the constructor Foo".
The
JSDoc Toolkit docs have more information on this
annotation.
|
@license or @preserve |
@license Description
For example: /** * @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: */ |
Anything marked by @license or @preserve will be retained by the compiler and output at the top of the compiled code for that file. This annotation allows important notices (such as legal licenses or copyright text) to survive compilation unchanged. Line breaks are preserved. |
@noalias |
@noalias
For example: /** @noalias */ function Range() {} |
Used in an externs file to indicate to the compiler that the variable or function should not be aliased as part of the alias externals pass of the compiler. |
@nosideeffects |
@nosideeffects
For example: /** @nosideeffects */ function noSideEffectsFn1() { // ... }; /** @nosideeffects */ var noSideEffectsFn2 = function() { // ... }; /** @nosideeffects */ a.prototype.noSideEffectsFn3 = function() { // ... }; |
This annotation can be used as part of function and constructor declarations to indicate that calls to the declared function have no side-effects. This annotation allows the compiler to remove calls to these functions if the return value is not used. |
@override |
@override
For example: /** * @return {string} Human-readable representation of project.SubClass. * @override */ project.SubClass.prototype.toString() { // ... }; |
Indicates that a method or property of a subclass intentionally hides a method or property of the superclass. If no other documentation is included, the method or property also inherits documentation from its superclass. |
@param |
@param {Type} varname Description
For example: /** * 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) { // ... }; |
Used with method, function and constructor calls to document the arguments of a function. Type names must be enclosed in curly braces. If the type is omitted, the compiler will not type-check the parameter. |
@private |
@private
For example: /** * Handlers that are listening to this logger. * @type Array.<Function> * @private */ this.handlers_ = []; |
Used in conjunction with a trailing underscore on the method
or property name to indicate that the member is
private.
Trailing underscores may eventually be deprecated as tools are
updated to enforce @private .
|
@protected |
@protected
For example: /** * 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) { // ... }; |
Used to indicate that the member or property is protected. Should be used in conjunction with names with no trailing underscore. |
@return |
@return {Type} Description
For example: /** * @return {string} The hex ID of the last item. */ goog.Baz.prototype.getLastId = function() { // ... return id; }; |
Used with method and function calls to document the return
type. When writing descriptions for boolean parameters,
prefer "Whether the component is visible" to "True if the
component is visible, false otherwise". If there is no return
value, do not use an @return tag.
Type names must be enclosed in curly braces. If the type
is omitted, the compiler will not type-check the return value.
|
@see |
@see Link
For example: /** * Adds a single item, recklessly. * @see #addSafely * @see goog.Collect * @see goog.RecklessAdder#add ... |
Reference a lookup to another class function or method. |
@supported |
@supported Description
For example: /** * @fileoverview Event Manager * Provides an abstracted interface to the * browsers' event systems. * @supported So far tested in IE6 and FF1.5 */ |
Used in a fileoverview to indicate what browsers are supported by the file. |
@suppress |
@suppress {warning1|warning2}
For example: /** * @suppress {deprecation} */ function f() { deprecatedVersionOfF(); } |
Suppresses warnings from tools. Warning categories are
separated by | .
|
@template |
@template
For example: /** * @param {function(this:T, ...)} fn * @param {T} thisObj * @param {...*} var_args * @template T */ goog.bind = function(fn, thisObj, var_args) { ... }; |
This annotation can be used to declare a template typename. |
@this |
@this Type
For example: pinto.chat.RosterWidget.extern('getRosterElement', /** * Returns the roster widget element. * @this pinto.chat.RosterWidget * @return {Element} */ function() { return this.getWrappedComponent_().getElement(); }); |
The type of the object in whose context a particular method is
called. Required when the this keyword is referenced
from a function that is not a prototype method.
|
@type |
@type Type
For example: /** * The message hex ID. * @type {string} */ var hexId = hexId; |
Identifies the type of a variable, property, or expression. Curly braces are not required around most types, but some projects mandate them for all types, for consistency. |
@typedef |
@typedef
For example: /** @typedef {(string|number)} */ goog.NumberLike; /** @param {goog.NumberLike} x A number or a string. */ goog.readNumber = function(x) { ... } |
This annotation can be used to declare an alias of a more complex type. |
推荐使用的有:
@typedef @type @this @template @suppress @supported @see @return @protected @private @param @override @nosideeffects @noalias @lends @interface @inheritDoc @implements @fileoverview @externs @extends @export @enum @deprecated @define @constructor @const @code
你可能在第三方代码中看到其他的JSDoc标注。昂它们在Google代码中并不怎么鼓励使用,但谁知道呢可能未来的某一天就突然的用起来了呢,先看看吧:
goog.provide
(google库的方法,主要功能是注册类和方法声明)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 (经验证无错,译者表示困惑,难道这里是按照length去判断的么)
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]);
用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()
,你最好使用赋值的方式。
遍历节点列表
节点列表是通过节点迭代器和一个过滤器来实现的,这表示它的一个属性例如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); }
坚持一致原则。
如果你要编辑代码,先花几分钟看看它的代码风格,如果它这么做,那你也应该这么做。
风格统一了,就有了一个共同思维的环境,参与者就可以专注的看你要说什么,而不是先想你是在说哪星球的语言。 虽然我们在这里提出统一样式规则,但就只是想让大家都知晓并借鉴而对自己的风格进行修正。 当然,保持自己独有的风格也是很重要的。balabala……
修正版本 2.28
译者:chajn