JavaScript 有很多很棒的代码风格指南。因此,没有必要再写一个。相反,本章描述了元代码风格规则,并调查了现有的代码风格指南和已建立的最佳实践。它还提到了我喜欢的、更具争议性的实践。其理念是补充现有的代码风格指南,而不是取代它们。
以下是我喜欢的代码风格指南:
此外,还有两个元代码风格指南
本节将介绍一些通用的代码编写技巧。
var 语句第二条规则是,如果你加入了一个现有项目,你应该严格遵守它的规则(即使你不同意)。
对于大多数代码来说,阅读代码的时间远远大于编写代码的时间。因此,尽可能简化阅读过程非常重要。以下是一些实现此目标的准则
redBalloon 比 rdBlln 更容易阅读。大多数代码库都充满了新的想法和概念。这意味着,如果你想使用一个代码库,你需要学习这些想法和概念。与教科书相比,代码的另一个挑战是,人们不会线性地阅读它。他们会从任何地方跳进去,并且应该能够大致理解发生了什么。代码库的三个部分会有所帮助
很多聪明才智都集中在这些优化上。然而,你通常不需要它们。一方面,JavaScript 引擎正变得越来越智能,并自动优化遵循既定模式的代码的速度。另一方面,代码压缩工具(第 32 章)会重写你的代码,使其尽可能小。在这两种情况下,工具都为你提供了便利,因此你无需自己动手。
有时,你不得不优化代码的性能。如果你这样做,一定要测量和优化正确的部分。在浏览器中,问题通常与 DOM 和 HTML 相关,而不是与语言本身相关。
大多数 JavaScript程序员都同意以下最佳实践:
始终使用严格相等 (===) 和严格不等 (!==)。我建议永远不要违反这条规则。我甚至更喜欢以下两个条件中的第一个,即使它们是等效的:
if(x!==undefined&&x!==null)...// my choiceif(x!=null)...// equivalent
在大括号分隔代码块的语言中,大括号风格决定了大括号的位置。在类 C 语言(如 Java 和 JavaScript)中,两种大括号风格最常见:Allman 风格和 1TBS。
如果一个语句包含一个块,那么该块被认为与语句的头部 somewhat separate:它的左大括号位于单独的一行中,与头部处于相同的缩进级别。例如:
// Allman brace stylefunctionfoo(x,y,z){if(x){a();}else{b();c();}}
在这里,一个块与它的语句头关联更紧密;它从语句头的同一行之后开始。控制流语句的主体总是放在大括号中,即使只有一个语句。例如:
// One True Brace Stylefunctionfoo(x,y,z){if(x){a();}else{b();c();}}
1TBS 是(较旧的)K&R(Kernighan 和 Ritchie)风格的变体。[21] 在 K&R 风格中,函数以 Allman 风格编写,并且在不需要的地方省略大括号,例如,在单语句 then 语句周围
// K&R brace stylefunctionfoo(x,y,z){if(x)a();else{b();c();}}
JavaScript 世界中的事实标准是 1TBS。它继承自 Java,大多数代码风格指南都推荐它。其中一个原因是客观的。如果你返回一个对象字面量,你必须将左大括号与关键字 return 放在同一行,如下所示(否则,自动分号插入会在 return 后面插入一个分号,这意味着什么也不返回;参见 陷阱:ASI 会意外地打断语句)
return{name:'Jane'};
显然,对象字面量不是代码块,但如果两者格式相同,则看起来更一致,并且你出错的可能性更小。
我个人的风格和偏好是
作为一个例外,如果一个语句可以写在一行中,我就会省略大括号。例如
if(x)returnx;
一些构造函数生成的对象也可以由字面量创建。后者通常是更好的选择:
varobj=newObject();// novarobj={};// yesvararr=newArray();// novararr=[];// yesvarregex=newRegExp('abc');// avoid if possiblevarregex=/abc/;// yes
永远不要使用构造函数 Array 来创建具有给定元素的数组。使用元素初始化数组(避免!)解释了原因
vararr=newArray('a','b','c');// never evervararr=['a','b','c'];// yes
本节收集了不推荐的耍小聪明的例子。
不要嵌套条件运算符:
// Don’t:returnx===0?'red':x===1?'green':'blue';// Better:if(x===0){return'red';}elseif(x===1){return'green';}else{return'blue';}// Best:switch(x){case0:return'red';case1:return'green';default:return'blue';}
不要通过逻辑运算符缩写 if 语句:
foo&&bar();// noif(foo)bar();// yesfoo||bar();// noif(!foo)bar();// yes
如果可能,请使用递增运算符 (++) 和递减运算符 (--) 作为语句;不要将它们用作表达式。在后一种情况下,它们会返回一个值,虽然有一个助记符,但你仍然需要思考才能弄清楚发生了什么:
// Unsure: what is happening?return++foo;// Easy to understand++foo;returnfoo;
if(x===void0)x=0;// not necessary in ES5if(x===undefined)x=0;// preferable
从 ECMAScript 5 开始,第二种检查方式更好。更改 undefined解释了原因。
returnx>>0;// noreturnMath.round(x);// yes
移位运算符可以用来将数字转换为整数。但是,通常最好使用更明确的替代方法,例如 Math.round()。转换为整数概述了所有转换为整数的方法。
有时你可以在 JavaScript 中耍小聪明——如果这种聪明才智已经成为一种既定的模式。
如果你泛型地使用方法,你可以将 Object.prototype 缩写为 {}。以下两个表达式是等效的:
Object.prototype.hasOwnProperty.call(obj,propKey){}.hasOwnProperty.call(obj,propKey)
而 Array.prototype 可以缩写为 []:
Array.prototype.slice.call(arguments)[].slice.call(arguments)
我对这一点持矛盾的态度。这是一种 hack(你正在通过实例访问原型属性)。但它减少了混乱,我希望引擎最终能优化这种模式。
对象字面量中的尾随逗号在 ECMAScript 5 中是合法的:
varobj={first:'Jane',last:'Doe',// legal: trailing comma};
让我们看一些我喜欢但有点争议的惯例。
我们将从语法惯例开始
我喜欢相对紧凑的空格。该模型采用书面英语:左括号后和右括号前没有空格。逗号后有空格:
varresult=foo('a','b');vararr=[1,2,3];if(flag){...}
对于匿名函数,我遵循 Douglas Crockford 的规则,即关键字 function 后面有一个空格。理由是,如果您删除名称,这就是命名函数表达式的外观
functionfoo(arg){...}// named function expressionfunction(arg){...}// anonymous function expression
returnresult?result:theDefault;// noreturn(result?result:theDefault);// yes
接下来,我将介绍变量的约定
// novarfoo=3,bar=2,baz;// yesvarfoo=3;varbar=2;varbaz;
这种方法的优点是删除、插入和重新排列行更简单,并且行会自动正确缩进。
var 声明是块级作用域的。换句话说,您可以在使用变量的上下文中声明变量(在循环内、在 then 块或 else 块内等)。这种局部封装使代码片段更容易单独理解。删除代码片段或将其移动到其他位置也更容易。作为上一条规则的补充:不要在两个不同的块中两次声明同一个变量。例如
// Don’t do thisif(v){varx=v;}else{varx=10;}doSomethingWith(x);
前面的代码与以下代码具有相同的效果和意图,这就是为什么要这样编写代码的原因
varx;if(v){x=v;}else{x=10;}doSomethingWith(x);
现在我们将介绍与面向对象相关的约定。
我建议您
new。这样做的主要优点是
对于构造函数,重要的是使用严格模式,因为它可以防止您忘记使用 new 运算符进行实例化。您应该知道您可以在构造函数中返回任何对象。有关使用构造函数的更多技巧,请参阅构造函数实现技巧。
我发现这样的构造函数调用在使用括号时看起来更清晰
varfoo=newFoo;// novarfoo=newFoo();// yes
使用括号,以便两个运算符不会相互竞争——结果并不总是您所期望的:
> false && true || true true > false && (true || true) false > (false && true) || true true
instanceof 特别棘手
> ! {} instanceof Array
false
> (!{}) instanceof Array
false
> !({} instanceof Array)
true但是,我发现构造函数后的方法调用没有问题
newFoo().bar().baz();// ok(newFoo()).bar().baz();// not necessary
本节收集了各种技巧
通过 Boolean、Number、String()、Object()(用作函数——切勿将这些函数用作构造函数)将值强制转换为类型。理由是此约定更具描述性:
> +'123' // no
123
> Number('123') // yes
123
> ''+true // no
'true'
> String(true) // yes
'true'this 作为隐式参数
this 应该仅指代当前方法调用的接收者;它不应该被滥用作隐式参数。理由是,此类函数更容易调用和理解。我还喜欢将面向对象和函数式机制分开:
// Avoid:functionhandler(){this.logError(...);}// Prefer:functionhandler(context){context.logError(...);}
in 和 hasOwnProperty 检查属性是否存在(请参阅属性的迭代和检测)这比与 undefined 进行比较或检查真值更具自我解释性且更安全
// All properties:if(obj.foo)// noif(obj.foo!==undefined)// noif('foo'inobj)...// yes// Own properties:if(obj.hasOwnProperty('foo'))...// risky for arbitrary objectsif(Object.prototype.hasOwnProperty.call(obj,'foo'))...// safe
每当您考虑样式问题时,请问问自己:是什么让我的代码更容易理解?抵制耍小聪明的诱惑,将大部分机械上的聪明才智留给 JavaScript 引擎和压缩器(请参阅第 32 章)。