Skip to content

Javascript 编码规范

[TOC]

术语

  • 原则: 必须坚持的指导思想
  • 建议: 需要加以考虑的约定
  • 不好示例: 反面教材,里面的都是错误示范
  • 推荐示例: 正面教程,可以按照里面示例类推

命令

文件

建议: 不使用index文件实现逻辑,index用于聚合导出。

避免componentA/index.js,然后在index.js 写component的逻辑,正确的写法是 componentA/componentA.jscomponentA/index.js 仅作导出。当你在浏览器resource查找文件就知道了-__-。

命名原则

原则: 源文件编码格式(包括注释)必须是UTF-8

大家一定被乱码的解释折磨过....

原则: 方法、变量需要一个好名字

好的名字有包括不局限以下特征:

  1. 清晰表达意图:使用有描述性的型单词,避免使用单个字母,或者自创缩写来表达,比如 let lastDay 比 let ld 要好
  2. 必须使用英文单词,不允许出现中文拼音(约定熟成除外)。中文拼音在多音字表现上会有歧义,而且不太利于团队以后成成长
  3. 避免误导性命名,有误导的命名比表达不清晰还要危险,比如 let nameArray 不是一个array类型,而是一个字符串。
  4. 能区分出意思,建议不要在变量后面加上data,info,object等一般意思的词。比如productInfo和productData有什么区别。

原则: 构造函数、类命名都是用大驼峰

推荐示例

js
function Kind() {}
class People() {}

不好示例

js
function kind() {}
class people() {}

原则: 方法命名使用动词、动宾结构,并遵守驼峰原则

格式如下

  1. get + 非布尔属性名
  2. is + 布尔属性名
  3. set + 属性名
  4. has + 名字\形容词
  5. 动词
  6. 动词 + 宾语 不好的示例
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 => klassdefault => defaultsprivate => hidden

原则: 常量使用全部大写并且用下划线_分隔

  1. 不要魔鬼数字,比如错误码 response.code === "0000" || response.code === 5, 这种代码,不熟悉的人需要花时间去理解 5 是什么,"0000" 又是什么。
  2. 不要魔鬼常量,比如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() {
        ...
    }
}

原则: 方法体、块语句、类的开始或者末尾不要有空行。

建议: 一般情况下,不要使用连续空行。

空格

建议: 关键字周围空格一致性

  1. 在保留字(if, for while) 和左括号( 之间添加一个空格
  2. else catch 与 关闭花括号 } 之间添加一个空格
  3. 在任何打开花括号前添加一个空格, (函数参数和``模板中去除外)
ts
// 下面这三种情况除外
function too({ name }) {}
function foo([ name ]) {}
const str = `#${s}`;
  1. 在任何三元或者二元操作符的两侧增加一个空格
  2. 数组和函数参数逗号, 后面添加一个空格,前面不要增加空格。
  3. ; 前面不要空格
  4. 单行 {} 内侧需要一个空格
ts
const objA = { name: 'xxx', total: 2 }
  1. 数组[]内侧不要有空格 [a, b, c],不要[ a, b ]。
  2. 禁止出现多个空格,除注释和缩进外。

花括号

建议: 总是给代码执行体添加{}

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循环除外

  1. 方便阅读
  2. 在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
}

建议: 永远不要改变参数的值。

  1. 如果函数中新增加一个功能,需要用到参数的原始值,想要一下需要改动的地方有多少。
  2. 如果参数是引用传参,改变参数的属性会导致修改传入外部,造成一些不必要的问题。

建议: 默认参数放在最后面

默认参数放在前面,需要写undefined来占位,放在后面可以不用。 不好示例

ts
function test(argA = {}, argB) {}
test(undefined, 'test');

推荐示例

ts
function test(argB, argA = {}) {}
test('test');  // cool....。

建议: es6不要使用arguments, 请rest替代

  1. 箭头函数不存在arguments
  2. arguments只是类数组,操作不方便

建议: 对于外部API,始终对参数进行校验。

为了保证健壮性,防御性编程不可少(大雾)

建议: 优先使用参数结构

申明与实现

原则: 函数申明一致性

函数有表达式声明和function申明。有如下区别

  1. function申明会将申明提升到作用域顶级,可以在申明前调用
  2. 表达式申明不会提升,在申请前使用会出错(not a function) 团队应该选择统一的风格来实现,推荐使用表达式申明。

建议: 匿名函数优先使用箭头函数

普通函数和箭头函数性能和开销没有太大差别,但是匿名函数没有this的困扰(^-^)。

原则: 箭头函数参数风格一致性

有下面有两种风格

  1. 只有一个参数的情况下去参数括号。
ts
const test = argA => argA * 2;
  1. 总是添加参数括号
ts
const test = (argA) => argA * 2;

推荐第二种方式,没有花括号的方式会有阅读上的停顿。

原则: 箭头函数return风格一致性

同上,方法体只有一个return 语句,是不是需要添加花括号。推荐总是添加花括号,丑一点但是方便断点调试。

建议: 函数有多个返回参数时使用对象解构

优先使用对象结构而不是数组解构。不然冷不丁出现这种const [a,,b,,c] = [1,1,1,1,1,1]

类与对象

建议: 优先采用class定义类

  1. class关键字定义类简洁,而且逻辑更易于阅读。
  2. 不仅仅时语法糖,class的构造函数有特殊的内部属性[[IsClassConstructor]]: true, 可以防止被作为普通函数使用。
  3. 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) 举例。

  1. AND-OR互换: !!conditionA && !!conditionB || !conditionC
  2. ! 抵消: conditionA && conditionB || !conditionC
  3. 括号加在 OR 中 conditionA && (conditionB || !conditionC)
  4. 观察是否能够消除括号,这个例子不能消除括号。

建议: 非特殊场景,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

异常

建议: 异常的原则

  1. 异常总是Error子类或者Error
  2. 优先考虑内置异常, 不满足情况下使用自定义异常
  3. 捕获的异常不处理需要注释说明

建议: promise中reject总是返回异常,而不是其他;

Promise rejct 返回 Error可以看到错误栈,方便调试,断点神器。

异步

原则: 不要 return await写法

直接return,async 本身会返回Promise对象。

杂谈

建议: 一般不使用eval,如果需要,务必需要多人评审。

原则: 防止出现隐形eval

setTimeout 和 setInterval第一个参数为字符串时,和eval一样。不过大部分浏览器会拦截这个漏洞。

原则: 非特殊场景不用with

with可以提升性能,如果掌握不好会有一定的安全风险,比如在语义不明的情况,开发可能错误的执行了某个对象上的方法。建议团队评估后使用。

建议: 类型转换都用显示的方法处理,不要短符号实现。

  1. 布尔类型: 用 Boolean(numA) 代替 !!numA
  2. 数字类型: 用 Number(strA) 代替 +strA
  3. 其余类推

原则: DOM卸载时,务必移除事件绑定。

不然哪天内存泄漏都找不到原因,引用计数GC的浏览器有这个问题。

原则: 及时清理定时器和延时器

同样会导致内存泄漏,还要