编程风格 - 第 1 章 基本的格式化
《编写可维护的 JavaScript》—— Nicholas C. Zakas
编程风格指南的核心是基本的格式化规则(formatting rule)。
这些规则直接决定了如何编写高水准的代码。与写字时所用的方格纸类似。
1. 缩进层级
两种缩进方式:
-
制表符(tab character)
-
好处
- 一个制表符一个缩进层级,一对一的关系
- 文本编辑器可以配置制表符的展现长度
-
坏处
- 系统对制表符的解释不一致,有的长有的短
-
-
空格:每个缩进层级由多个空格字符组成,2 或 4 或 8 空格代表一个层级
- 好处:所有的系统和编辑器中,文件的展现格式不会有差异
- 坏处:需要配置编辑器的 Tab 键插入的空格数量
缩进风格参考:
- jQuery Core Style Guide: 制表符
- Douglas Crockford’s Code Conventions for the JavaScript Programming Language: 4 个空格
- SproutCore Style Guide: 2 个空格
- Google JavaScript Guide: 2 个空格
- Dojo Style Guide: 制表符
推荐:
- 使用 4 个空格作为一个缩进,很多编辑器默认的缩进设置为 4 个空格
- 使用 2 个空格,代码的视觉展现并不是最优的
注意:
- 选择制表符还是空格做缩进只是个人的偏好,但是绝对不要混用
2. 语句结尾
JavaScript 的语句要么独占一行,要么以分号结尾。
有赖于分析器的自动分号插入机制(Automatic Semicolon Insertion,ASI),JavaScript 代码省略分号也是可以正常工作的。
但当某个场景我们任务不需要插入分号而 ASI 认为需要插入时,常常会产生错误。
3. 行的长度
如果一行代码太长,编辑窗口出现了横向滚动条,会让开发人员感觉很别扭。
很多语言的编程规范都提到了一行代码最长不应该超过 80 个字符。
这个数值来源于很久之前文本编辑器的单行最多字符限制,即编辑器中单行最多只能显示 80 个字符,
超过 80 个字符的行要么折行,要么被隐藏起来。
4. 换行
当一行长度达到了单行最大字符数限制时,就需要手动将一行拆成两行。
通常在运算符后换行,下一行会增加两个层级的缩进,比如:(两个空格为一个缩进层次)
// 1. 在运算符后换行;2. 第二行额外加两个缩进
callAFunction(document, element, window, 'some string value', true, 123,
navigator);
if (isLeapYear && isFebruary && day === 29 &&
noPlans) {
waitAnotherFourYears();
}
// 上下行对齐以保证可读性
var result = something + anotherThing + yetAntherThing +
antherSomethingElse;
为什么要在运算符后换行?
因为 ASI 机制有时会在行结束的位置插入分号,
总是将运算符置于行尾,ASI 就不会自作主张地插入分号,也就避免了错误了发生。
5. 空行
在编程规范中,空行是常常被忽略的一个方面。
通常来讲,代码看起来应当是一系列可读的段落,而不是一大段揉在一起的连续文本。
有时一段代码的语义和另一段代码不相关,这时就应该使用空行将它们分隔,确保语义有关联的代码展现在一起。
// 在每个流程控制语句之前添加空格
if (wl && wl.length) {
for (i = 0, l = wl.length; i < l; i++) {
p = wl[i];
type = Y.Lang.type(r[p]);
if (s.hasOwnProperty(p)) {
if (merge && type === 'object') {
Y.mix(r[p], s[p]);
} else if (ov || !(p in r)) {
r[p] = s[p];
}
}
}
}
一般来讲,在下面的场景中添加空行也是不错的注意:
- 在注释(单行或多行)之前
- 在方法之间
- 在方法中的变量声明和第一条语句之间
- 在方法内的逻辑片段之间
6. 命名
“计算机科学只存在两个难题:缓存失效和命名。” —— Phil Karlton。
只要写代码,都会涉及到变量和函数,因此变量和函数命名对增强代码可读性至关重要。
JavaScript 语言的核心 ECMAScript,遵循了驼峰式(Camel Case)命名法。
- Camel Case(小驼峰式):首字符小写,后续每个单词首字母都大写。
- Pascal Case(大驼峰式):首字符大写,后续每个单词首字母都大写。
比如:
var thisIsMyName;
var anotherVariable;
var aVeryLongVariableName;
匈牙利命名法:
在名字之前添加类型标识符前缀,比如 sName
(字符串类型)、iCount
(整形类型)、oPerson
(对象类型)。
这种风格在 2000 年左右还比较流行,现在主流的编程规范都不推荐这种命名法。
7. 变量和函数
变量名应当遵循小驼峰式命名,且前缀为名词以区分函数(函数以动词为前缀)。比如:
// 好的写法
var count = 10;
var myName = 'Nicholas';
var found = true;
// 不好的写法:变量看起来像函数
var getCount = 10;
var isFound = true;
// 好的写法
function getName() {
return myName;
}
// 不好的写法: 函数看起来像变量
function theName() {
return myName;
}
通常来讲,命名长度应该尽可能短,并抓住要点。
尽量在变量中体现出值的数据类型,
如,count、length、size 表明数据类型是数字,name、title、message 表明数据类型是字符串。
单个字符命名的变量(i、j、k)通常在循环中使用。
使用这些能够体现数据类型的命名,可以让你的代码容易被别人和自己读懂。
避免使用没有意义的命名,如 foo、bar、tmp。
对于函数或方法命名来说,第一个单词应该是动词,这里有常用的动词约定:
- can: 返回一个布尔值
- has: 返回一个布尔值
- is: 返回一个布尔值
- get: 返回一个非布尔值
- set: 返回一个非布尔值
使用这些约定可以使代码可读性更好,如:
if (isEnabled()) {
setName('Nicholas');
}
if (getName() === 'Nicholas') {
doSomething();
}
8. 常量
在 ES6 之前,JavaScript 中并没有真正的常量的概念,然而这并不能阻止开发者将变量用作常量。
为了区分普通的变量(其值可变)和常量(初始化后其值不可变),一种通用的命名约定诞生了。
这个约定源于 C 语言,使用大写字母和下划线来命名,下划线用于分隔单词,比如
var MAX_COUNT = 10;
if (count < MAX_COUNT) {
doSomething();
}
使用这种不同的约定来定义普通的变量和常量,使两者非常易于区分。
上面的代码中,一眼就能看出 count
是变量,MAX_COUNT
是常量。
这个约定为代码(underlying code)增加了另外一层语义。
9. 构造函数
在 JavaScript 中,构造函数只不过是通过 new 运算符来调用的函数,用以创建对象。
语言本身已经包含了很多内置构造函数,比如 Object
、RegExp
;开发者也可以创建自己的构造函数来生成新类型。
构造函数的命名遵循大驼峰(Pascal Case)命名法,将构造函数从变量和普通函数中区分开来。
构造函数的命名也常常是名词,因为它们是用来创建某个类型的实例的,如下
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
alert(this.name);
};
var me = new Person('吴钦飞');
以大驼峰命名的函数如果是名词的话,前面一定有 new 运算符,遵循这条约定可以帮我们快速定位问题,如下:
var me = Person('吴钦飞');
var you = getPerson('哇哈哈');
根据命名约定,一眼就能看出第一行出问题了。
10. 直接量
JavaScript 中包含了一些类型的原始值:字符串、数字、布尔值、null、undefined。同样也包含对象直接量、数组直接量。
这其中,只有布尔值是自解释的(self-explanatory)的,其他的类型的值则需要思考一下如何才能更准确地表达出来。
11. 字符串
在 JavaScript 中,字符串可以用双引号括起来,也可以用单引号括起来,比如:
var name = "Nicholas says, \"Hi.\"";
var name = 'Nicholas says, "Hi."';
字符串界定符(string delimiter)使用双引号还是单引号在功能上并无区别,你的代码应当从头到尾只保持一种风格。
多行字符串,如下:
// 不好的写法
var longString = "Here's the story, of a man \
named Brady.";
尽管从技术上讲这种写法是非法的 JavaScript 语法,但的确能在代码中创建多行字符串。
通常不推荐使用这种写法,因为它是一种奇技淫巧而非语言特性。
多行字符串的一种替代写法是使用字符串连接符(+)将字符串分成多份。
var longString = "Here's the story, of a man " +
"named Brady.";
12. 数字
在 JavaScript 中的数字类型只有一种,因为所有的数字形式(整数和浮点数)都存储为相同的数据类型。
但有一些写法有问题:
// 整数
var count = 10;
// 小数
var price = 10.0;
var price = 10.00;
// 不推荐的写法:没有小数部分
var price = 10.;
// 不推荐的写法:没有整数部分
var price = .1;
// 不推荐的写法:八进制写法已经被废弃了
var num = 010;
// 十六进制写法
var num = 0xA2;
// 科学计数法
var num = 1e23;
前两种错误的写法(省略了整数部分或小数部分),很难搞清楚省略的部分是不小心丢掉了还是刻意为之。
因此为了避免歧义,请不要省略小数点之前或之后的数字。
数字直接量 010 不是表示 10,而是八进制中的 8。
大多数开发者对八进制格式并不熟悉,也很少用到,所有最好的做法就是在代码中禁止八进制直接量。
13. null
理解 null
最好的方式就是将它当做对象的占位符(placeholder)。
null
是一个特殊值,但我们常常误解它,将它和 undefined
搞混,在下列场景中应当是 null
。
- 初始化一个对象类型的变量
- 当函数的参数期望是对象时
- 当函数的返回值期望是对象时
14. undefined
undefined
是一个特殊值,容易与 null
搞混,因为 null == undefined; //=> true
。
未被初始的变量的变量都有一个初始值,即 undefined
,表示这个变量等待被赋值。
建议避免在代码中使用 undefined
,
因为 typeof
的操作数无论是值为 undefined
的变量还是未声明的变量,其结果都是 "undefined"
。
var person;
console.log(typeof person); //=> "undefined"
// foo 未声明
console.log(typeof foo); //=> "undefined"
通过禁止使用特殊值 undefined
,可以确保只在变量未声明的情况下 typeof 才会返回 "undefined"
。
15. 对象直接量
创建对象推荐使用对象直接量,在直接量中写出所有属性。
不推荐先通过 Object
构造函数插件实例后,再添加属性。
// 不好的写法
var book = new Object();
book.title = 'Maintainable JavaScript';
book.author = 'Nicholas C. Zakas';
// 好的写法
var book = {
title: 'Maintainable JavaScript',
author: 'Nicholas C. Zakas',
};
16. 数组直接量
推荐使用数组直接量定义数组;不推荐使用 Array
构造函数来创建数组。
// 不好的写法
var colors = new Array('red', 'green', 'blue');
var numbers = new Array(1, 2, 3, 4);
// 好的写法
var colors = ['red', 'green', 'blue'];
var numbers = [1, 2, 3, 4];