《编写可维护的JavaScript》读书笔记之编程风格-变量、函数和运算符

变量、函数和运算符

变量声明

变量声明是通过 var 语句来完成的。JavaScript 中允许多次使用 var 语句,此外 var 语句几乎可以用在 JavaScript 脚本中的任意地方。

【注意】:不论 var 语句是否真正会被执行,所有的 var 语句都提前到包含这段逻辑的函数的顶部执行。

function doSomething() {
    
    var result = 10 + value;
    var value = 10;
    return result;
}

// 等价于
function doSomething() {
    
    var result;
    var value;
    
    result = 10 + value;
    value = 10;
    return result;
}

【说明】:在函数内部任意地方定义变量和在函数顶部定义变量是完全一样的。因此,一种流行的风格是将所有变量声明放在函数顶部而不是散落在各个角落。简言之,依照这种风格写出的代码逻辑和 JavaScript 引擎解析这段代码的习惯是非常相似的。

【建议】:总是将局部变量的定义作为函数内第一条语句。Crockford 编程规范、SproutCore 编程风格指南和 Dojo 编程风格指南也推荐这样做。

【其他】:

  • Crockford 编程规范推荐在函数顶部使用单 var 语句。
  • Dojo 编程风格指南规定,只有当变量之间有关联性时,才允许使用单 var 语句。

【推荐】:
将所有的 var 与合并为一个语句(单 var 语句),每个变量的初始化独占一行。对于那些没有初始值的变量来说,它们应当出现在 var 语句的尾部。

function doSomethingWithItems(items) {
    
    var value = 10,
        result = value + 10,
        i,
        len;
    
    // ...
}

【优点】:保持成本最低,推荐合并 var 语句,可以让代码更短、下载更快。

函数声明

和变量声明一样,函数声明会被 JavaScript 引擎提前。因此,在代码中函数的调用可以出现在函数声明之前。

// 不好的写法
doSomething();

function doSomething() {
    alert("Hello world!");
}

// 这段代码是可以正常运行的,会被引擎解析为:
function doSomething() {
    alert("Hello world!");
}

doSomething();

【推荐】:总是先声明 JavaScript 函数然后使用函数。Crockford 编程规范包含这种设计,还推荐函数内部的局部函数应当紧接着变量声明之后声明。

function doSomethingWithItems(items) {
    
    var i, len,
        value = 10,
        result = value + 10;
        
    function doSomething(items) {
        // 代码逻辑
    }
    
    // ...
}

【注意】:

  • 当函数在声明之前就使用时,在 JSLint 和 JSHint 中都会给出警告。
  • 函数声明不应当出现在语句块之内。
// 不好的写法
if (condition) {
    function doSomething() {
        alert("Hi!");
    }
} elsew {
    function doSomething() {
        alert("Yo!");
    }
}

【说明】:这段代码在不同浏览器中的运行结果也是不尽相同的。不管 condition 的计算结果如何,大多数浏览器都会自动使用第二个声明。而 Firefox 则根据 condition 的计算结果选用合适的函数声明。这种场景是 ECMAScript 的一个灰色地带,应当尽可能避免。函数声明应当在条件语句的外部使用。这种模式也是 Google 的 JavaScript 风格指南明确禁止的。

函数调用间隔

一般情况下,对于函数调用写法推荐的风格是,在函数名和左括号之间没有空格。这样做是为了将它和块语句(block statement)区分开来。

// 好的写法
doSomething(item);

// 不好的写法:看起来像一个块语句
doSomething (item);

// 用来做对比的块语句
while (item) {
    // 代码逻辑
}

【说明】:

  • Crockford 编程规范对此有明确的规定。
  • jQuery 核心风格指南中的规定更进一步,规定应当在左括号之后和右括号之前都加上空格。
// jQuery 风格
doSomething( item );
  • 这种风格是为了让参数更易读。但存在一些例外情况,特别是和这些传入单参数的函数相关的场景,这些参数包括对象直接量、数组直接量、函数表达式或者字符串。
// jQuery 例外情况
doSomething(function() {});
doSomething({ item: item });
doSomething([ item ]);
doSomething("Hi!");
  • 通常情况下,如果一个风格有超过一个例外,那么这种风格是不好的,因为这会给开发者带来一些困惑。

立即调用的函数

JavaScript 中允许声明匿名函数(本身没有命名的函数),并将匿名函数赋值给变量或者属性。

var doSomething = function() {
    // 函数体
};

【用法】:在匿名函数的尾部加上一对圆括号来立即执行并返回一个值,然后将这个值赋值给变量。

// 不好的写法
var value = function() {
    
    // 函数体
    
    return  {
        message: "Hi";
    }
}();

【问题】:会让人误以为将一个匿名函数赋值给了这个变量。除非读完整段代码看到最后一行的那对圆括号,否则你不会知道是将函数赋值给变量还是将函数的执行结果赋值给变量。这种困惑会影响代码的可读性。

【改进】:为了让立即执行函数能够被一眼看出来,可以将函数用一对圆括号包裹起来。

// 好的写法
var value = (function() {
    
    // 函数体
    
    return {
        messge: "Hi";
    }
}());

【说明】:通过圆括号将立即执行函数包裹这种方式,可以清晰地表明这是一个立即执行的函数,并且添加这一对圆括号并不会改变代码的逻辑。Crockford 编程规范推荐这种模式,并且在省略圆括号的情况下,JSLint 会发出警告。

严格模式

ECMAScript5 引入严格模式(strict mode),希望通过这种方式来谨慎地解析执行 JavaScript,以减少错误。

【指令】:

"use strict"

