编写可维护的Javascript

/ 0评 / 0

为什么编码规范如此重要?

软件生命周期中80%的成本消耗在了维护上。
几乎所有的软件维护者都不是它的最初作者。
编码规范提高了软件的可读性,它让工程师能够快速且充分地理解新的代码。
如果你将源码作为产品来发布,你需要确保它是可完整打包的,且像你创建的其他产品一样整洁。
摘自:《Java语言编码规范》

基本的格式化

1.1 缩进层级
使用4个空格字符的缩进
1.2 语句结尾

JavaScript的分析器中存在一个叫作:自动分号插入(Automatic Semicolon Insertio, ASI) 的机制,使得JavaScript在代码省略分号的时候也可以正常工作。

总是使用分号
1.3 行的长度
长度限定在80个字符
1.4 换行
在运算符后换行,第二行追加两个缩进
例外情况:当给变量赋值时,第二行的位置应当和赋值运算符的位置保持对齐。

var result = one + two + three +
             four + five;

1.5 空行
在方法之间
在方法中的局部变量和第一条语句之间
在多行或单行注释之前
在方法内的逻辑片段之间插入空行,提高可读性
1.6 命名
采用小驼峰
1.6.1 变量和函数
变量名应当总是遵守驼峰大小写命名法,并且命名前缀应当是名词
尽量在变量名中体现出值的数据类型。比如,命名count, length, size表明数据类型是数字,name, title, message表明数据类型是字符串。
常见动词约定

can 函数返回一个布尔值
has 函数返回一个布尔值
is 函数返回一个布尔值
get 函数返回一个非布尔值
set 函数用来保存一个值

1.6.2 常量
使用大写字母和下划线
1.6.3 构造函数
使用大驼峰命名法
命名常用名词
1.7 直接量
1.7.1 字符串
使用单引号来括住字符串
多行字符串用字符串连字符(+)将字符串分成多行
1.7.2 数字
使用常用的格式
1.7.3 null
友情提醒:null有很多坑,使用过程中请注意
null是个特殊值,很容易跟undefined搞混

在下列情况下使用null
用来初始化一个变量,这个变量可能赋值为一个对象
用来和一个已经初始化的变量比较,这个变量可以是也可以不是一个对象
当函数的参数期望是对象时,用作参数传入
当函数的返回值期望是对象时,用作返回值传出
下列情况下不要使用null
不要使用null来检测是否传入了某个参数
不要用null来检测一个未初始化的变量

1.7.4 undefined
友情提醒:undefined也有很多坑,使用过程中请注意
//pass
1.7.5 对象直接量
在直接量中直接写出所有属性
结束使用分号
1.7.6 数组直接量
使用方括号初始化

注释

2.1 单行注释
独占一行的注释,用来解释下一行代码。这行注释之前使用一个空行,且缩进层级和下一级代码保持一致
在代码行的尾部的注释。代码结束到注释之间至少有一个缩进。如果超过了最大字符数限制,就将这条注释放置于当前代码行的上方
被注释掉的大段代码
2.2 多行注释

/*
 * example
 * 例子
 */

2.3 使用注释
一般原则:在需要让代码变得更清晰时添加注释
2.3.1 难于理解的代码
//pass
2.3.2 可能被误认为错误的代码
当代码看上去有错误时

while (element && (element = element[axis])) { // 提示:赋值操作
    //pass
}

2.3.3 浏览器hack
解释一下为什么要这样子hack
2.4 文档注释
使用JavaDoc风格

/**
相关描述
@method 方法名
@param {object} 被合并的一个或者多个对象
@return {object} 一个新的合并后的对象
**/

语句和表达式

块语句总是使用花括号
3.1 花括号对齐方式

if (condition) {
    doSomeThing();
} else {
    doSomeThingElse();
}

3.2 块语句间隔
在左圆括号之前和右圆括号之后各添加一个空格。
3.3 switch语句
3.3.1 缩进
使用Java风格

switch (condition) {
    case: 'first':
        // doSomeThing();
        break;
    case: 'second':
        // doSomeThing();
        break;
    default:
        // doSomeThing();
}

3.3.2 case语句的连续执行

switch (condition) {
    case: 'first':
    case: 'third':
        // doSomeThing();
        break;
    case: 'second':
        // doSomeThing();
        break;
    default:
        // doSomeThing();
}

3.3.3 default
如果default没有行为,省略之
3.4 with
//pass
3.5 for
使用条件语句跳过循环

for (var i = 0; i < 10; i++) {
    if (i != 2) {
        process(values[i])
    }
}

3.6 for-in
所有for-in都必须使用hasOwnProperty(),以避免遍历从原型继承来的属性

变量、函数和运算符

4.1 变量声明
所有变量声明放在函数顶部,且将var语句合并成一句,每个变量的初始化独占一行
4.2 函数声明
// pass
4.3 函数调用间隔
在函数名和左括号之间没有空格
4.4 立即调用的函数
在代码段的起始用括号括起来,再用一对圆括号来立即执行
4.5 严格模式
不要在全局中使用严格模式
4.6 相等
使用===和!==
4.6.1 eval()
只允许在将Ajax的返回值转为JavaScript的时候使用
4.6.2 原始包装类型
禁止使用

避免使用全局变量

5.1 单全局变量
创建一个唯一的全局对象名,并将所有的功能都挂载到这个全局对象上。
5.1.1 命名空间

var Steve = {};

Steve.work = {};

Steve.life = {};

Steve.study = {};

5.1.2 模块
使用RequireJS

事件处理

6.1 不好的典型用法实例

function handleClick(event) {
    var popup = document.getElementById('popup');
    popup.style.left = event.clientX + 'px';
    popup.style.top = event.clientY + 'px';
    popup.className = 'reveal';
}

6.2 规则1: 隔离应用逻辑
将应用逻辑从所有事件处理程序中抽离出来

var App = {
    handleClick: function(event) {
        this.showPopup(event);
    },

    showPopup: function(event) {
        var popup = document.getElementById('popup');
        popup.style.left = event.clientX + 'px';
        popup.style.top = event.clientY + 'px';
        popup.className = 'reveal';
    }
};

$(element).on('click', function(event) {
    App.handleClick(event);
});

6.3 规则2: 不要分发事件对象
让事件处理程序来处理event对象,然后拿到所有需要的数据传给应用逻辑。

var App = {
    handleClick: function(event) {
        this.showPopup(event.clientX, event.clientY);
    },

    showPopup: function(x, y) {
        var popup = document.getElementById('popup');
        popup.style.left = x + 'px';
        popup.style.top = y + 'px';
        popup.className = 'reveal';
    }
};

$(element).on('click', function(event) {
    App.handleClick(event);
});

将配置数据从代码中分离出来

7.1 什么是配置数据
配置数据是应用中写死的值
配置数据是可发生改变的,而且可以在修改页面中展示的信息时,不去修改JavaScript代码
7.2 抽离配置数据
将config放入单独的文件

不是你的对象不要动

8.1 原则
不覆盖方法
不新增方法
不删除方法
8.2 基于对象的继承
// pass
8.3 基于类型的继承
在定义了构造函数的情况下,基于类型的继承是最合适的
首先,原型继承
然后,构造器继承

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

function Author(name) {
    Person.call(this, name);
}

Authon.prototype = new Person();

8.4 门脸模式(推荐)
8.5 判断对象是否存在
使用typeof运算符,判断myObj是否有定义

if (typeof myObj == "undefined") {
    var myObj = { };
}

本文严重依赖:《编写可维护的javascript》

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注