JS规范

一、命名

  • 【强制】 常量 使用 全部字母大写,单词间下划线分隔 的命名方式
var HTML_ENTITY = {};
  • 【强制】 除了 常量、枚举变量、类 使用 Pascal命名法,其余情况统一使用 Camel命名法。
// 常用参数
var loadingModules = {};

// 常量
var HTML_ENTITY = {};

// 类
function TextNode(options) {
}
  • 【强制】 枚举变量 使用 Pascal命名法,枚举的属性 使用 全部字母大写,单词间下划线分隔 的命名方式。
var TargetState = {
    READING: 1,
    READED: 2,
    APPLIED: 3,
    READY: 4
};
  • 【强制】 类名 使用 名词。
function Engine(options) {
}
  • 【建议】 函数名 使用 动宾短语。
function getStyle(element) {
}
  • 【建议】 boolean 类型的变量使用 is 或 has 开头。
var isReady = false;
var hasMoreCommands = false;
  • 【建议】 Promise对象 用 动宾短语的进行时 表达。
var loadingData = ajax.get('url');
loadingData.then(callback);

二、注释

  • 单行注释 【强制】 必须独占一行。// 后跟一个空格,缩进与下一行被注释说明的代码一致。

  • 多行注释 【建议】 避免使用 /.../ 这样的多行注释。有多行注释内容时,使用多个单行注释。

  • 文档化注释 【强制】 为了便于代码阅读和自文档化,以下内容必须包含以 /*.../ 形式的块注释中。

    • 解释:
      • 1、文件
      • 2、namespace
      • 3、类
      • 4、函数或方法
      • 5、类属性
      • 6、事件
      • 7、全局变量
      • 8、常量
    • 【强制】 文档注释前必须空一行。
    • 【建议】 自文档化的文档说明 what,而不是 how。
  • 类型定义 【强制】 类型定义都是以{开始, 以}结束。

    • 常用类型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。
    • 【强制】 对于基本类型 {string}, {number}, {boolean},首字母必须小写。
  • 文件注释

    • 【强制】 文件顶部必须包含文件注释,用 @file 标识文件说明。
    • 【建议】 文件注释中可以用 @author 标识开发者信息。
/**
 * @file Describe the file
 * @author author-name(mail-name@domain.com)
 *         author-name2(mail-name2@domain.com)
 */
  • 类注释
    • 【建议】 使用 @class 标记类或构造函数。
    • 【建议】 使用 @extends 标记类的继承信息。
    • 【强制】 使用包装方式扩展类成员时, 必须通过 @lends 进行重新指向。
    • 【强制】 类的属性或方法等成员信息使用 @public / @protected / @private 中的任意一个,指明可访问性。
/**
 * 类描述
 *
 * @class
 * @extends Developer
 */
var Fronteer = function () {
    Developer.call(this);

    /**
     * 属性描述
     *
     * @type {string}
     * @private
     */
    this._level = 'T12';

    // constructor body
};
util.inherits(Fronteer, Developer);

/**
 * 方法描述
 *
 * @private
 * @return {string} 返回值描述
 */
Fronteer.prototype._getLevel = function () {
};
  • 函数/方法注释
    • 【强制】 函数/方法注释必须包含函数说明,有参数和返回值时必须使用注释标识。
    • 【强制】 参数和返回值注释必须包含类型信息和说明。
    • 【建议】 当函数是内部函数,外部不可访问时,可以使用 @inner 标识。
    • 【强制】 对 Object 中各项的描述, 必须使用 @param 标识。
