文章由于要拿来讨论制定小组开发规范和用于特定的场景,会比较“详尽",没有相应团队需求的掘友,节省时间,可以到别处掘金了。
随着组内新的小伙伴的加入,大家的编码风格又有向单兵作战的方向“进化”的趋势,重读一下《编写可维护的 JavaScript》,结合大家的习惯,提出一些可以讨论的点(文中用❓标出),和应该遵循的规范(👌标出),供小组讨论。特别同意作者提出的编码规范如此重要的原因。
- 软件生命周期中80%的成本消耗在了维护上。
- 几乎所有的软件维护者都不是它的最初作者。
- 编码规范提高了软件的可读性,它让工程师能够快速且充分的理解新的代码。
基本的格式化
缩进层级(👌Tab键配置4空格缩进)
- 缩进方式-建议制表符缩进。缩进方式有制表符缩进和空格缩进,可配置敲击Tab键插入对应数量空格,操作方便。
- 缩进数量-建议制表符配置四空格缩进。当前常用的编辑器默认4空格缩进,也可配置敲击Tab键插入四个空格。四空格缩进有相对良好的代码呈现,并且兼顾8空格缩进和2空格缩进的伙伴。
👮♀️坚决避免单文件混用缩进方式。👮♀️
语句结尾(❓ 使用分号)
有赖于分析器自动分号插入机制(Automatic Semicolon Insertion, ASI),通常情况下可以正常工作,但ASI补全规则复杂,特定情况下容易出错,因此建议不省略分号。
以下代码会打印 undefined,不符合函数返回一个包含数据的对象的本意。
// 原始代码
const asi = () => {
return
{
intent: "收银设备损坏怎么办",
id: 1
};
}
console.log(asi());
// 分析器分析后代码
const asi = () => {
return;
{
intent: "收银设备损坏怎么办",
id: 1
};
}
console.log(asi());
行长度(👌80个字符)
由于编辑器宽度限制,单行代码过长,要么折行,要么被隐藏起来需要滚动滚动条才能看到,更不友好的是折行设置不同对代码可读性造成影响。
| 语言 | 单行限制字数 |
|---|---|
| Android | 100 |
| Java | 80 |
| Ruby | 80 |
Crokford 的JavaScript代码规范中指定一行80
适当使用空行(❓ 结合当前业务逻辑,什么情况下使用空行)
对于空行的使用没有一个标准,只有一些建议:
- 在方法之间
- 在方法中的局部变量和第一条语句之间。
- 在多行或者单行注释之前。
- 在方法内的逻辑片段之间,提高可读性。
命名(👌驼峰式、合理使用动或名词、常量特殊命名)
"驼峰式大小写“有两种-Camel Case (首字母小写式驼峰命名) 和 Pascal Case(首字母大写式驼峰命名)。
变量和函数
变量名应当遵循因当遵循驼峰命名法,命名前缀应该是名词,用以区分函数,函数前缀应该是动词。
let intentName = "收银设备损坏怎么办";
let found = true;
function getIntentName() {
return intentName;
}
命名通常要短并抓住要点,尽量体现出值的类型,如,命名count、length和size表明值类型是数字,命名name、title和message表明值类型是字符串。
对于函数和方法命名,第一个单词应该是动词,有如下一些常见约定。
| 动词 | 含义 |
|---|---|
| can | 返回Boolean |
| has | 返回Boolean |
| is | 返回Boolean |
| get | 返回非布尔值 |
| set | 保存一个值 |
if (hasIntent) {
setIntentName("收银设备损坏怎么办");
}
if (getIntentName === "收银设备损坏怎么办") {
doSomething();
}
常量
js 的常量命名约定源自于C语言,使用下划线和大写字母来命名,下划线用一份个单词。
let MAX_COUNT = 100;
let BASE_URL = "https://www.guoran.com";
构造函数
沿用语言内置的构造函数的命名方法-大写式驼峰命名,比如Object、RegExp。
直接量(👌精确使用一些类型原始值)
- 字符串:推荐双引号。
- 数字:十进制有小数部分,要完整写整数和小数部分,避免使用八进制。
- null:用来初始化一个变量,这个变量可能赋值为一个对象,当函数参数期望是对象时,用作参数传入,当函数的返回值期望是对象时,用作返回值传出。
- undefined: 表示变量声明等待被赋值。
null使用时应该将它当做对象的占位符。将变量初始值赋值为null表明这个变量的意图,它最终可能赋值为对象。typeof运算符null的类型时返回”object“,区分undefined。
let intent = null;
let skill;
typeof(intent); // object
typeof(skill); // undefined
注释
什么情况下使用注释( ❓当代码不够清晰时)
注释并不是越多越好,简单,对阅读者不会产生歧义的代码不需要写注释。注释应该被加在必要的地方。
- 难于理解的代码。
- 可能被误认为错误的代码。
- 浏览器特性hack。
- 特殊含义的常量。
- 只应用于当前业务逻辑。
注释类型(👌单行、多行)
单行注释
// 单行注释
当行注释可以独占一行,用来解释下一行的代码,在注释之前加一个空行,且缩进和下一行代码保持一致,也可以在代码的行尾注释,在代码和注释之间加一个缩进。如果注释和当前行代码数量超过了80个字符,则使用第一种方式注释。
if (condition) {
// 符合条件
doSomething();
}
多行注释
多行注释之前应当有一个空行。
doSomething()
/*
* 多行注释第一行
* 多行注释第二行
*/
👮禁止在行内使用多行注释👮
语句和表达式
所有的块状语句都应该使用花括号,包括 if、for、while、do...while,特别是if语句省略花括号,时常会造成困惑,代码的优雅并不在于看起来简洁,而在于带来更好的可读性,造成更少的困惑。推荐块状语句之前加空行。
// 容易造成困惑的代码
if (condition)
// 符合条件
doSomething();
doSomethingElse();
// 好的写法
if (condition) {
doSomething();
}
doSomethingElse();
花括号的对齐方式(👌左花括号放在块语句中第一句代码末尾)
if (condition) {
doSomething();
} else {
doSomethingElse();
}
块语句间隔(👌采用Crockford推荐方式)
在左括号之前和右括号之后添加一个空格。
if (condition) {
doSomething();
}
switch 语句( ❓关于连续执行和default)
在逻辑和结构清晰地情况下可以使用连续执行,特别是有注释标明的情况下。对于default语句,作者的建议是如果default内没有逻辑,可以省略,❓我倾向于default为switch语句的一部分。
switch (condition) {
case 'first':
// 逻辑代码
break;
case 'second':
// 逻辑代码
break;
default: // 没有逻辑,但是没有省略,倾向于default为switch语句的一部分
break;
}
for循环(👌关于更改循环进程的建议)
有两种方法可以更改循环执行过程(除了使用return和throw语句)。第一种方式是使用break,不管所有的循环迭代有没有执行完毕,使用break总是可以跳出循环,第二种是continue,可以立即退出本次循环,进入下一次循环,❓ continue由于部分js检测工具会警告,建议用if替代。
let array = [1, 2, 3, 4, 5, 6, 7];
// break
for (let i = 0; i < array.length; i++) {
if (i === 2) {
break;
}
console.log(array[i]); // 1 2
}
// continue
for (let i = 0; i < array.length; i++) {
if (i === 2) {
continue;
}
console.log(array[i]); // 1 2 4 5 6 7
}
// continue => if
for (let i = 0; i < array.length; i++) {
if (i !== 2) {
console.log(array[i]); // 1 2 4 5 6 7
}
}
for-in 循环(👌循环中的hasOwnProperty)
for-in 循环不仅遍历对象的实例属性,同样也遍历从原型链继承来的属性,遍历从原型链继承来的属性时,往往意外终止,出于这个原因,建议使用hasOwnProperty()方法来过滤出实例属性。
let object = {
name: 'Quasi',
age: 18
};
for (const key in object) {
if (Object.hasOwnProperty.call(object, key)) {
const element = object[key];
console.log(key, element); // name Quasi age 18
}
}
👮禁止使用for-in遍历数组成员👮
变量、函数和运算符
变量/函数声明(👌变量声明提升)
变量声明提前意味着:在函数内部任意地方定义变量/函数和在函数顶部定义变量是完全一样的。但是为了提高可读性和兼容代码检测工具,建议总是在使用之前定义变量/函数。❓ 关于单let语句,作者建议使用单let语句,但是为了代码有可读性,我倾向于Dojo编程风格指南规定,只有在变量之间有关联时,再去使用单let语句。
立即调用函数(👌圆括号包裹立即执行函数,提高可读性)
const doSomething = (() => {
// 逻辑代码
})();
相等(👌谨慎使用相等强制类型转换)
比较相等时应当使用 ===和!==,避免使用 ==和!=发生强制类型转换。
// == 字符串被强制转换为数字
console.log(8 == "8") // true
console.log(8 === "8") // false
// == 布尔值强制转换为数字
console.log(true == 1) // true
console.log(true === 1) // false
// == 十六进制强制转换为十进制进行比较
console.log(25 == "0x19") // true
console.log(25 === "0x19") // false
// == null 和undefined
console.log(null == undefined) // true
console.log(null === undefined) // false