【说明】:

  • 尽管这看起来像是一个没有赋值给变量的字符串,但 ECMAScript5 JavaScript 引擎还是会将其识别为一条指令,以严格模式来解析代码。
  • 该编译指令(pragma)不仅用于全局,也适用于局部,比如一个函数内。但是不推荐将 “use strict” 用在全局作用域中(尽管所有流行的编程规范中都没有提及),因为这会让文件中的所有代码都以严格模式来解析,所以如果将若干个文件连接合并成一个文件时,当其中一个文件在全局作用域中启用了严格模式,则所有的代码都将以严格模式解析。由于严格模式中的运算符规则和在非严格模式下的情形有很大不同,因此其他文件中的(非严格模式下的)代码很可能会报错。

【示例】:

// 不好的写法:全局的严格模式
"use strict"
function doSomething() {
    // 代码
}

// 好的写法
function doSomething() {
    "use strict";
    // 代码
}

【技巧】:如果你希望在多个函数中应用严格模式而不必写很多行 “use strict” 的话,可以使用立即执行函数。

// 好的写法
(funciton() {
    "use strict";
    function doSomething() {
        // 代码
    }
    function doSomethingElse() {
        // 代码
    }
})();

【注意】:当 “use strict” 出现在函数体之外,在 JSLint 和 JSHint 中都会给出警告。

相等

由于 JavaScript 具有强制类型转换机制(type coercion),JavaScript 中的判断相等操作是很微妙的。对于某些运算来说,为了得到成功的结果,强制类型转换会驱使某种类型的变量自动转换成其他不同类型,这种情形往往会造成意想不到的结果。

【常见的场景】:
使用了判断相等运算符 == 和 != 的时候。当要比较的两个值的类型不同时,这两个运算符都会有强制类型转换。

  • 字符串和数字比较:字符串会被转换为数字,类似使用 Number() 转换函数。
  • 布尔值和数字比较:布尔值会首先转换为数字,然后进行比较。
  • 对象和其他类型比较:首先调用对象的 valueOf() 方法,得到原始类型值再进行比较。如果没有定义 valueOf(),则调用 toString()。
var object = {
    toString: function() {
        return "Ox19";
    }
};

console.log(object == 25);  // true
  • null 和 undefined 相等。

【说明】:

  • 由于强制类型转换的缘故,推荐不要使用 == 和 !=,而是应当使用 === 和 !==。用这些运算符作比较不会涉及强制类型转换。
  • jQuery 核心风格指南则允许在和 null 比较时用 ==,因为这时程序编写者往往是想判断值是否为 null 或 undefined。
if(object.a == null) {
    // 等价于 object.a === null || object.a === undefined
}

【推荐】:毫无例外地总是使用 === 和 !==。

【注意】:如果使用了 == 或 !=,JSLint 默认会报警告,而 JSHint 则会针对使用 == 或 != 来比较假值的情形报警告。

eval()

在 JavaScript 中,eval() 的参数是一个字符串,eval() 会将传入的字符串当作代码来执行。开发者可以通过这个函数来载入外部的 JavaScript 代码,或者随即生成 JavaScript 代码并执行它。

eval("alert('Hi!')");

var count = 10;
var number = eval("5 + count");
console.log(number);    // 15

【说明】:在 JavaScript 中 eval() 并不是唯一可以执行 JavaScript 字符串的函数,使用 Function 构造函数亦可以做到这一点,setTimeout() 和 setInterval() 也可以。

setTimeout("document.body.style.background='red'", 50);
setInterval("document.title = 'It is now '" + (new Date()), 1000);

【通用原则】:严禁使用 Function,并且只在别无他法时使用 eval()。setTimeout() 和 setInterval() 也是可以使用的,但不要用字符串形式而要用函数。

setTimeout(function() {
    document.body.style.background = 'red';
}, 50);

setInterval(function() {
    document.title = 'It is now ' + (new Date());
}, 1000);

【说明】:ECMAScript5 严格模式对于 eval() 有着严格的限制,禁止在一个封闭的作用域中使用它创建新变量或者函数。这条限制帮助我们避免了 eval() 先天的安全漏洞。

原始包装类型

JavaScript 中的一个不易被了解且被常常误解的方面是,这门语言对原始包装类型(primitive wrapper types)的依赖。

【概述】:JavaScript 里有 3 种原始包装类型:String、Boolean 和 Number。每种类型都代表全局作用域中的一个构造函数,并分别表示各自对应的原始值的对象。

【主要作用】:让原始值具有对象般的性行为。

var name = "Nicholas";
console.log(name.toUpperCase());

【说明】:尽管 name 是一个字符串,是原始类型不是的对象,但你仍然可以使用诸如 toUpperCase() 之类的方法,即将字符串当作对象来对待。这种做法之所以行得通,是因为在这条语句的表象背后 JavaScript 引擎创建了 String 类型的新实例,紧跟着就被销毁了,当再次需要时就会又创建另外一个对象。当再次需要时就会又创建另外一个对象。

【示例】:

var name = "Nicholas";
name.author = true;
console.log(name.author);   // undefined

【说明】:在第 2 行结束后,author 属性就不见了。因为表示在这个字符串的临时 String 对象在第 2 行执行结束后就销毁了,在第 3 行中又创建了一个新 String 对象,但此时该 String 对象没有 author 属性。

【建议】:尽管我们可以使用这些包装类型,但推荐大家避免使用它们。因为开发者的思路常常会在对象和原始值之间跳来跳去,这样会在增加出 bug 的概率,从而使开发者陷入困惑。何况也没有理由自己手动创建包装类型对象。

【注意】:Google 的 JavaScript 风格指南禁止使用原始包装类型。当你使用 String、Number 或 Boolean 来创建新对象时,JSLint 和 JSHint 都会给出警告。

猜你喜欢

转载自blog.csdn.net/weixin_43378396/article/details/85527467
今日推荐