Skip to content

数说前端js代码规范 #18

@WarpPrism

Description

@WarpPrism

文档说明

本文档的目标是使 JavaScript 代码风格保持一致,容易被理解和被维护。

虽然本文档是针对 JavaScript 设计的,但是在使用各种 JavaScript 的预编译语言时(如 TypeScript 等)时,适用的部分也应尽量遵循本文档的约定。

1. 代码风格

1.1 文件

[建议] JavaScript 文件使用无 BOM 的 UTF-8 编码。

1.2 结构

1.2.1 缩进

[强制] 使用 4 个空格做为一个缩进层级,不允许使用 2 个空格 或 tab 字符。

1.2.2 空格

[强制] 二元运算符两侧必须有一个空格,一元运算符与操作对象之间不允许有空格。
示例:

var a = !arr.length;
a++;
a = b + c;

[强制] 用作代码块起始的左花括号 { 前必须有一个空格。

// good
if (condition) {
}

while (condition) {
}

function funcName() {
}

// bad
if (condition){
}

while (condition){
}

function funcName(){
}

[强制] if / else / for / while / function / switch / do / try / catch / finally 关键字后,必须有一个空格。
示例:

// good
if (condition) {
}

while (condition) {
}

(function () {
})();

// bad
if(condition) {
}

while(condition) {
}

(function() {
})();

[强制] 在对象创建时,属性中的 : 之后必须有空格,: 之前不允许有空格。
示例:

// good
var obj = {
    a: 1,
    b: 2,
    c: 3
};

// bad
var obj = {
    a : 1,
    b:2,
    c :3
};

[强制] , 和 ; 前不允许有空格。如果不位于行尾,, 和 ; 后必须跟一个空格。
示例:

// good
callFunc(a, b);

// bad
callFunc(a , b) ;

[强制] 在函数调用、函数声明、括号表达式、属性访问、if / for / while / switch / catch 等语句中,()[] 内紧贴括号部分不允许有空格。

示例:

// good

callFunc(param1, param2, param3);

save(this.list[this.indexes[i]]);

needIncream && (variable += increament);

if (num > list.length) {
}

while (len--) {
}


// bad

callFunc( param1, param2, param3 );

save( this.list[ this.indexes[ i ] ] );

needIncreament && ( variable += increament );

if ( num > list.length ) {
}

while ( len-- ) {
}

[强制] 单行声明的数组与对象,如果包含元素,{}[] 内紧贴括号部分不允许包含空格。

解释:

声明包含元素的数组与对象,只有当内部元素的形式较为简单时,才允许写在一行。元素复杂的情况,还是应该换行书写。

示例:

// good
var arr1 = [];
var arr2 = [1, 2, 3];
var obj1 = {};
var obj2 = {name: 'obj'};
var obj3 = {
    name: 'obj',
    age: 20,
    sex: 1
};

// bad
var arr1 = [ ];
var arr2 = [ 1, 2, 3 ];
var obj1 = { };
var obj2 = { name: 'obj' };
var obj3 = {name: 'obj', age: 20, sex: 1};

[强制] 行尾不得有多余的空格。

1.2.3 换行

[强制] 每个独立语句结束后必须换行。

[强制] 每行不得超过 120 个字符。

解释:

超长的不可分割的代码允许例外,比如复杂的正则表达式。长字符串不在例外之列。

[强制] 运算符处换行时,运算符必须在新行的行首。

示例:

// good
if (user.isAuthenticated()
    && user.isInRole('admin')
    && user.hasAuthority('add-admin')
    || user.hasAuthority('delete-admin')
) {
    // Code
}

var result = number1 + number2 + number3
    + number4 + number5;


// bad
if (user.isAuthenticated() &&
    user.isInRole('admin') &&
    user.hasAuthority('add-admin') ||
    user.hasAuthority('delete-admin')) {
    // Code
}

var result = number1 + number2 + number3 +
    number4 + number5;

[强制] 在函数声明、函数表达式、函数调用、对象创建、数组创建、for 语句等场景中,不允许在 , 或 ; 前换行。
示例:

// good
var obj = {
    a: 1,
    b: 2,
    c: 3
};

foo(
    aVeryVeryLongArgument,
    anotherVeryLongArgument,
    callback
);


// bad
var obj = {
    a: 1
    , b: 2
    , c: 3
};

foo(
    aVeryVeryLongArgument
    , anotherVeryLongArgument
    , callback
);

[建议] 在语句的行长度超过 120 时,根据逻辑条件合理缩进。

示例:

// 按一定长度截断字符串,并使用 + 运算符进行连接。
// 分隔字符串尽量按语义进行,如不要在一个完整的名词中间断开。
// 特别的,对于 HTML 片段的拼接,通过缩进,保持和 HTML 相同的结构。
var html = '' // 此处用一个空字符串,以便整个 HTML 片段都在新行严格对齐
    + '<article>'
    +     '<h1>Title here</h1>'
    +     '<p>This is a paragraph</p>'
    +     '<footer>Complete</footer>'
    + '</article>';

// 也可使用数组来进行拼接,相对 `+` 更容易调整缩进。
var html = [
    '<article>',
        '<h1>Title here</h1>',
        '<p>This is a paragraph</p>',
        '<footer>Complete</footer>',
    '</article>'
];
html = html.join('');

// 当函数调用时,如果有一个或以上参数跨越多行,应当每一个参数独立一行。
// 这通常出现在匿名函数或者对象初始化等作为参数时,如 `setTimeout` 函数等。
setTimeout(
    function () {
        alert('hello');
    },
    200
);

// 链式调用较长时采用缩进进行调整。
$('#items')
    .find('.selected')
    .highlight()
    .end();

// 三元运算符由3部分组成,因此其换行应当根据每个部分的长度不同,形成不同的情况。
var result = thisIsAVeryVeryLongCondition
    ? resultA : resultB;

var result = condition
    ? thisIsAVeryVeryLongResult
    : resultB;

1.2.4 语句

[强制] 不得省略语句结束的分号。
[强制] 在 if / else / for / do / while 语句中,即使只有一行,也不得省略块 {...}。

示例:

// good
if (condition) {
    callFunc();
}

// bad
if (condition) callFunc();
if (condition)
    callFunc();

1.3 命名

[强制] 变量函数,函数的参数方法/属性 使用 Camel命名法。
示例:

var loadingModules = {};

function stringFormat(sourceString) {
}

[强制] 常量使用 全部字母大写,单词间下划线分隔 的命名方式。
示例:

var HTML_ENTITY = {};

[强制] 枚举变量 使用 Pascal命名法。枚举的属性 使用 全部字母大写,单词间下划线分隔 的命名方式。
示例:

var TargetState = {
    READING: 1,
    READED: 2,
    APPLIED: 3,
    READY: 4
};

1.4 注释

1.4.1 文档化注释

[建议] Sublime Text的同学,可以安装docBlkr文档编写插件。
[强制] 为了便于代码阅读和自文档化,以下内容必须包含以 /**...*/ 形式的块注释中。

解释:

文件
namespace

函数或方法
类属性
事件
全局变量
常量
AMD 模块
[强制] 文档注释前必须空一行。

[建议] 自文档化的文档说明 what,而不是 how。

示例:

/**
 * 函数描述
 *
 * @param {string} p1 参数1的说明
 * @param {string} p2 参数2的说明,比较长
 *     那就换行了.
 * @param {number=} p3 参数3的说明(可选)
 * @return {Object} 返回值描述
 */
function foo(p1, p2, p3) {
    var p3 = p3 || 10;
    return {
        p1: p1,
        p2: p2,
        p3: p3
    };
}

2 语言特征

2.1 变量

原则上不建议使用全局变量,对于已有的全局变量或第三方框架引入的全局变量,需要根据检查工具的语法标识。

[强制] 每个 var 只能声明一个变量。

解释:

一个 var 声明多个变量,容易导致较长的行长度,并且在修改时容易造成逗号和分号的混淆。

示例:

// good
var hangModules = [];
var missModules = [];
var visited = {};

// bad
var hangModules = [],
    missModules = [],
    visited = {};

2.2 条件

[强制] 在 Equality Expression 中使用类型严格的 ===。仅当判断 null 或 undefined 时,允许使用 == null。

解释:

使用 === 可以避免等于判断中隐式的类型转换。

2.3 循环

[建议] 对有序集合进行遍历时,缓存 length。

解释:

虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 length 访问时会动态计算元素个数,此时缓存 length 能有效提高程序性能。

示例:

for (var i = 0, len = elements.length; i < len; i++) {
    var element = elements[i];
    // ......
}

2.4 类型

2.4.1 类型转换

[建议] 转换成 string 时,使用 + ''

示例:

// good
num + '';

// bad
new String(num);
num.toString();
String(num);

[建议] 转换成 number 时,通常使用 +

示例:

// good
+str;

// bad
Number(str);

[建议] string 转换成 number,要转换的字符串结尾包含非数字并期望忽略时,使用 parseInt

示例:

var width = '200px';
parseInt(width, 10);

[强制] 使用 parseInt 时,必须指定进制。

示例:

// good
parseInt(str, 10);

// bad
parseInt(str);

[建议] 转换成 boolean 时,使用 !!

示例:

var num = 3.14;
!!num;

[建议] number 去除小数点,使用 Math.floor / Math.round / Math.ceil,不使用 parseInt

示例:

// good
var num = 3.14;
Math.ceil(num);

// bad
var num = 3.14;
parseInt(num, 10);

2.5 字符串

[强制] 字符串开头和结束使用单引号 '。

解释:

输入单引号不需要按住 shift,方便输入。
实际使用中,字符串经常用来拼接 HTML。为方便 HTML 中包含双引号而不需要转义写法。
示例:

var str = '我是一个字符串';
var html = '<div class="cls">拼接HTML可以省去双引号转义</div>';

2.6 动态特征

2.6.1 eval

[强制] 避免使用直接 eval 函数。

解释:

直接 eval,指的是以函数方式调用 eval 的调用方法。直接 eval 调用执行代码的作用域为本地作用域,应当避免。

如果有特殊情况需要使用直接 eval,需在代码中用详细的注释说明为何必须使用直接 eval,不能使用其它动态执行代码的方式,同时需要其他资深工程师进行 Code Review。

[建议] 尽量避免使用 eval 函数。

3 浏览器环境

3.1 模块化

3.1.1 AMD

[强制] 使用 AMD 作为模块定义。

解释:

AMD 作为由社区认可的模块定义形式,提供多种重载提供灵活的使用方式,并且绝大多数优秀的 Library 都支持 AMD,适合作为规范。

目前,比较成熟的 AMD Loader 有:

官方实现的 requirejs

[强制] 模块 id 必须符合标准。

解释:

模块 id 必须符合以下约束条件:

类型为 string,并且是由 / 分割的一系列 terms 来组成。例如:this/is/a/module
term 应该符合 [a-zA-Z0-9_-]+ 规则。
不应该有 .js 后缀。
跟文件的路径保持一致。

3.1.2 define

[建议] 定义模块时不要指明 iddependencies

解释:

在 AMD 的设计思想里,模块名称是和所在路径相关的,匿名的模块更利于封包和迁移。模块依赖应在模块定义内部通过 local require 引用。

所以,推荐使用 define(factory) 的形式进行模块定义。

示例:

define(
    function (require) {
    }
);

[建议] 使用 return 来返回模块定义。

解释:

使用 return 可以减少 factory 接收的参数(不需要接收 exportsmodule),在没有 AMD Loader 的场景下也更容易进行简单的处理来伪造一个 Loader。

示例:

define(
    function (require) {
        var exports = {};

        // ...

        return exports;
    }
);

3.1.3 require

[强制] 全局运行环境中,require 必须以 async require 形式调用。

解释:

模块的加载过程是异步的,同步调用并无法保证得到正确的结果。

示例:

// good
require(['foo'], function (foo) {
});

// bad
var foo = require('foo');

3.1.4 UMD

如果你打算编写通用模块(支持Common.js、AMD等),你可能需要参考一下如何用UMD来包装你的模块:

UMD

3.2 DOM

3.2.1 DOM操作

[建议] 操作 DOM 时,尽量减少页面 reflow。

解释:

页面 reflow 是非常耗时的行为,非常容易导致性能瓶颈。下面一些场景会触发浏览器的reflow:

DOM元素的添加、修改(内容)、删除。
应用新的样式或者修改任何影响元素布局的属性。
Resize浏览器窗口、滚动页面。
读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。
[建议] 尽量减少 DOM 操作。

解释:

DOM 操作也是非常耗时的一种操作,减少 DOM 操作有助于提高性能。举一个简单的例子,构建一个列表。我们可以用两种方式:

在循环体中 createElementappend 到父元素中。
在循环体中拼接 HTML 字符串,循环结束后写父元素的 innerHTML
第一种方法看起来比较标准,但是每次循环都会对 DOM 进行操作,性能极低。在这里推荐使用第二种方法。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions