码迷,mamicode.com
首页 > 编程语言 > 详细

JavaScript编码规范

时间:2016-05-18 21:47:30      阅读:241      评论:0      收藏:0      [点我收藏+]

标签:

JavaScript编码规范

1 命名

1.1 变量使用Camel命名法

示例:

var loadingModules = {};
1.2 常量使用全部字母大写,单词间下划线分隔的命名方式

示例:

var HTML_ENTITY = {};    
1.3 函数使用Camel命名法

示例:

function stringFormat(source) {
}
1.4 函数的参数使用Camel命名法

示例:

function hear(theBells) {
}
1.5 类使用Pascal命名法

示例:

function TextNode(options) {
}
1.6 枚举变量使用Pascal命名法,枚举的属性使用全部字母大写,单词间下划线分隔 的命名方式

示例:

var TargetState = {
    READING: 1,
    READED: 2,
    APPLIED: 3,
    READY: 4
};
1.7 命名空间使用Camel命名法

示例:

equipments.heavyWeapons = {};
1.8 类名使用名词

示例:

function Engine(options) {
}
1.9 函数名使用动宾短语

示例:

function getStyle(element) {
}
1.10 boolean类型的变量使用is或has开头

示例:

var isReady = false;
var hasMoreCommands = false;
1.11 Promise对象用动宾短语的进行时表达

示例:

var loadingData = ajax.get(‘url‘);
loadingData.then(callback);

2 注释

2.1 单行注释

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

2.2 多行注释

避免使用 /*...*/ 这样的多行注释。有多行注释内容时,使用多个单行注释。

2.3 文档化注释

为了便于代码阅读和自文档化,以下内容必须包含以 /**...*/ 形式的块注释中。
1.文件
2.namespace
3.类
4.函数或方法
5.类属性
6.事件
7.全局变量
8.常量
9.AMD 模块
文档注释前必须空一行。

2.4 文件注释

文件顶部必须包含文件注释,用 @file 标识文件说明。
文件注释中可以用 @author 标识开发者信息。
示例:

/**
 * @file Describe the file
 * @author author-name(mail-name@domain.com)
 *         author-name2(mail-name2@domain.com)
 */
2.5 命名空间注释

示例:

/**
 * @namespace
 */
var util = {};
2.6 类注释

使用 @class 标记类或构造函数
示例:

/**
 * 描述
 *
 * @class
 */
function Developer() {
    // constructor body
}

使用 @extends 标记类的继承信息。
示例:

/**
 * 描述
 *
 * @class
 * @extends Developer
 */
function Fronteer() {
    Developer.call(this);
    // constructor body
}
util.inherits(Fronteer, Developer);

使用包装方式扩展类成员时,必须通过 @lends 进行重新指向。
示例:

/**
 * 类描述
 *
 * @class
 * @extends Developer
 */
function Fronteer() {
    Developer.call(this);
    // constructor body
}

util.extend(
    Fronteer.prototype,
    /** @lends Fronteer.prototype */{
        getLevel: function () {
            // TODO
        }
    }
);

类的属性或方法等成员信息使用 @public / @protected / @private 中的任意一个,指明可访问性。(解释:生成的文档中将有可访问性的标记,避免用户直接使用非 public 的属性或方法。)
示例:

/**
 * 类描述
 *
 * @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 () {
};
2.7 函数/方法注释

函数/方法注释必须包含函数说明,有参数和返回值时必须使用注释标识。(解释:当 return 关键字仅作退出函数/方法使用时,无须对返回值作注释标识。)
参数和返回值注释必须包含类型信息,且不允许省略参数的说明。
当函数是内部函数,外部不可访问时,可以使用 @inner 标识。
示例:

/**
 * 函数描述
 *
 * @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
    };
}

对 Object 中各项的描述, 必须使用 @param 标识。
示例:

/**
 * 函数描述
 *
 * @param {Object} option 参数描述
 * @param {string} option.url option项描述
 * @param {string=} option.method option项描述,可选参数
 */
function foo(option) {
    // TODO
}

重写父类方法时, 应当添加 @override 标识。如果重写的形参个数、类型、顺序和返回值类型均未发生变化,可省略 @param、@return,仅用 @override 标识,否则仍应作完整注释。(解释:简而言之,当子类重写的方法能直接套用父类的方法注释时可省略对参数与返回值的注释。)

2.8 事件注释

必须使用 @event 标识事件,事件参数的标识与方法描述的参数标识相同。
示例:

/**
 * 值变更时触发
 *
 * @event Select#change
 * @param {Object} e e描述
 * @param {string} e.before before描述
 * @param {string} e.after after描述
 */
this.fire(
    ‘change‘,
    {
        before: ‘foo‘,
        after: ‘bar‘
    }
);

在会广播事件的函数前使用 @fires 标识广播的事件,在广播事件代码前使用 @event 标识事件。
对于事件对象的注释,使用 @param 标识,生成文档时可读性更好。
示例:

/**
 * 点击处理
 *
 * @fires Select#change
 * @private
 */
Select.prototype.clickHandler = function () {

    /**
     * 值变更时触发
     *
     * @event Select#change
     * @param {Object} e e描述
     * @param {string} e.before before描述
     * @param {string} e.after after描述
     */
    this.fire(
        ‘change‘,
        {
            before: ‘foo‘,
            after: ‘bar‘
        }
    );
};
2.9 常量注释

常量必须使用 @const 标记,并包含说明和类型信息。
示例:

/**
 * 常量说明
 *
 * @const
 * @type {string}
 */
var REQUEST_URL = ‘myurl.do‘;
2.10 复杂类型注释

对于类型未定义的复杂结构的注释,可以使用 @typedef 标识来定义。
示例:

// `namespaceA~` 可以换成其它 namepaths 前缀,目的是为了生成文档中能显示 `@typedef` 定义的类型和链接。
/**
 * 服务器
 *
 * @typedef {Object} namespaceA~Server
 * @property {string} host 主机
 * @property {number} port 端口
 */

/**
 * 服务器列表
 *
 * @type {Array.<namespacea~server>}
 */
var servers = [
    {
        host: ‘1.2.3.4‘,
        port: 8080
    },
    {
        host: ‘1.2.3.5‘,
        port: 8081
    }
];
2.10 细节注释

对于内部实现、不容易理解的逻辑说明、摘要信息等,我们可能需要编写细节注释。
细节注释遵循单行注释的格式。说明必须换行时,每行是一个单行注释的起始。
示例:

function foo(p1, p2, opt_p3) {
    // 这里对具体内部逻辑进行说明
    // 说明太长需要换行
    for (...) {
        ....
    }
}

有时我们会使用一些特殊标记进行说明。特殊标记必须使用单行注释的形式。下面列举了一些常用标记:
TODO: 有功能待实现。此时需要对将要实现的功能进行简单说明。
FIXME: 该处代码运行没问题,但可能由于时间赶或者其他原因,需要修正。此时需要对如何修正进行简单说明。
HACK: 为修正某些问题而写的不太好或者使用了某些诡异手段的代码。此时需要对思路或诡异手段进行描述。
XXX: 该处存在陷阱。此时需要对陷阱进行描述。

3 语言特性

3.1 变量

变量、函数在使用前必须先定义。(解释:不通过 var 定义变量将导致变量污染全局环境。)
示例:

// good
var name = ‘MyName‘;

// bad
name = ‘MyName‘;

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

/* globals jQuery */
var element = jQuery(‘#element-id‘);

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

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

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

变量必须 即用即声明,不得在函数或其它形式的代码块起始位置统一声明所有变量。(解释:变量声明与使用的距离越远,出现的跨度越大,代码的阅读与维护成本越高。虽然JavaScript的变量是函数作用域,还是应该根据编程中的意图,缩小变量出现的距离空间。)
示例:

// good
function kv2List(source) {
    var list = [];

    for (var key in source) {
        if (source.hasOwnProperty(key)) {
            var item = {
                k: key,
                v: source[key]
            };

            list.push(item);
        }
    }

    return list;
}

// bad
function kv2List(source) {
    var list = [];
    var key;
    var item;

    for (key in source) {
        if (source.hasOwnProperty(key)) {
            item = {
                k: key,
                v: source[key]
            };

            list.push(item);
        }
    }

    return list;
}
3.2 条件

在 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‘) {
  // ......
}
						

按执行频率排列分支的顺序。(解释:按执行频率排列分支的顺序好处是:1.阅读的人容易找到最常见的情况,增加可读性。2.提高执行效率。)
对于相同变量或表达式的多值条件,用 switch 代替 if。
示例:

// good
switch (typeof variable) {
    case ‘object‘:
        // ......
        break;
    case ‘number‘:
    case ‘boolean‘:
    case ‘string‘:
        // ......
        break;
}

// bad
var type = typeof variable;
if (type === ‘object‘) {
    // ......
}
else if (type === ‘number‘ || type === ‘boolean‘ || type === ‘string‘) {
    // ......
}

如果函数或全局中的 else 块后没有任何语句,可以删除 else。
示例:

// good
function getName() {
    if (name) {
        return name;
    }

    return ‘unnamed‘;
}

// bad
function getName() {
    if (name) {
        return name;
    }
    else {
        return ‘unnamed‘;
    }
}
3.3 循环

不要在循环体中包含函数表达式,事先将函数提取到循环体外。(解释:循环体中的函数表达式,运行过程中会生成循环次数个函数对象。)
示例:

// 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];
    // ......
}
3.4 类型
3.4.1 类型检测

类型检测优先使用 typeof。对象类型检测使用 instanceof。null 或 undefined 的检测使用 == null。
示例:

// string
typeof variable === ‘string‘

// number
typeof variable === ‘number‘

// boolean
typeof variable === ‘boolean‘

// Function
typeof variable === ‘function‘

// Object
typeof variable === ‘object‘

// RegExp
variable instanceof RegExp

// Array
variable instanceof Array

// null
variable === null

// null or undefined
variable == null

// undefined
typeof variable === ‘undefined‘    

 

3.4.2 类型转换

转换成 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);
3.5 字符串

字符串开头和结束使用单引号 ‘。(解释:1.输入单引号不需要按住 shift,方便输入。2.实际使用中,字符串经常用来拼接 HTML。为方便 HTML 中包含双引号而不需要转义写法。)
示例:

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

使用 数组 或 + 拼接字符串。(解释:1.使用 + 拼接字符串,如果拼接的全部是 StringLiteral,压缩工具可以对其进行自动合并的优化。所以,静态字符串建议使用 + 拼接。2.在现代浏览器下,使用 + 拼接字符串,性能较数组的方式要高。3.如需要兼顾老旧浏览器,应尽量使用数组拼接字符串。)
示例:

// 使用数组拼接字符串
var str = [
    // 推荐换行开始并缩进开始第一个字符串, 对齐代码, 方便阅读.
    ‘<ul>‘,
        ‘<li>第一项</li>‘,
        ‘<li>第二项</li>‘,
    ‘</ul>‘
].join(‘‘);

// 使用 `+` 拼接字符串
var str2 = ‘‘ // 建议第一个为空字符串, 第二个换行开始并缩进开始, 对齐代码, 方便阅读
    + ‘<ul>‘,
    +    ‘<li>第一项</li>‘,
    +    ‘<li>第二项</li>‘,
    + ‘</ul>‘;

使用字符串拼接的方式生成HTML,需要根据语境进行合理的转义。(解释:在 JavaScript 中拼接,并且最终将输出到页面中的字符串,需要进行合理转义,以防止安全漏洞。下面的示例代码为场景说明,不能直接运行。)
示例:

// HTML 转义
var str = ‘<p>‘ + htmlEncode(content) + ‘</p>‘;

// HTML 转义
var str = ‘<input type="text" value="‘ + htmlEncode(value) + ‘">‘;

// URL 转义
var str = ‘<a href="/?key=‘ + htmlEncode(urlEncode(value)) + ‘">link</a>‘;

// JavaScript字符串 转义 + HTML 转义
var str = ‘<button onclick="check(\‘‘ + htmlEncode(strLiteral(name)) + ‘\‘)">提交</button>‘;
3.6 对象

使用对象字面量 {} 创建新 Object。
示例:

// good
var obj = {};

// bad
var obj = new Object();

对象创建时,如果一个对象的所有 属性 均可以不添加引号,建议所有 属性 不添加引号。
示例:

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

对象创建时,如果任何一个 属性 需要添加引号,则所有 属性 建议添加 ‘。(解释:如果属性不符合 Identifier 和 NumberLiteral 的形式,就需要以 StringLiteral 的形式提供。)
示例:

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

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

不允许修改和扩展任何原生对象和宿主对象的原型。
示例:

// 以下行为绝对禁止
String.prototype.trim = function () {
};

属性访问时,尽量使用 .。(解释:属性名符合 Identifier 的要求,就可以通过 . 来访问,否则就只能通过 [expr] 方式访问。
通常在 JavaScript 中声明的对象,属性命名是使用 Camel 命名法,用 . 来访问更清晰简洁。部分特殊的属性(比如来自后端的 JSON ),可能采用不寻常的命名方式,可以通过 [expr] 方式访问。)
示例:

info.age;
info[‘more-info‘];

for in 遍历对象时, 使用 hasOwnProperty 过滤掉原型中的属性。
示例:

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

使用数组字面量 [] 创建新数组,除非想要创建的是指定长度的数组。
示例:

// good
var arr = [];

// bad
var arr = new Array();

遍历数组不使用 for in。(解释:数组对象可能存在数字以外的属性, 这种情况下 for in 不会得到正确结果。)
示例:

var arr = [‘a‘, ‘b‘, ‘c‘];

// 这里仅作演示, 实际中应使用 Object 类型
arr.other = ‘other things‘;

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

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

实现数组排序功能,尽量使用数组的 sort 方法。(解释:自己实现的常规排序算法,在性能上并不优于数组默认的 sort 方法。以下两种场景可以自己实现排序:1.需要稳定的排序算法,达到严格一致的排序结果。2.数据特点鲜明,适合使用桶排。)
清空数组使用 .length = 0。

3.8 函数
3.8.1 函数长度

一个函数的长度控制在 50 行以内。(解释:将过多的逻辑单元混在一个大函数中,易导致难以维护。一个清晰易懂的函数应该完成单一的逻辑单元。复杂的操作应进一步抽取,通过函数的调用来体现流程。特定算法等不可分割的逻辑允许例外。)
示例:

function syncViewStateOnUserAction() {
    if (x.checked) {
        y.checked = true;
        z.value = ‘‘;
    }
    else {
        y.checked = false;
    }

    if (a.value) {
        warning.innerText = ‘‘;
        submitButton.disabled = false;
    }
    else {
        warning.innerText = ‘Please enter it‘;
        submitButton.disabled = true;
    }
}

// 直接阅读该函数会难以明确其主线逻辑,因此下方是一种更合理的表达方式:

function syncViewStateOnUserAction() {
    syncXStateToView();
    checkAAvailability();
}

function syncXStateToView() {
    y.checked = x.checked;

    if (x.checked) {
        z.value = ‘‘;
    }
}

function checkAAvailability() {
    if (a.value) {
        clearWarnignForA();
    }
    else {
        displayWarningForAMissing();
    }
}
3.8.2 参数设计

一个函数的参数控制在 6 个以内。(解释:除去不定长参数以外,函数具备不同逻辑意义的参数建议控制在 6 个以内,过多参数会导致维护难度增大。某些情况下,如使用 AMD Loader 的 require 加载多个模块时,其 callback 可能会存在较多参数,因此对函数参数的个数不做强制限制。)
通过 options 参数传递非数据输入型参数。(解释:有些函数的参数并不是作为算法的输入,而是对算法的某些分支条件判断之用,此类参数建议通过一个 options 参数传递。)
示例:

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

    if (removeEventListeners) {
        element.clearEventListeners();
    }
}

可以转换为下面的签名:

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

    if (options.removeEventListeners) {
        element.clearEventListeners();
    }
}

这种模式有几个显著的优势:
1.boolean 型的配置项具备名称,从调用的代码上更易理解其表达的逻辑意义。
2.当配置项有增长时,无需无休止地增加参数个数,不会出现 removeElement(element, true, false, false, 3) 这样难以理解的调用代码。
3.当部分配置参数可选时,多个参数的形式非常难处理重载逻辑,而使用一个 options 对象只需判断属性是否存在,实现得以简化。

3.8.4 空函数

空函数不使用 new Function() 的形式。
示例:

var emptyFunction = function () {};    

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

var EMPTY_FUNCTION = function () {};

function MyClass() {
}

MyClass.prototype.abstractMethod = EMPTY_FUNCTION;
MyClass.prototype.hooks.before = EMPTY_FUNCTION;
MyClass.prototype.hooks.after = EMPTY_FUNCTION;
3.9 面向对象

类的继承方案,实现时需要修正 constructor。(解释:通常使用其他 library 的类继承方案都会进行 constructor 修正。如果是自己实现的类继承方案,需要进行 constructor 修正。)
示例:

/**
 * 构建类之间的继承关系
 *
 * @param {Function} subClass 子类函数
 * @param {Function} superClass 父类函数
 */
function inherits(subClass, superClass) {
    var F = new Function();
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass;
}    

声明类时,保证 constructor 的正确性。
示例:

function Animal(name) {
    this.name = name;
}

// 直接prototype等于对象时,需要修正constructor
Animal.prototype = {
    constructor: Animal,

    jump: function () {
        alert(‘animal ‘ + this.name + ‘ jump‘);
    }
};

// 这种方式扩展prototype则无需理会constructor
Animal.prototype.jump = function () {
    alert(‘animal ‘ + this.name + ‘ jump‘);
};

 

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

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

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

自定义事件的 事件名 必须全小写。(解释:在 JavaScript 广泛应用的浏览器环境,绝大多数 DOM 事件名称都是全小写的。为了遵循大多数 JavaScript 开发者的习惯,在设计自定义事件时,事件名也应该全小写。)

自定义事件只能有一个 event 参数。如果事件需要传递较多信息,应仔细设计事件对象。(解释:一个事件对象的好处有: 1.顺序无关,避免事件监听者需要记忆参数顺序。 2.每个事件信息都可以根据需要提供或者不提供,更自由。 3.扩展方便,未来添加事件信息时,无需考虑会破坏监听器参数形式而无法向后兼容。)

设计自定义事件时,应考虑禁止默认行为。(解释:常见禁止默认行为的方式有两种: 1.事件监听函数中 return false。 2.事件对象中包含禁止默认行为的方法,如 preventDefault。)

3.10 动态特性
3.10.1 eval

避免使用直接 eval 函数。(解释:直接 eval,指的是以函数方式调用 eval 的调用方法。直接 eval 调用执行代码的作用域为本地作用域,应当避免。 如果有特殊情况需要使用直接 eval,需在代码中用详细的注释说明为何必须使用直接 eval,不能使用其它动态执行代码的方式,同时需要其他资深工程师进行 Code Review。)

尽量避免使用 eval 函数。

3.10.2 动态执行代码

使用 new Function 执行动态代码。(解释:通过 new Function 生成的函数作用域是全局使用域,不会影响当当前的本地作用域。如果有动态代码执行的需求,建议使用 new Function。)
示例:

var handler = new Function(‘x‘, ‘y‘, ‘return x + y;‘);
var result = handler($(‘#x‘).val(), $(‘#y‘).val());
3.10.3 with

尽量不要使用 with。(解释:使用 with 可能会增加代码的复杂度,不利于阅读和管理;也会对性能有影响。大多数使用 with 的场景都能使用其他方式较好的替代。所以,尽量不要使用 with。)

3.10.4 delete

减少 delete 的使用。(解释:如果没有特别的需求,减少或避免使用 delete。delete 的使用会破坏部分 JavaScript 引擎的性能优化。)

处理 delete 可能产生的异常。(解释:对于有被遍历需求,且值 null 被认为具有业务逻辑意义的值的对象,移除某个属性必须使用 delete 操作。 在严格模式或 IE 下使用 delete 时,不能被删除的属性会抛出异常,因此在不确定属性是否可以删除的情况下,建议添加 try-catch 块。)
示例:

try {
    delete o.x;
}
catch (deleteError) {
    o.x = null;
}    
3.10.5 对象属性

避免修改外部传入的对象。(解释:JavaScript 因其脚本语言的动态特性,当一个对象未被 seal 或 freeze 时,可以任意添加、删除、修改属性值。 但是随意地对 非自身控制的对象 进行修改,很容易造成代码在不可预知的情况下出现问题。因此,设计良好的组件、函数应该避免对外部传入的对象的修改。 下面代码的 selectNode 方法修改了由外部传入的 datasource 对象。如果 datasource 用在其它场合(如另一个 Tree 实例)下,会造成状态的混乱。)
示例:

function Tree(datasource) {
    this.datasource = datasource;
}

Tree.prototype.selectNode = function (id) {
    // 从datasource中找出节点对象
    var node = this.findNode(id);
    if (node) {
        node.selected = true;
        this.flushView();
    }
};

对于此类场景,需要使用额外的对象来维护,使用由自身控制,不与外部产生任何交互的 selectedNodeIndex 对象来维护节点的选中状态,不对 datasource 作任何修改。

function Tree(datasource) {
    this.datasource = datasource;
    this.selectedNodeIndex = {};
}

Tree.prototype.selectNode = function (id) {

    // 从datasource中找出节点对象
    var node = this.findNode(id);

    if (node) {
        this.selectedNodeIndex[id] = true;
        this.flushView();
    }

};

除此之外,也可以通过 deepClone 等手段将自身维护的对象与外部传入的分离,保证不会相互影响。

具备强类型的设计。(解释:1.如果一个属性被设计为 boolean 类型,则不要使用 1 或 0 作为其值。对于标识性的属性,如对代码体积有严格要求,可以从一开始就设计为 number 类型且将 0 作为否定值。2.从 DOM 中取出的值通常为 string 类型,如果有对象或函数的接收类型为 number 类型,提前作好转换,而不是期望对象、函数可以处理多类型的值。)

4 DOM

4.1 元素获取

对于单个元素,尽可能使用 document.getElementById 获取,避免使用document.all。
对于多个元素的集合,尽可能使用 context.getElementsByTagName 获取。其中 context 可以为 document 或其他元素。指定 tagName 参数为 * 可以获得所有子元素。
遍历元素集合时,尽量缓存集合长度。如需多次操作同一集合,则应将集合转为数组。(解释:原生获取元素集合的结果并不直接引用 DOM 元素,而是对索引进行读取,所以 DOM 结构的改变会实时反映到结果中。)
示例:

<div></div>
<span></span>

<script>
var elements = document.getElementsByTagName(‘*‘);

// 显示为 DIV
alert(elements[0].tagName);

var div = elements[0];
var p = document.createElement(‘p‘);
docpment.body.insertBefore(p, div);

// 显示为 P
alert(elements[0].tagName);
</script>

获取元素的直接子元素时使用 children。避免使用childNodes,除非预期是需要包含文本、注释和属性类型的节点。

4.2 样式获取

获取元素实际样式信息时,应使用 getComputedStyle 或 currentStyle。(解释:通过 style 只能获得内联定义或通过 JavaScript 直接设置的样式。通过 CSS class 设置的元素样式无法直接通过 style 获取。)

4.3 样式设置

尽可能通过为元素添加预定义的 className 来改变元素样式,避免直接操作 style 设置。
通过 style 对象设置元素样式时,对于带单位非 0 值的属性,不允许省略单位。(解释:除了 IE,标准浏览器会忽略不规范的属性值,导致兼容性问题。)

4.4 DOM 操作

操作 DOM 时,尽量减少页面 reflow。(解释:页面 reflow 是非常耗时的行为,非常容易导致性能瓶颈。下面一些场景会触发浏览器的reflow:1.DOM元素的添加、修改(内容)、删除。2.应用新的样式或者修 改任何影响元素布局的属性。3.Resize浏览器窗口、滚动页面。4.读取元素的某些属性(offsetLeft、offsetTop、 offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left /Width/Height、getComputedStyle()、currentStyle(in IE)) 。)

尽量减少 DOM 操作。(解释:DOM 操作也是非常耗时的一种操作,减少 DOM 操作有助于提高性能。举一个简单的例子,构建一个列表。我们可以用两种方式:1.在循环体中 createElement 并 append 到父元素中。2.在循环体中拼接 HTML 字符串,循环结束后写父元素的 innerHTML。第一种方法看起来比较标准,但是每次循环都会对 DOM 进行操作,性能极低。在这里推荐使用第二种方法。)

4.5 DOM 事件

优先使用 addEventListener / attachEvent 绑定事件,避免直接在 HTML 属性中或 DOM 的 expando 属性绑定事件处理。(解释:expando 属性绑定事件容易导致互相覆盖。)

使用 addEventListener 时第三个参数使用 false。(解释:标准浏览器中的 addEventListener 可以通过第三个参数指定两种时间触发模型:冒泡和捕获。而 IE 的 attachEvent 仅支持冒泡的事件触发。所以为了保持一致性,通常 addEventListener 的第三个参数都为 false。)

在没有事件自动管理的框架支持下,应持有监听器函数的引用,在适当时候(元素释放、页面卸载等)移除添加的监听器。      

JavaScript编码规范

标签:

原文地址:http://www.cnblogs.com/herofei/p/5506413.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!