Appearance
Javascript 编码规范
[TOC]
术语
- 原则: 必须坚持的指导思想
- 建议: 需要加以考虑的约定
- 不好示例: 反面教材,里面的都是错误示范
- 推荐示例: 正面教程,可以按照里面示例类推
命令
文件
建议: 不使用index文件实现逻辑,index用于聚合导出。
避免componentA/index.js,然后在index.js 写component的逻辑,正确的写法是 componentA/componentA.js。 componentA/index.js 仅作导出。当你在浏览器resource查找文件就知道了-__-。
命名原则
原则: 源文件编码格式(包括注释)必须是UTF-8
大家一定被乱码的解释折磨过....
原则: 方法、变量需要一个好名字
好的名字有包括不局限以下特征:
- 清晰表达意图:使用有描述性的型单词,避免使用单个字母,或者自创缩写来表达,比如 let lastDay 比 let ld 要好
- 必须使用英文单词,不允许出现中文拼音(约定熟成除外)。中文拼音在多音字表现上会有歧义,而且不太利于团队以后成成长
- 避免误导性命名,有误导的命名比表达不清晰还要危险,比如 let nameArray 不是一个array类型,而是一个字符串。
- 能区分出意思,建议不要在变量后面加上data,info,object等一般意思的词。比如productInfo和productData有什么区别。
类
原则: 构造函数、类命名都是用大驼峰
推荐示例
js
function Kind() {}
class People() {}不好示例
js
function kind() {}
class people() {}原则: 方法命名使用动词、动宾结构,并遵守驼峰原则
格式如下
- get + 非布尔属性名
- is + 布尔属性名
- set + 属性名
- has + 名字\形容词
- 动词
- 动词 + 宾语 不好的示例
js
function type() {}
function Finished() {}
function visible() {}
function DRAW() {}
function keyListener() {}推荐示例:
js
function getType() {}
function isFinished() {}
function setVisible() {}
function draw() {}
function addKeyListener() {}原则: 方法名不能过长影响可读性
方法名不能超过15个字符,可以采用去掉元音字符或者业界约定的缩写来表示。 比如 function getCustomerInfomation() {} 可以改成 function getCustomerInfo() {}
建议: 私有属性或者方法, 建议以下划线_开头
变量
原则: 变量名遵循驼峰风格, 首字母大写,后续每个单词小写
建议: 避免使用否定的布尔变量名
对于否定的布尔变量名,当使用逻辑非运算符时,会出现双重否定,对阅读造成阻碍,比如!isNotError。
不好示例:
js
const isNotError = true; // 到底有没有错
const inNotFinished = false; // 结束没有推荐示例
js
const isError = false;
const inNotFinished = true;建议: 缩写词应该全部大写或者小写,不要混搭。
原则: 不要用保留字作为键名或者变量名,使用同义词。
为了兼容未来,虽然在语法上使用保留字作为键名是可以允许的。可以这样改class => klass, default => defaults,private => hidden
原则: 常量使用全部大写并且用下划线_分隔
- 不要魔鬼数字,比如错误码
response.code === "0000" || response.code === 5, 这种代码,不熟悉的人需要花时间去理解5是什么,"0000"又是什么。 - 不要魔鬼常量,比如
const NUM_FOUR = 4这种脱了裤子放屁行为; 推荐示例
js
const SUCCESS_CODE = '0000';
if (response.code === SUCCESS_CODE) {
....
}
const MAX_GOODS_NUM = 4;注释
注释原则
最好的注释就是没有注释
注释的作用是解释代码。如果变量命令足够清晰,代码结构好就不需要注释。举个例子
js
// 大于65岁并且是职工,或者性别是女可以领福利
if ((person.flag === 0 && person.age > 65) || person.sex === 1) {
....
}别人审阅读代码时需要看注释才能知道if语句中含义。可以换成如下写法
js
if (isBenefits(person)) {
...
}直接通过阅读代码就可以理解到意思,如果需要深读判断逻辑,同时隔离的函数也方便阅读。
原则: 注释的作用是帮助理解代码,而不是误解代码
无用的注释,过期的注释及时清理掉。如果一个注释和代码实现不一致,那么enummmm....。
原则: 不要出现todo代码,todo代码自己在个人备忘录记录。
大家习惯把部分代码标记上todo,然后推到公共分支。如果某一天你不接手这个项目了,后面的人看到你写的todo,他到底需不要todo,这是个问题。
建议: 不要临时注释屏蔽代码,直接删除。
临时注释屏蔽部分代码是大部分人常见的操作,可以直接删除。如果后续需要找回,git来帮忙。否则后面的人接手一看,这个代码屏蔽注释了,不知道啥原因,不敢动,久而久之,x山。
原则: 不要用注释记录文件修改信息
都用git了, 不要在文件开头去记录xxx多久修改了啥。git信息里面都有。
注释风格
原则: 单行注释使用 //, 多行注释 /** */, 文件注释用 /* */
推荐大家使用jsDoc风格的注释规范
原则: 注释和描述的代码相邻,放在代码上方,并且和代码的缩进一致。
原则: 注释要和上方的代码块有间隔,间隔一行。 // 后空两格。
排版
风格
排版可以从下面的维度出发,给出每个维度的建议项和原由,各团队可以自行商议。
建议: 团队内风格应该保持一致,大家商量,抛弃个人喜好
建议: 团队内将风格固化到IDE中,通过eslint,stylelint, editorConfig等来保证一致性。
缩进
原则: 禁止使用制表符作为缩进
制表符在不同IDE或者代码阅读器上面表现的风格不一致,有可能产生对齐错乱的问题。空格在不会有这样的问题。我们在开发中常使用tab键实现缩进,需要把tab设置为缩进空格。
建议: 缩进使用2个空格缩进。
缩进过大,在嵌套过深的情况,需要拖动横向滚动条条才能阅读代码,不方便阅读。
换行
建议: 超长代码需要换行,换行时操作符放在前面
推荐示例
js
const resultA = xxx
? 'a' : 'b';
const resultB = xxxxxxxxxxxxA
&& xxxxxxxxxxB
&& xxxxxxxxxxC建议: 函数参数尽量在同一行,对于超长的场景每个参数独占一行。
推荐示例
ts
function someFunction(arg1, arg2, arg3, arg4) {}
function someLongerFunction(
xxxxxxxxxxA,
xxxxxxxxxxB,
xxxxxxxxxxC,
xxxxxxxxxxD
) {}建议: 对象字面量超过4个,都应该换行
不好的示例
js
const airticle = { title: '中国之声', author: 'some people', date: '2012-02-01', preview: 999, total: 334343 };推荐示例
ts
const airticle = {
title: '中国之声',
author: 'some people',
date: '2012-02-01',
preview: 999,
total: 334343
};建议: 链式调用对象时,一行最好不超过4个调用,可以改成每个独占一行。
不好示例
ts
someInstance.queryAll().find('classA').forEach((item) => handle(item)).map(item => item.name).end()推荐示例
ts
someInstance
.queryAll()
.find('classA')
.forEach((item) => handle(item))
.map(item => item.name)
.end()建议: else 和 if尾括号放在同一行,else if同理
推荐示例
ts
if () {
....
} else {
....
}不好示例
ts
if () {
}
else {
}建议: 设置每行的最大长度,超过最大行数需要换行。
阅读代码最喜欢时从上到下阅读,不喜欢横向滚动条。推荐每行最大长度不超过180
建议: 条件语句过长的,可以将每个条件都放入当都一行,操作符放在开头
空行
原则: 不同的逻辑块之间空一行
相对独立的代码块之间考虑换行(一般这个时候你需要提炼函数了-_-)。
建议: class中,方法和方法之间需要换行。不同逻辑区间的属性之间换行。
ts
class People {
age: 36,
sex: 'male',
career: 'teacher',
isFlag: false,
isRegisted: true
children: [],
parent: {}
checkValid() {
...
}
handleError() {
...
}
}原则: 方法体、块语句、类的开始或者末尾不要有空行。
建议: 一般情况下,不要使用连续空行。
空格
建议: 关键字周围空格一致性
- 在保留字(if, for while) 和左括号( 之间添加一个空格
- else catch 与 关闭花括号 } 之间添加一个空格
- 在任何打开花括号前添加一个空格, (函数参数和``模板中去除外)
ts
// 下面这三种情况除外
function too({ name }) {}
function foo([ name ]) {}
const str = `#${s}`;- 在任何三元或者二元操作符的两侧增加一个空格
- 数组和函数参数逗号, 后面添加一个空格,前面不要增加空格。
- ; 前面不要空格
- 单行 {} 内侧需要一个空格
ts
const objA = { name: 'xxx', total: 2 }- 数组[]内侧不要有空格 [a, b, c],不要[ a, b ]。
- 禁止出现多个空格,除注释和缩进外。
花括号
建议: 总是给代码执行体添加{}
while, if, for, do,即使执行语句只有一行。
ts
if (result) foo++;上面这种看着很酷,如果需要多写一个语句,就需要三行的改动,如果我们事先添加好花括号。改动只有一行,出错的机率大幅度减低。血的教训——__——。
建议: 花括号要和语句在一行
ts
class Xxx {
...
}不好示例
ts
class Xxx
{
....
}建议: 对象字面量和数组中使用拖尾逗号,
ts
const obj = {
keyA: 'xxx',
keyB: 'xxx', // 拖尾逗号
}
const someArray = ['xxxA', 'xxxB',]建议: 每行代码句后添加分号。
ts
// 这个代码会被解析 some = 'xxx'; 而不是 some = getSome;
function getSome(arg1) { return arg1; }
const some = getSome
(()=> {
return 'xxx'
})()
// 这个代码会被解析成 return; { .... };
return
{
....
}建议: 单文件长度最好不要超过1500行,尽可能的考虑拆分
建议: 单个方法长度不要超过50行
建议: 圈复杂度不要超过20
建议: 块嵌套深度不超过4层。if语句判断等
建议: 回调深读不超过4层。
变量
原则: 优先使用const,其次let。非特殊场景不使用var
原则: 在使用地方申明变量,且尽快初始化
原则: 每行申明一个变量,结构和for循环除外
- 方便阅读
- 在debug模式下,可以按行阅读。单行代码无法优美的查看(大雾)。 不好示例
ts
const varA = 'nb', B = 123;推荐示例
ts
const varA = 'nb';
const varB = 123;
for (let i = 0, len = arr.length; i < len; i++) {
...
}
const [varA, varB] = ['nb', 123]建议: 申明阶段禁止连续赋值
建议: 变量不需要使用undefined来初始化
未赋值的变量会默认有一个undefined的初始值(????)
建议: 非特殊情况一般使用基础类型的字面量来初始化,而不是封装类型。
不好的示例
ts
const stringA = new String('A');
const numberB = new Number(123);
console.log(numberB + 1) // 124;
// 会有语义上的不明确
const arrA = new Array(3, 4, 5); // [3, 4, 5]
const arrA = new Array(4) // [empty * 4] 语义不明确
const arrC = new Array('4') // ['4'] // 语义不明确
// 考虑可读性和冗余性,对象也是
const objA = new Object({
keyA: 'longfor',
keyB: 123
})建议: 内部作用域变量不要覆盖外部作用域变量
会导致更深层次的代码无法获取到想要的变量,在后续变更操作中,增加出错风险。
不好示例
ts
function a() {
const aa = 1;
function b() {
const aa = 2;
function c() {
// 如果c函数需要知道a函数中aa咋办???????
console.log(aa)
}
c()
}
b()
}
a()参数
建议: 方法的参数不超过5个
超过5个的参数,将逻辑关联的参数放入对一个对象中。
建议: 函数参数指定默认参数,不是使用 || 指定默认参数
不好示例
ts
// 如果 a = false 或 a = '';enummmmm.....
function test(a) {
a = a || true
}建议: 永远不要改变参数的值。
- 如果函数中新增加一个功能,需要用到参数的原始值,想要一下需要改动的地方有多少。
- 如果参数是引用传参,改变参数的属性会导致修改传入外部,造成一些不必要的问题。
建议: 默认参数放在最后面
默认参数放在前面,需要写undefined来占位,放在后面可以不用。 不好示例
ts
function test(argA = {}, argB) {}
test(undefined, 'test');推荐示例
ts
function test(argB, argA = {}) {}
test('test'); // cool....。建议: es6不要使用arguments, 请rest替代
- 箭头函数不存在arguments
- arguments只是类数组,操作不方便
建议: 对于外部API,始终对参数进行校验。
为了保证健壮性,防御性编程不可少(大雾)
建议: 优先使用参数结构
申明与实现
原则: 函数申明一致性
函数有表达式声明和function申明。有如下区别
- function申明会将申明提升到作用域顶级,可以在申明前调用
- 表达式申明不会提升,在申请前使用会出错(not a function) 团队应该选择统一的风格来实现,推荐使用表达式申明。
建议: 匿名函数优先使用箭头函数
普通函数和箭头函数性能和开销没有太大差别,但是匿名函数没有this的困扰(^-^)。
原则: 箭头函数参数风格一致性
有下面有两种风格
- 只有一个参数的情况下去参数括号。
ts
const test = argA => argA * 2;- 总是添加参数括号
ts
const test = (argA) => argA * 2;推荐第二种方式,没有花括号的方式会有阅读上的停顿。
原则: 箭头函数return风格一致性
同上,方法体只有一个return 语句,是不是需要添加花括号。推荐总是添加花括号,丑一点但是方便断点调试。
建议: 函数有多个返回参数时使用对象解构
优先使用对象结构而不是数组解构。不然冷不丁出现这种const [a,,b,,c] = [1,1,1,1,1,1]
类与对象
类
建议: 优先采用class定义类
- class关键字定义类简洁,而且逻辑更易于阅读。
- 不仅仅时语法糖,class的构造函数有特殊的内部属性
[[IsClassConstructor]]: true, 可以防止被作为普通函数使用。 String(SomeClass)会得到什么? -- class SomeClass {}
建议: 使用extends来实现继承。
原则: 构造函数中禁止在super() 前调用this或者super.xx
建议: 在构造函数中申明所有的属性和方法。
不要在类实例化后再动态添加属性, 如果一个属性实在执行时被赋值的,也需要先申明,方便VM优化。
字符串
建议: 优先使用单引号
当你创建一个包含html代码时候就知道了
建议: 字符串使用模板字符串
可阅读性,一堆 + 很难阅读,特别时字符串中带有 + 时。
原则: 不使用\字符串行连续符号。
当你在\ 后面加个空格时,找代码会找的你怀疑人生。 直接写在一行,大部分IED会处理用多行来显示一行。
数组
建议: 添加元素使用数组的push方法,而不是索引赋值
数组的length有长度限制,push会检查并抛错,赋值没有。极限情况下出现length不更新问题。
建议: 不在数组定义非数字索引
有特殊场景需要添加,建议替换成map或者set。
建议: 数组遍历优先使用数组上方法
建议: 除特殊场景, 不要使用for in遍历数组。
建议: forEach中不要对数组进行增加删除操作
对象
建议: 对象字面量属性名统一风格,不加引号。
非特殊场景不使用混搭风格 不好示例
ts
const objA = {
'name': 'AAA',
title: 'welcome to AAA',
// 特殊情况
'some-one': '123'
}建议: 尽量在申明时将所有属性申明好,少动态添加。
建议: 对象字面量中,方法使用简写。
推荐示例
ts
const objA = {
key: 'a',
getKey() {
return this.key;
}
}不好示例
ts
const objA = {
key: 'a',
getKey: function() {
return this.key;
}
}建议: 推荐使用对象字面量中使用简写属性
推荐示例
ts
const key = 'a'
const objA = {
key,
getKey() {
return this.key;
}
}不好示例
ts
const key = 'a'
const objA = {
key: key,
getKey() {
return this.key;
}
}建议: 使用.号来访问属性方法,只有动态情况或者数字索引下采用[]访问
原则: get 和 set 必须成对出现,否则没有意义。
如果只有get,表示该属性只可读,不可写。 如果只有set,表示该属性只可写,不可读。
原则: 禁止在对象上使用Object.proptotype 的内置属性,用call代替
如果一个来自外部的JSON resData = { hasOwnProperty: 1 }; 如果调用resData.hasOwnProperty() 就会出现安全风险。用Object.proptotype.hasOwnProperty.call(resData) 则不会。
建议: for in循环对象需要约束
for in 会将原型链上的属性包括进来,建议使用Object.prototype.hasOwnProperty() 过滤,或者直接使用Object.keys转成数组循环。
建议: 严禁对内置的类型添加修改属性方法。
常见在String.prototype 上面添加方法等。
运算和表达式
条件表达式
建议: 条件表达式中,变量在先,表达式在后。
有种观点时变量在后面,为了防止 if (a = 1) {}这种低级错误出现。不过为了可阅读性,我们推荐变量的在前面,配合现在IDE,可以避免这种错误。
建议: 总使用 === 和 !==, 不使用 == 和 !=
==和!=对于新人来说难以掌握,而且也不利于阅读。所有 == 和 != 的场景都可以用 === 和 !== 表示出来。
建议: 条件语句尽量简单
不好示例
ts
if (isValid === true) {}
const isFinished = condition ? true : false;建议: 不要在一个复杂的条件表达式最前面添加否定!
不好示例
ts
if (!((person.flag === 0 && person.age > 65) || person.sex === 1)) { // 来读读这个是啥意思
....
}需要否定的地方,用数学知识处理一下,解开括号。原则是 AND-OR互换,!抵消。括号加在 OR 中,从左到右依赖删除括号。对!(!conditionA || !conditionB && conditionC) 举例。
- AND-OR互换:
!!conditionA && !!conditionB || !conditionC - ! 抵消:
conditionA && conditionB || !conditionC - 括号加在 OR 中
conditionA && (conditionB || !conditionC) - 观察是否能够消除括号,这个例子不能消除括号。
建议: 非特殊场景,if else 条件判断不要否定在前
建议: 禁止使用嵌套的三元表达式
嵌套的三元表示式需要用栈的思维来阅读。建议多写几个if替代。
建议: 混合条件表示式,使用括号来标记运算顺序。
建议: 每个switch语句总是有default,即使default里面为空
防御性编程意识不可少,当定义了default,就会想switch的错误场景。
建议: 非特殊场景,总是给每个case添加break
建议: 总是给每个case的执行体添加花括号{}
switch中,每个case都在switch代码块,作用域共享。为了防止出错,建议给每个case添加花括号形成块作用域。
推荐示例
ts
switch (code) {
case: '0000': {
....
break
}
}正则
原则: 正则表达式中不要出现连续空格
不好示例
ts
const testReg = /test test/; // 你能直接知道是几个空格吗推荐示例
ts
const testReg = /test {4}test/建议: 总是使用具名捕获组
比如 'web-doc'.match(/-(?<customName>\w)/).groups,可以得到 { customName: 'd' }
一些特性
作用域
建议: 不要在顶层作用域申明函数变量
一般情况下,大部分人会忘记顶层作用域就是全局作用域。如果要申明,应该显示的申明到windows或者global上。
建议: 谨慎添加window上属性。
尽可能用命名空间实现,给windows挂载一个特殊的属性,剩下的挂在在这个属性下面。参考jquery 或者underscore
数字
建议: 禁止省略小数点前后的0
好好写嘛
不好示例
ts
const numA = .5;
const numB = 2.;
const numC = -.9;原则: 使用isNaN() 来检查NaN
异常
建议: 异常的原则
- 异常总是Error子类或者Error
- 优先考虑内置异常, 不满足情况下使用自定义异常
- 捕获的异常不处理需要注释说明
建议: promise中reject总是返回异常,而不是其他;
Promise rejct 返回 Error可以看到错误栈,方便调试,断点神器。
异步
原则: 不要 return await写法
直接return,async 本身会返回Promise对象。
杂谈
建议: 一般不使用eval,如果需要,务必需要多人评审。
原则: 防止出现隐形eval
setTimeout 和 setInterval第一个参数为字符串时,和eval一样。不过大部分浏览器会拦截这个漏洞。
原则: 非特殊场景不用with
with可以提升性能,如果掌握不好会有一定的安全风险,比如在语义不明的情况,开发可能错误的执行了某个对象上的方法。建议团队评估后使用。
建议: 类型转换都用显示的方法处理,不要短符号实现。
- 布尔类型: 用
Boolean(numA)代替!!numA - 数字类型: 用
Number(strA)代替+strA - 其余类推
原则: DOM卸载时,务必移除事件绑定。
不然哪天内存泄漏都找不到原因,引用计数GC的浏览器有这个问题。
原则: 及时清理定时器和延时器
同样会导致内存泄漏,还要