/**
 * 函数描述
 *
 * @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
    };
}

三、条件

  • 【强制】 在 Equality Expression 中使用类型严格的 ===。仅当判断 null 或 undefined 时,允许使用 == null。
    • 使用 === 可以避免等于判断中隐式的类型转换。
// good
if (age === 30) { }

// bad
if (age == 30) { }
  • 【建议】 尽可能使用简洁的表达式。
// 字符串为空

// good
if (!name) { }

// bad
if (name === '') { }

// 字符串非空

// good
if (name) { }

// bad
if (name !== '') { }

// 数组非空

// good
if (collection.length) { }

// bad
if (collection.length > 0) { }

// 布尔不成立

// good
if (!notTrue) { }

// bad
if (notTrue === false) { }

// null 或 undefined

// good
if (noValue == null) { }

// bad
if (noValue === null || typeof noValue === 'undefined') { }

四、循环

  • 【建议】 不要在循环体中包含函数表达式,事先将函数提取到循环体外。
    • 循环体中的函数表达式,运行过程中会生成循环次数个函数对象。
// good
function clicker() {
    // ......
}

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


// bad
for (var i = 0, len = elements.length; i < len; i++) {
    var element = elements[i];
    addListener(element, 'click', function () {});
}
  • 【建议】 对循环内多次使用的不变值,在循环外用变量缓存。
// good
var width = wrap.offsetWidth + 'px';
for (var i = 0, len = elements.length; i < len; i++) {
    var element = elements[i];
    element.style.width = width;
    // ......
}


// bad
for (var i = 0, len = elements.length; i < len; i++) {
    var element = elements[i];
    element.style.width = wrap.offsetWidth + 'px';
    // ......
}
  • 【建议】 对有序集合进行遍历时,缓存 length。
    • 虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 length 访问时会动态计算元素个数,此时缓存 length 能有效提高程序性能。
for (var i = 0, len = elements.length; i < len; i++) {
    var element = elements[i];
    // ......
}
  • 【建议】 对有序集合进行顺序无关的遍历时,使用逆序遍历。
    • 逆序遍历可以节省变量,代码比较优化。
var len = elements.length;
while (len--) {
    var element = elements[len];
    // ......
}

五、数值

  • 【建议】 string 转换成 number,要转换的字符串结尾包含非数字并期望忽略时,使用 parseInt。
  • 【强制】 使用 parseInt 时,必须指定进制。
var width = '200px';
parseInt(width, 10);
  • 【建议】 转换成 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);

六、字符串

  • 【强制】 字符串开头和结束使用单引号 '。
    • 输入单引号不需要按住 shift,方便输入。
    • 实际使用中,字符串经常用来拼接 HTML。为方便 HTML 中包含双引号而不需要转义写法。
var str = '我是一个字符串';
var html = '<div class="cls">拼接HTML可以省去双引号转义</div>';
  • 【建议】 使用 数组 或 + 拼接字符串。
    • 使用 + 拼接字符串,如果拼接的全部是 StringLiteral,压缩工具可以对其进行自动合并的优化。所以,静态字符串建议使用 + 拼接。
    • 在现代浏览器下,使用 + 拼接字符串,性能较数组的方式要高。
    • 如需要兼顾老旧浏览器,应尽量使用数组拼接字符串。
// 使用数组拼接字符串
var str = [
    // 推荐换行开始并缩进开始第一个字符串, 对齐代码, 方便阅读.
    '<ul>',
        '<li>第一项</li>',
        '<li>第二项</li>',
    '</ul>'
].join('');

// 使用 + 拼接字符串
var str2 = '' // 建议第一个为空字符串, 第二个换行开始并缩进开始, 对齐代码, 方便阅读
    + '<ul>',
    +    '<li>第一项</li>',
    +    '<li>第二项</li>',
    + '</ul>';
  • 【建议】 复杂的数据到视图字符串的转换过程,选用一种模板引擎。
    • 使用模板引擎有如下好处:
      • 在开发过程中专注于数据,将视图生成的过程由另外一个层级维护,使程序逻辑结构更清晰。
      • 优秀的模板引擎,通过模板编译技术和高质量的编译产物,能获得比手工拼接字符串更高的性能。 ```
        {{ test${index} }}

#### 七、对象
+ 【强制】 使用对象字面量 {} 创建新 Object。

// good var obj = {};

// bad var obj = new Object();


+ 【强制】 对象创建时,如果一个对象的所有 属性 均可以不添加引号,则所有 属性 不得添加引号。

var info = { name: 'someone', age: 28 };


+ 【强制】 对象创建时,如果任何一个 属性 需要添加引号,则所有 属性 必须添加 '。

// good var info = { 'name': 'someone', 'age': 28, 'more-info': '...' };

// bad var info = { name: 'someone', age: 28, 'more-info': '...' };


+ 【建议】 for in 遍历对象时, 使用 hasOwnProperty 过滤掉原型中的属性。

var newInfo = {}; for (var key in info) { if (info.hasOwnProperty(key)) { newInfo[key] = info[key]; } }


#### 八、数组
+ 【强制】 使用数组字面量 [] 创建新数组,除非想要创建的是指定长度的数组。

// good var arr = [];

// bad var arr = new Array();


+ 【强制】 遍历数组不使用 for in。
    - 数组对象可能存在数字以外的属性, 这种情况下 for in 不会得到正确结果.

var arr = ['a', 'b', 'c']; arr.other = 'other things'; // 这里仅作演示, 实际中应使用Object类型

// 正确的遍历方式 for (var i = 0, len = arr.length; i < len; i++) { console.log(i); }

// 错误的遍历方式 for (i in arr) { console.log(i); }


+ 【建议】 不因为性能的原因自己实现数组排序功能,尽量使用数组的 sort 方法。
    - 自己实现的常规排序算法,在性能上并不优于数组默认的 sort 方法。以下两种场景可以自己实现排序:
        - 需要稳定的排序算法,达到严格一致的排序结果。
        - 数据特点鲜明,适合使用桶排。

+ 【建议】 清空数组使用 .length = 0。


#### 函数
+ 【建议】 通过 options 参数传递非数据输入型参数。
    - 有些函数的参数并不是作为算法的输入,而是对算法的某些分支条件判断之用,此类参数建议通过一个 options 参数传递。
    - 这种模式有几个显著的优势:
        - boolean 型的配置项具备名称,从调用的代码上更易理解其表达的逻辑意义。
        - 当配置项有增长时,无需无休止地增加参数个数,不会出现 removeElement(element, true, false, false, 3) 这样难以理解的调用代码。
        - 当部分配置参数可选时,多个参数的形式非常难处理重载逻辑,而使用一个 options 对象只需判断属性是否存在,实现得以简化。

// bad /**

  • 移除某个元素 *
  • @param {Node} element 需要移除的元素
  • @param {boolean} removeEventListeners 是否同时将所有注册在元素上的事件移除 */ function removeElement(element, removeEventListeners) { element.parent.removeChild(element); if (removeEventListeners) {
     element.clearEventListeners();
    
    } }

