codingEcho / frontend

front-end develpoment experience

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

JavaScript编程指南(三)编程规范

codingEcho opened this issue · comments

JavaScript 编程指南


3 JavaScript 编程规范

3.1 代码风格

3.1.1 缩进层级

可以使用IDE自带的格式化功能,但是有的IDE格式化后的代码也不好看,如Eclipse(一般不会有人用它来写JavaScript)。

3.1.2 语句的结尾

  • 一般要求一个语句独占一行
  • 语句的结尾必须用分号“;

3.1.3 行的长度

每行的长度不应过长

3.1.4 换行

注意什么时候该换行,什么时候不该换行

3.1.5 空行

添加适当的空行

  • 在方法之间
  • 在方法中的局部变量和第一条语句之间
  • 在多行或单行注释之前
  • 在方法内的逻辑片段之间插入空行,提高可读性
  • 在return之前(方法仅有一句return除外)
function getArray() {
    return []; // return前无需空行
}

/*
 * 这是一个多行注释,
 * 之前有个空行。
 */

// 这是单行注释,之前有个空行
function getElement() {
    var $target = $('#id'), // 用jQuery id 选择器
        $element; // [空行(下方):局部变量和第一条语句之间]

    if ($target.length) {
        $element = $target.find('.css-class');
    }

    return $element || null; // [return和其它语句之间]
}

3.2 使用直接量

3.2.1 字符串

无论是用单引号还是双引号扩住字符串,在代码风格中务必统一代码的风格。
**个人推荐:**本人习惯使用单引号,原因可能和大多数人一样:HTML中属性一般使用双引号,所以JavaScript中使用单引号,直接拷贝HTML就可以使用了;

一些只能使用单引号的地方:

  • 在HTML中如果要给元素属性赋值JSON必须用外单、内双;
    <input type="hidden" name="datauserInfo" id="userInfo" value='{"name":"mike","age":12}' />

    <script type="text/javascript">
        (function () {
            function printUserInfo() {
                var hiddenUserInfo = document.getElementById('userInfo'),
                    userInfo = JSON.parse(hiddenUserInfo.value);

                console.dir(userInfo);
            }

            printUserInfo();

        }());
    </script>

字符串换行

// 不好的写法
var longString = 'Here's the story of man \
named Jane.' ;

// 好的写法
var longString = 'Here's the story of man ' +
'named Jane.';

3.2.2 数值

整数请勿添加小数点,如果是小数请勿省略小数点前面和后面部分。

var count = 10;
var price = 10.0;
var price = 10.00;

3.2.3 null

null是个一个特殊值,但我们常常误解它,将它和undefined搞混淆,下列场景中**应该**使用null:

  • 用来初始化一个变量,这个变量可能赋值为一个对象。
  • 用来和一个已经初始化的变量比较,这个变量可以是也可以不是一个对象。
  • 当函数的参数期望值是对象时,用作参数传入。
  • 当函数的返回值期望是对象时,用作返回值传出。

**不应该**使用null的场景:

  • 不要使用null来检测是否传入了某个参数。
  • 不要使用null来检测一个未初始化的变量。
// good
var person = null;

// good
function getPerson() {
    if (condition) {
        return new Person('Jane');
    } else {
        return null;
    }
}

// good
var person = getPerson();
if (person !== null) {
    doSomething();
}

// bad:用来和未初始化的变量比较
var person;
if (person != null) {
    doSomething();
}

// bad:检测是否传入了参数
function doSomething(arg1, arg2, arg3, arg4) {
    if (arg4 != null) {
        doSomethingElse();
    }
}

3.2.4 undefined

undefined 是一个特殊值,我们常将它和null混淆;

让人困惑的地方:

1. null==undefined; // true
2. typeof运算无论是值为undfined的变量还是未声明的变量,其都返回'undefined'
// bad
var person;
console.log(person === undefined); // true

// foo未被声明
var person;
console.log( typeof person);  // 'undefined'
console.log( typeof foo); // 'undefined'

避免使用undefined,确保只有在一种情况下typeof运算才会返回'undefined';

// good
var person = null;
console.log(person === null); //true

注意:(typeof null)返回'object'

3.2.5 对象直接量

直接量可以高效完成非直接量写法相同的任务,非直接量写法语法看起来要更为复杂。

// bad
var book = new Object();
book.title = 'Maintainable JavaScript';
book.author = 'Zakas';

// good
var book = {
    title: 'Maintainable JavaScript',
    author: 'Zakas'
};

3.2.6 数组直接量

使用数组直接量而不是构造函数

// bad
var colors = new Array('red', 'yellow', 'blue', 'white'),
    numbers = new Array(1, 2, 3, 4);

