GoogleJavaScript编码规范指南修订版:2.9AaronWhyteBobJervisDanPupiusEricArvidssonFritzSchneiderRobbyWalker目录JavaScript语言规范变量常量分号嵌套函数块内函数声明异常自定义异常标准特性封装基本类型多级原型结构方法定义闭包eval()with(){}thisfor-in循环关联数组多行字符串Array和Object直接量修改内置对象的原型IE下的条件注释JavaScript编码风格命名自定义toString()方法延迟初始化明确作用域代码格式化括号字符串可见性(私有域和保护域)JavaScript类型注释编译TipsandTricks重要注意事项显示被隐藏的内容link▽这份指南中,可以点击旁边的按钮来显示更多的细节.Hooray!这里是更多详细的内容,你也可以点击最上面的”显示/隐藏全部按钮”来切换显示更多内容.背景JavaScript是一种客户端脚本语言,Google的许多开源工程中都有用到它.这份指南列出了编写JavaScript时需要遵守的规则.JavaScript语言规范变量link▽声明变量必须加上var关键字.Decision:当你没有写var,变量就会暴露在全局上下文中,这样很可能会和现有变量冲突.另外,如果没有加上,很难明确该变量的作用域是什么,变量也很可能像在局部作用域中,很轻易地泄漏到Document或者Window中,所以务必用var去声明变量.常量link▽常量的形式如:NAMES_LIKE_THIS,即使用大写字符,并用下划线分隔.你也可用@const标记来指明它是一个常量.但请永远不要使用const关键词.Decision:对于基本类型的常量,只需转换命名./***Thenumberofsecondsinaminute.*@type{number}*/goog.example.SECONDS_IN_A_MINUTE=60;对于非基本类型,使用@const标记./***Thenumberofsecondsineachofthegivenunits.*@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(){return42;}//Nosemicolonhere.(function(){//Someinitializationcodewrappedinafunctiontocreateascopeforlocals.})();varx={'i':1,'j':2}//Nosemicolonhere.//2.TryingtodoonethingonInternetExplorerandanotheronFirefox.//Iknowyou'dneverwritecodelikethis,butthrowmeabone.[normalVersion,ffVersion][isIE]();varTHINGS_TO_EAT=[apples,oysters,sprayOnCheese]//Nosemicolonhere.//3.conditionalexecutionalabash-1==resultOfOperation()||die();这段代码会发生些什么诡异事呢?1.报JavaScript错误–例子1上的语句会解释成,一个函数带一匿名函数作为参数而被调用,返回42后,又一次被”调用”,这就导致了错误.2.例子2中,你很可能会在运行时遇到‘nosuchpropertyinundefined’错误,原因是代码试图这样x[ffVersion][isIE]()执行.3.当resultOfOperation()返回非NaN时,就会调用die,其结果也会赋给THINGS_TO_EAT.为什么?JavaScript的语句以分号作为结束符,除非可以非常准确推断某结束位置才会省略分号.上面的几个例子产出错误,均是在语句中声明了函数/对象/数组直接量,但闭括号(‘}’或’]')并不足以表示该语句的结束.在JavaScript中,只有当语句后的下一个符号是后缀或括号运算符时,才会认为该语句的结束.遗漏分号有时会出现很奇怪的结果,所以确保语句以分号结束.嵌套函数link▽可以使用嵌套函数很有用,比如,减少重复代码,隐藏帮助函数,等.没什么其他需要注意的地方,随意使用.块内函数声明link▽不要在块内声明一个函数不要写成:if(x){functionfoo(){}}虽然很多JS引擎都支持块内声明函数,但它不属于ECMAScript规范(见ECMA-262,第13和14条).各个浏览器糟糕的实现相互不兼容,有些也与未来ECMAScript草案相违背.ECMAScript只允许在脚本的根语句或函数中声明函数.如果确实需要在块中定义函数,建议使用函数表达式来初始化变量:if(x){varfoo=function(){}}异常link▽可以你在写一个比较复杂的应用时,不可能完全避免不会发生任何异常.大胆去用吧.自定义异常link▽可以有时发生异常了,但返回的错误信息比较奇怪,也不易读.虽然可以将含错误信息的引用对象或者可能产生错误的完整对象传递过来,但这样做都不是很好,最好还是自定义异常类,其实这些基本上都是最原始的异常处理技巧.所以在适当的时候使用自定义异常.标准特性link▽总是优于非标准特性.最大化可移植性和兼容性,尽量使用标准方法而不是用非标准方法,(比如,优先用string.charAt(3)而不用string[3],通过DOM原生函数访问元素,而不是使用应用封装好的快速接口.封装基本类型link▽不要没有任何理由去封装基本类型,另外还存在一些风险:varx=newBoolean(false);if(x){alert('hi');//Shows'hi'.}除非明确用于类型转换,其他情况请千万不要这样做!varx=Boolean(0);if(x){alert('hi');//Thiswillneverbealerted.}typeofBoolean(0)=='boolean';typeofnewBoolean(0)=='object';有时用作number,string或boolean时,类型的转换会非常实用.多级原型结构link▽不是首选多级原型结构是指JavaScript中的继承关系.当你自定义一个D类,且把B类作为其原型,那么这就获得了一个多级原型结构.这些原型结构会变得越来越复杂!使用theClosure库中的goog.inherits()或其他类似的用于继承的函数,会是更好的选择.functionD(){goog.base(this)}goog.inherits(D,B);D.prototype.method=function(){...};方法定义link▽Foo.prototype.bar=function(){...};有很多方法可以给构造器添加方法或成员,我们更倾向于使用如下的形式:Foo.prototype.bar=function(){/*...*/};闭包link▽可以,但小心使用.闭包也许是JS中最有用的特性了.有一份比较好的介绍闭包原理的文档.有一点需要牢记,闭包保留了一个指向它封闭作用域的指针,所以,在给DOM元素附加闭包时,很可能会产生循环引用,进一步导致内存泄漏.比如下面的代码:functionfoo(element,a,b){element.onclick=function(){/*usesaandb*/};}这里,即使没有使用element,闭包也保留了element,a和b的引用,.由于element也保留了对闭包的引用,这就产生了循环引用,这就不能被GC回收.这种情况下,可将代码重构为:functionfoo(element,a,b){element.onclick=bar(a,b);}functionbar(a,b){returnfunction(){/*usesaandb*/}}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对象:varuserOnline=false;varuser='nusrat';varxmlhttp=newXMLHttpRequest();xmlhttp.open('GET','='+user,false);xmlhttp.send('');//Serverreturns://userOnline=true;if(xmlhttp.status==200){eval(xmlhttp.responseText);}//userOnlineisnowtrue.with(){}link▽不要使用使用with让你的代码在语义上变得不清晰.因为with的对象,可能会与局部变量产生冲突,从而改变你程序原本的用义.下面的代码是干嘛的?with(foo){varx=3;returnx;}答案:任何事.局部变量x可能被foo的属性覆盖,当它定义一个setter时,在赋值3后会执行很多其他代码.所以不要使用with语句.thislink▽仅在对象构造器,方法,闭包中使用.this的语义很特别.有时它引用一个全局对象(大多数情况下),调用者的作用域(使用eval时),DOM树中的节点(添加事件处理函数时),新创建的对象(使用一个构造器),或者其他对象(如果函数被call()或apply()).使用时很容易出错,所以只有在下面两个情况时才能使用:在构造器中对象的方法(包括创建的闭包)中for-in循环link▽只用于object/map/hash的遍历对Array用for-in循环有时会出错.因为它并不是从0到length-1进行遍历,而是所有出现在对象及其原型链的键值.下面就是一些失败的使用案例:functionprintArray(arr){for(varkeyinarr){print(arr[key]);}}printArray([0,1,2,3]);//Thisworks.vara=newArray(10);printArray(a);//Thisiswrong.a=document.getElementsByTagName('*');printArray(a);//Thisiswrong.a=[0,1,2,3];a.buhu='wine';printArray(a);//Thisiswrongagain.a=newArray;a[3]=3;printArray(a);//T