// good /**

  • 移除某个元素 *
  • @param {Node} element 需要移除的元素
  • @param {Object} options 相关的逻辑配置
  • @param {boolean} options.removeEventListeners 是否同时将所有注册在元素上的事件移除 */ function removeElement(element, options) { element.parent.removeChild(element); if (options.removeEventListeners) {
     element.clearEventListeners();
    
    } }

+ 【建议】 对于性能有高要求的场合,建议存在一个空函数的常量,供多处使用共享。

var EMPTY_FUNCTION = function () {};

function MyClass() { }

MyClass.prototype.abstractMethod = EMPTY_FUNCTION; MyClass.prototype.hooks.before = EMPTY_FUNCTION; MyClass.prototype.hooks.after = EMPTY_FUNCTION;



#### 面向对象
+ 【建议】 属性在构造函数中声明,方法在原型中声明。
    - 原型对象的成员被所有实例共享,能节约内存占用。所以编码时我们应该遵守这样的原则:原型对象包含程序不会修改的成员,如方法函数或配置项。

function TextNode(value, engine) { this.value = value; this.engine = engine; }

TextNode.prototype.clone = function () { return this; };

```

注:详情查看
JSRUN前端笔记, 是针对前端工程师开放的一个笔记分享平台,是前端工程师记录重点、分享经验的一个笔记本。JSRUN前端采用的 MarkDown 语法 (极客专用语法), 这里属于IT工程师。