// good
var colors = ['red', 'yellow', 'blue', 'white'],
    numbers = [1, 2, 3, 4];

3.3 注释

3.3.1 行注释

  • 独占一行的注释,用来解释下一行代码。这行注释之前总有一个空行,且缩进层级和下一行代码保持一致。
  • 在代码行的尾部的注释。代码结束到注释之间至少有个一缩紧。注释(包括之前的代码部分)不应到超过单行最大字符数显示,如果超过了,就将这条注释放置于当前代码行的上方。
  • 被注释掉的打断代码(很多编辑器都可以批量注释掉多行代码)。

3.3.2 多行注释

/*
 * 这是一个多行注释,
 * 之前有个空行。
 */

3.3.3 使用注释

添加注释的一般原则:需要让代码变得更清晰时添加注释。

  • 难于理解的代码
  • 可能被认为错误的代码
  • 浏览器特性hack
  • 文档注释

3.4 JavaScript语句

3.4.1 块语句必须使用花括号

包括:

  • if
  • for
  • while
  • do...while
  • try...catch...finally
// Bad
if (condition)
    doSomething();

// Bad
if (condition) doSomething();

// Bad
if (condition) { doSomething(); }

// Good
if (condition) {
    doSomething();
}

3.4.2 括号对齐方式

// 首选使用(和Java类似)
if (condition) {
    doSomething();
} else {
    doSomethingElse();
}

// 微软风格,没什么问题,但是为了风格一致性,不推荐使用!
if (condition)
{
    doSomething();
}
else
{
    doSomethingElse();
}

3.4.3 switch 语句

  • 允许多个case匹配同一个代码块(确保你不是忘记写了break;),最好加注释说明;
  • 推荐保留default,谁也不敢保证代码万无一失。
switch (condition) {
    case 'one':
        //...
        break;
    // 当值为'two'或 '二'时,做相同的处理
    case 'two':
    case '二':
        //...
        break;
    default:
        throw '发现非期望的值:' + condition;
}

3.4.5 with 语句

禁止使用with语句(JavaScript大神可以忽略)

3.4.6 for 语句

  • break:结束循环
var values = [1, 2, 3, 4, 5, 6, 7],
    i,
    len;
for (i = 0, len = values.length; i < len; i++) {
    if (i === 2) {
        break; // 迭代不会继续
    }
    console.log (values[i]);
}
// 以上代码只输出: 1 2 ,不再往下执行
  • continue:跳过本次循环,往下执行
var values = [1, 2, 3, 4, 5, 6, 7],
    i,
    len;
for (i = 0, len = values.length; i < len; i++) {
    if (i === 2) {
        continue; // 跳过本次循环
    }
    console.log(values[i]);
}
// 不输出索引为2的元素,即:3

// Crockford 的编程规范不允许使用continue,而是主张使用条件语句代替
for (i = 0, len = values.length; i < len; i++) {
    if (i !== 2) {
        console.log(values[i]);
    }
}

3.4.7 for-in 语句

// 用hasOwnProperty()方法过滤出实例属性
var prop;
for (prop in object) {
    // 若想要查找原型链,则省略if条件语句
    if (object.hasOwnProperty(prop)) {
        console.log('Property name is ' + prop);
        console.log('Property value is ' + object[prop]);
    }
}

// 不要用for-in来遍历数组
var values = [1, 2, 3, 4, 5, 6, 7],
    i;
// bad
for (i in values) {
    console.log(values[i]);
}

3.5 变量函数和运算符

3.5.1 变量声明

  • 变量先声明再使用
  • JavaScript没有块级变量声明
function doSomethingWithItems(items) {
    for (var i = 0, len = itmes.length; i < len; i++) {
        doSomething(items[i]);
    }
}

function doSomethingWithItems(items) {
    var i,
        len;

    for (i = 0, len = items.length; i < len; i++) {
        doSomething(items[i]);
    }
}

最佳实践:

  • 建议总是将局部变量的定义作为函数内第一条语句;
  • 并且使用单var模式(为了保持成本最低,推荐合并 var 语句)
function doSomethingWithItems(items) {
    var value = 10,
        result = value + 10,
        i,
        len;

    function doSomething(item) {
        // do something
    }

    for (i = 0, len = items.length; i < len; i++) {
        doSomething(items[i]);
    }
}

3.5.2 函数声明

// bad:调用代码在前,定义代码在后
doSomething();

function doSomething() {
    console.log('Hello world!');
}

// good:先定义,后使用
function doSomething() {
    console.log('Hello world!');
}

doSomething();

3.5.3 函数调用的间隔

// good
doSomething(itme);

// bad:多一个空格,断片了吧!手贱了吧!
doSomething (item);

3.5.4 即时调用的函数

// bad
var value = function () {
    // code

    return {
        message: 'Hi'
    };
}();

// good
var value = (function () {
    //code

    return {
        message: 'Hi'
    }
}());

3.5.5 严格模式

  • 不要使用全局的严格模式
  • 也不必每个函数都写严格模式
// bad:全局的严格模式
'use strict';
function doSomething() {
    //code
}

// bad
function doSomething() {
    'use strict';
    //code
}

// good
(function () {
    'use strict';

    function doSomething() {
        //code
    }

    function doSomethingElse() {
        //code
    }
})();

3.5.6 相等

掌握:== != 和 === !==

如果一个布尔值和数字比较,布尔值会首先转换位数字,然后进行比较。
false 值变为0、true变为1.

//数字1和true
console.log(1 == true); // true

//数字0和false
console.log(0 == false); // true

//数字2和false
console.log(2 == true); // false

如果其中一个值是对象而另一个不是,则会首先调用对象的valueOf()方法,
得到原始类型值再进行比较。如果没有定义valueOf(),则在调用toString()。
之后的比较操作就和上文提到的多类型值比较的情形了一样。

// 数字5和字符串5
console.log(5 == '5'); // true
console.log(5 === '5'); // false

// 数字25和16进制字符串25
console.log(25 == '0x19'); // true
console.log(25 === '0x19'); // false

// 数字1和true
console.log(1 == true); // true
console.log(1 === true); // false

// 数字0和false
console.log(0 == false); // true
console.log(0 === false); // false

// 数字2和true
console.log(2 == true); // false
console.log(2 === true); // false

3.6 避免使用全局变量

var color = 'red';

function sayColor() {
    console.log(color); // 不好的做法,color从哪里来的??
}

console.log(window.color); // 'red'

console.log(typeof window.sayColor); // 'function'

全局变量带来的问题

  • 命名冲突
  • 代码脆弱性
  • 难以测试

3.6.1 意外的全局变量

在处理老代码时要非常小心的使用严格模式,对于新代码最好使用严格模式来避免意外的全局变量,
同时一些常见的编码错误也能在严格模式中被捕捉到。

3.6.2 单全局变量方式

function Book(title) {
    this.title = title;
    this.page = 1;
}

Book.prototype.turnPage = function (direction) {
    this.page += direction;
}

var Chapter1 = new Book( 'Introction to Style GuideLines');
var Chapter2 = new Book( 'Basic Formatting');
var Chapter3 = new Book( 'Comments');

// 使用单全局变量

var MaintainableJS = {};

MaintainableJS.Book = function (title) {
    this.title = title;
    this.page = 1;
};

MaintainableJS.Book.turnPage = function (direction) {
    this.page = direction;
};

MaintainableJS.Chapter1 = new MaintainableJS.Book( 'Introction to Style GuideLines');
MaintainableJS.Chapter2 = new Book('Basic Formatting');
MaintainableJS.Chapter3 = new Book('Comments');

3.6.3 命名空间

var ZakasBooks = {};

// 编写可维护JavaScript的命名空间
ZakasBooks.MaintainableJavaScript = {};

// 高性能JavaScript的mingmingkongjian
ZakasBooks.HighPerformanceJavaScript = {};

var YourGlobal = {
    namespace: function (ns) {
        var parts = ns.split( '.'),
            object = this,
            i,
            len;
        for (i = 0, len = parts.length; i < len; i++) {
            if (!object[parts[i]]) {
                object[parts[i]] = {};
            }
            object = object[parts[i]];
        }
        return object;
    }
};

YourGlobal.namespace('Books.MaintainableJavaScript');

YourGlobal.Books.MaintainableJavaScript.author = 'Nicholas c. Zakas';

YourGlobal.namespace('Books.HighPerformaceJavaScript');

console.log(YourGlobal.Books.MaintainableJavaScript.author);

YourGlobal.namespace('Bookes').ANewBook = {};

//good- 拆分应用逻辑
var MyApplication = {

    dandleClick: function (event) {
        this.showPooup(event);
    },

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

};

addEventListener(element, 'click', function (event) {
    MyApplication.handleClick(event);
});

//good
var MyApplication = {

    dandleClick: function (event) {
        this.showPooup(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';
    }

};

addEventListener(element, 'click', function (event) {
    MyApplication.handleClick(event);
});

//good #3
//good
var MyApplication = {

    dandleClick: function (event) {
        /// <summary>
        /// 事件处理程序
        /// </summary>
        /// <param name="event"></param>

        //假设事件支持DOM Level2
        event.prventDefault();
        event.stopPropagation();

        //传入应用逻辑
        this.showPooup(event.clientX, event.clientY);
    },

    showPopup: function (x, y) {
        /// <summary>
        /// 应用逻辑
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        var popup = document.getElementById( 'popup');
        popup.style.left = x + 'px';
        popup.style.top = y + 'px';
        popup.className = 'reveal';
    }

};

addEventListener(element, 'click', function (event) {
    MyApplication.handleClick(event);
});

3.7 避免空比较

var Controller = {
    process: function (items) {
        if (items !== null) {//不好的写法
            itmes.sort();
            items.forEach( function (items) {
                //执行一些逻辑
            });
        }
    }
};

3.7.1 检测原始值

5种原始类型:字符串、数字、布尔值、null、undefined

如果希望一个值是字符串、数字、布尔值、undefined、最佳选择是typeof运算符

typeof 对5中原始类型的计算结果
typeof
字符串(String) "string"
数字(Number) "number"
布尔值(Boolean) "boolean"
undefined "undefined"
null "object" (低效的判断方法,建议直接使用===和!==)
typeof 对数组、正则表达式、日期、函数均返回 "object"

typeof运算符的独特之处在于,将其应用于一个未声明的变量也不会报错。

简单的和null比较通常不会包含足够的信息以判断值的类型是否合法,但有一个例外,如果所期望的值真的是null,
则可以直接和null比较,这时应当使用 === 或者 !== 来和null进行比较

var element = document.getElementById('my-div');
if (element !== null) { 
    // null是可以预见的输出
    element.className = 'found';
}

3.7.2 检测引用值

引用值也称作对象(object), 在JavaScript中除了原始值之外的值都是引用,
ObjectArrayDateError;

console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof new Date()); // "object"
console.log(typeof new RegExp()); // "object"
console.log(typeof null); // "object"

使用instanceof来检测引用类型

instanceof检测引用类型汇总
instanceof
Date检测日期
RegExp检测正则表达式
Error检测Error
// 检测日期date
if (value instanceof Date) {
    console.log(value.getFullYear());
}

// 检测正则表达式
if (value instanceof RegExp) {
    if (value.test(anotherValue)) {
        console.log( "Matches");
    }
}

// 检测Error
if (value instanceof Error) {
    throw value;
}

// IE8及更早版本的IE
console.log(typeof document.getElementById); // "object"
console.log(typeof document.createAttribute); // "object"
console.log(typeof document.getElementsByTagName); // "object"

function isJSON(value) {
    return Object.prototype.toString.call(value) === [ "object JSON"];
}

// 兼容的做法
function isArray(value) {
    if (typeof Array.isArray === "function") {
        return Array.isArray(value);
    } else {
        return Object.prototype.toString.call(value) === ["object Array"];
    }
}

3.7.3 检测属性

// bad:检测假值
if (object[propertyName]) {
    //code...
}

// bad:和null比较
if (object[propertyName] != null) {
    //code...
}

// bad:和undefined比较
if (object[propertyName] != undefined) {
    //code...
}

上面的代码里的每个判断,实际上是通过给定的名字来检查属性的值,而非判断
给定的名字所指的属性是否存在,因为当属性值为假(false)时结果会出错,
比如0""(empty string)、falsenullundefined

用in运算符判断属性是否存在

var obj = {
    count: 0,
    related: null
};

// good
if ( "count" in obj) {
    // 这里的代码会执行
    console.log("count is in obj");
}

// bad:检测假值
if (obj[ "count"]) {
    //这里的代码不会执行
}

// good
if ( "related" in obj) {
    //这里的代码不会执行
}

if (obj[ "releated"] != null) {
    //这里代码不会执行
}

if (obj.length) {
    console.log("nothing");
} else {
    console.log("obj.length is :" + obj.length); // obj.length is :undefined
}

如果你只想检查实例对象的某个属性是否存在则使用Object.hasOwnProperty()方法,

需要注意的是:
IE8及更早版本中,DOM对象并非继承自Object,所以不包含该方法

// 对于所有非DOM对象来说,这是好的写法
if (obj.hasOwnProperty( "related")) {
    // 执行这里的代码
}

// 如果你不确定是否为DOM对象,则这样来写
if ( "hasOwnProperty" in obj && obj.hasOwnProperty( "related")) {
    // 执行这里的代码
}

因为存在IE8以及更早版本的IE,在判断实例对象时候存在时,NC更倾向于使用in运算符,
只有在需要判断实例属性时才会使用到hasOwnProperty()

不管你什么时候需要检测属性的存在性,请使用in运算符或者hasOwnProperty(),
这样做可以避免很多Bug

Revision: 2015.11.06