diff --git a/chapter2.markdown b/chapter2.markdown index 97c1acd..7f7fead 100644 --- a/chapter2.markdown +++ b/chapter2.markdown @@ -1,19 +1,19 @@ # 第二章 概要 -本章将概要介绍一些编写高质量JavaScript的最佳实践、模式和习惯,比如避免全局变量、使用单`var`声明、预缓存循环中的`length`、遵守编码约定等等。本章还包括一些编程习惯,这些习惯跟具体的代码关系不大,而是更多关注代码创建的总体过程,包括撰写API文档、同事评审以及使用JSLint。这些习惯和最佳实践可以帮助你写出更好更易读和可维护性更好的代码,当几个月或数年后你再重读你的代码时,就会深有体会了。 +本章将概要介绍一些编写高质量 JavaScript 的最佳实践、模式和习惯,比如避免全局变量、使用单`var`声明、预缓存循环中的`length`、遵守编码约定等等。本章还包括一些编程习惯,这些习惯跟具体的代码关系不大,而是更多关注代码创建的总体过程,包括撰写 API 文档、同事评审以及使用 JSLint。这些习惯和最佳实践可以帮助你写出更好更易读和可维护性更好的代码,当几个月或数年后你再重读你的代码时,就会深有体会了。 ## 编写可维护的代码 -修复软件bug成本很高,而且随着时间的推移,修复这些bug的成本会越来越高,尤其以出现在已经打包发布的软件中的bug为最甚。发现bug时立刻解决掉是最好的,但前提是你对你的代码依然很熟悉,否则当你转身投入到另外一个项目的开发中后,已经根本不记得当初的代码的模样了。当过了一段时间后你再去阅读当初的代码时你需要更多的时间: +修复软件 bug 成本很高,而且随着时间的推移,修复这些 bug 的成本会越来越高,尤其以出现在已经打包发布的软件中的 bug 为最甚。发现 bug 时立刻解决掉是最好的,但前提是你对你的代码依然很熟悉,否则当你转身投入到另外一个项目的开发中后,已经根本不记得当初的代码的模样了。当过了一段时间后你再去阅读当初的代码时你需要更多的时间: - 重新学习并理解面临的问题 - 理解用于问题的代码 -在大项目或者大公司的软件开发中还有另一个问题,就是解决这个bug的人和制造这个bug的人往往不是同一个人(而发现bug的往往又是另外一个人)。因此不管是隔了很长时间重读自己的代码还是阅读团队内其他人的代码,减少理解代码所需的时间成本都是非常重要的。这对于公司的利益底线和工程师的幸福指数同样重要,因为每个人都宁愿去开发新的项目而不愿花很多时间和精力去维护旧代码。 +在大项目或者大公司的软件开发中还有另一个问题,就是解决这个 bug 的人和制造这个 bug 的人往往不是同一个人(而发现 bug 的往往又是另外一个人)。因此不管是隔了很长时间重读自己的代码还是阅读团队内其他人的代码,减少理解代码所需的时间成本都是非常重要的。这对于公司的利益底线和工程师的幸福指数同样重要,因为每个人都宁愿去开发新的项目而不愿花很多时间和精力去维护旧代码。 软件开发中的另一个普遍现象是,在读代码上花的时间要远远超过写代码的时间。当你专注于某个问题的时候,你往往会坐下来用一下午的时间写出大量的代码。在当时的场景下,这些代码是可以正常运行的,但当应用趋于成熟,会有很多因素促使你重读代码、改进代码或对代码做微调。比如: -- 发现了bug +- 发现了 bug - 需要给应用添加新需求 - 需要将应用迁移到新的平台中运行(比如当市场中出现了新的浏览器时) - 代码重构 @@ -33,9 +33,9 @@ ## 减少全局变量 -JavaScript使用函数来管理作用域,在一个函数内定义的变量称作“本地变量”,本地变量在函数外部是不能被访问的。与之相对,“全局变量”是不在任何函数体内部声明的变量,或者是直接使用而未声明的变量。 +JavaScript 使用函数来管理作用域,在一个函数内定义的变量称作“本地变量”,本地变量在函数外部是不能被访问的。与之相对,“全局变量”是不在任何函数体内部声明的变量,或者是直接使用而未声明的变量。 -每一个JavaScript运行环境都有一个“全局对象”,不在任何函数体内使用this就可以获得对这个全局对象的引用。你所创建的每一个全局变量都是这个全局对象的属性。为了方便起见,浏览器会额外提供一个全局对象的属性`window`,(一般)指向全局对象本身。下面的示例代码展示了如何在浏览器中创建或访问全局变量: +每一个 JavaScript 运行环境都有一个“全局对象”,不在任何函数体内使用 this 就可以获得对这个全局对象的引用。你所创建的每一个全局变量都是这个全局对象的属性。为了方便起见,浏览器会额外提供一个全局对象的属性`window`,(一般)指向全局对象本身。下面的示例代码展示了如何在浏览器中创建或访问全局变量: myglobal = "hello"; // 反模式 console.log(myglobal); // "hello" @@ -45,20 +45,20 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称 ### 全局变量的问题 -全局变量的问题是,它们在整个JavaScript应用或者是整个web页面中是始终被所有代码共享的。它们存在于同一个命名空间中,因此命名冲突的情况会时有发生,毕竟在应用程序的不同模块中,经常会出于某种目的定义相同的全局变量。 +全局变量的问题是,它们在整个 JavaScript 应用或者是整个 web 页面中是始终被所有代码共享的。它们存在于同一个命名空间中,因此命名冲突的情况会时有发生,毕竟在应用程序的不同模块中,经常会出于某种目的定义相同的全局变量。 同样,在网页中嵌入不是页面开发者编写的代码是很常见的,比如: -- 网页中使用了第三方的JavaScript库 +- 网页中使用了第三方的 JavaScript 库 - 网页中使用了广告代码 - 网页中使用了用以分析流量和点击率的第三方统计代码 - 网页中使用了很多组件、挂件和按钮等等 -假设某一段第三方提供的脚本定义了一个全局变量result。随后你在自己写的某个函数中也定义了一个全局变量result。这时,第二个变量就会覆盖第一个,会导致第三方脚本工作不正常。 +假设某一段第三方提供的脚本定义了一个全局变量 result。随后你在自己写的某个函数中也定义了一个全局变量 result。这时,第二个变量就会覆盖第一个,会导致第三方脚本工作不正常。 因此,为了让你的脚本和这个页面中的其他脚本和谐相处,要尽量少使用全局变量,这一点非常重要。本书随后的章节中会讲到一些减少全局变量的技巧和策略,比如使用命名空间或者即时函数等,但减少全局变量最有效的方法还是坚持使用`var`来声明变量。 -在JavaScript中有意无意地创建全局变量是件很容易的事,因为它有两个特性:首先,你可以不声明而直接使用变量,其次,JavaScirpt中具有“隐式全局对象”的概念,也就是说任何不通过`var`声明的变量都会成为全局对象的一个属性(可以把它们当作全局变量)。(译注:在ES6中可以通过`let`来声明块级作用域变量。)看一下下面这段代码: +在 JavaScript 中有意无意地创建全局变量是件很容易的事,因为它有两个特性:首先,你可以不声明而直接使用变量,其次,JavaScirpt 中具有“隐式全局对象”的概念,也就是说任何不通过`var`声明的变量都会成为全局对象的一个属性(可以把它们当作全局变量)。(译注:在 ES6 中可以通过`let`来声明块级作用域变量。)看一下下面这段代码: function sum(x, y) { // 反模式:隐式全局变量 @@ -68,7 +68,7 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称 这段代码中,我们直接使用了`result`而没有事先声明它。这段代码的确是可以正常工作,但被调用后会产生一个全局变量`result`,这可能会导致其他问题。 -解决办法是,总是使用var来声明变量,下面代码就是改进了的`sum()`函数: +解决办法是,总是使用 var 来声明变量,下面代码就是改进了的`sum()`函数: function sum(x, y) { var result = x + y; @@ -83,11 +83,11 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称 // ... } -为什么会这样呢?因为这里的计算顺序是从右至左的:首先计算表达式`b=0`,这里的`b`是未声明的;这个表达式的结果是`0`,然后通过var创建了本地变量`a`,并赋值为`0`。换言之,可以将代码写成这样: +为什么会这样呢?因为这里的计算顺序是从右至左的:首先计算表达式`b=0`,这里的`b`是未声明的;这个表达式的结果是`0`,然后通过 var 创建了本地变量`a`,并赋值为`0`。换言之,可以将代码写成这样: var a = (b = 0); -如果变量b已经被声明,这种链式赋值的写法是可以使用的,不会意外地创建全局变量,比如: +如果变量 b 已经被声明,这种链式赋值的写法是可以使用的,不会意外地创建全局变量,比如: function foo() { var a, b; @@ -97,7 +97,7 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称 > 避免使用全局变量的另一个原因是出于可移植性考虑,如果你希望将你的代码运行于不同的平台环境(宿主),那么使用全局变量就非常危险。因为很有可能你无意间创建的某个全局变量在当前的平台环境中是不存在的,你以为可以安全地使用,而在另一个环境中却是本来就存在的。 -### 忘记var时的副作用 +### 忘记 var 时的副作用 隐式创建的全局变量和显式定义的全局变量之间有着细微的差别,就是通过`delete`来删除它们的时候表现不一致。 @@ -123,7 +123,7 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称 typeof global_novar; // "undefined" typeof global_fromfunc; // "undefined" -在ES5严格模式中,给未声明的变量赋值会报错(比如这段代码中提到的两个反模式)。 +在 ES5 严格模式中,给未声明的变量赋值会报错(比如这段代码中提到的两个反模式)。 ### 访问全局对象 @@ -133,14 +133,14 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称 return this; }()); -这种方式总是可以访问到全局对象,因为在被当作函数(而不是构造函数)执行的函数体内,`this`总是指向全局对象。但这种情况在ECMAScript5的严格模式中行不通,因此在严格模式中你不得不寻求其他的替代方案。比如,如果你在开发一个库,你会将你的代码包装在一个即时函数中(在第四章会讲到),然后从全局作用域给这个匿名函数传入一个指向`this`的参数。 +这种方式总是可以访问到全局对象,因为在被当作函数(而不是构造函数)执行的函数体内,`this`总是指向全局对象。但这种情况在 ECMAScript5 的严格模式中行不通,因此在严格模式中你不得不寻求其他的替代方案。比如,如果你在开发一个库,你会将你的代码包装在一个即时函数中(在第四章会讲到),然后从全局作用域给这个匿名函数传入一个指向`this`的参数。 -### 单var模式 +### 单 var 模式 在函数的顶部使用唯一一个`var`语句是非常推荐的一种模式,它有如下一些好处: - 可以在同一个位置找到函数所需的所有变量 -- 避免在变量声明之前使用这个变量时产生的逻辑错误(参考下一小节“声明提前:分散的var带来的问题”) +- 避免在变量声明之前使用这个变量时产生的逻辑错误(参考下一小节“声明提前:分散的 var 带来的问题”) - 提醒你不要忘记声明变量,顺便减少潜在的全局变量 - 代码量更少(输入代码更少且更易做代码优化) @@ -156,9 +156,9 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称 // 函数体… } -你可以使用一个`var`语句来声明多个变量,变量之间用逗号分隔,也可以在这个语句中加入变量初始化的部分。这是一种非常好的实践方式,可以避免逻辑错误(所有未初始化的变量都被声明了,且值为undefined),并增加了代码的可读性。过段时间后再看这段代码,你可以从初始化的值中大概知道这个变量的用法,比如你一眼就可看出某个变量是对象还是整数。 +你可以使用一个`var`语句来声明多个变量,变量之间用逗号分隔,也可以在这个语句中加入变量初始化的部分。这是一种非常好的实践方式,可以避免逻辑错误(所有未初始化的变量都被声明了,且值为 undefined),并增加了代码的可读性。过段时间后再看这段代码,你可以从初始化的值中大概知道这个变量的用法,比如你一眼就可看出某个变量是对象还是整数。 -你可以在声明变量时做一些额外的工作,比如在这个例子中就写了`sum=a+b`这种代码。另一个例子就是当代码中用到对DOM元素时,你可以把DOM引用赋值的操作也放在这个变量声明语句中,比如下面这段代码: +你可以在声明变量时做一些额外的工作,比如在这个例子中就写了`sum=a+b`这种代码。另一个例子就是当代码中用到对 DOM 元素时,你可以把 DOM 引用赋值的操作也放在这个变量声明语句中,比如下面这段代码: function updateElement() { var el = document.getElementById("result"), @@ -166,9 +166,9 @@ JavaScript使用函数来管理作用域,在一个函数内定义的变量称 // 使用el和style… } -### 声明提前:分散的var带来的问题 +### 声明提前:分散的 var 带来的问题 -JavaScript允许在函数的任意地方写任意多个`var`语句,但它们的行为会像在函数体顶部声明变量一样,这种现象被称为“声明提前”,当你在声明语句之前使用这个变量时,可能会造成逻辑错误。对于JavaScript来说,一旦在某个作用域(同一个函数内)里声明了一个变量,那么这个变量在整个作用域内都是存在的,包括在`var`声明语句之前的位置。看一下这个例子: +JavaScript 允许在函数的任意地方写任意多个`var`语句,但它们的行为会像在函数体顶部声明变量一样,这种现象被称为“声明提前”,当你在声明语句之前使用这个变量时,可能会造成逻辑错误。对于 JavaScript 来说,一旦在某个作用域(同一个函数内)里声明了一个变量,那么这个变量在整个作用域内都是存在的,包括在`var`声明语句之前的位置。看一下这个例子: // 反模式 myname = "global"; // 全局变量 @@ -179,7 +179,7 @@ JavaScript允许在函数的任意地方写任意多个`var`语句,但它们 } func(); -这个例子中,你可能会期望第一个`alert()`弹出“global”,第二个`alert()`弹出“local”。这种结果看起来是合乎常理的,因为在第一个`alert()`执行时,`myname`还没有被声明,这时就应该“寻找”全局变量`myname`。但实际情况并不是这样,第一个`alert()`弹出“undefined”,因为`myname`已经在函数内被声明了(尽管声明语句在后面)。所有的变量声明都会被提前到函数的顶部,因此,为了避免类似带有“歧义”的程序逻辑,最好在使用之前一起声明它们。 +这个例子中,你可能会期望第一个`alert()`弹出“global”,第二个`alert()`弹出“local”。这种结果看起来是合乎常理的,因为在第一个`alert()`执行时,`myname`还没有被声明,这时就应该“寻找”全局变量`myname`。但实际情况并不是这样,第一个`alert()`弹出 “undefined”,因为`myname`已经在函数内被声明了(尽管声明语句在后面)。所有的变量声明都会被提前到函数的顶部,因此,为了避免类似带有“歧义”的程序逻辑,最好在使用之前一起声明它们。 上一个代码片段等价于下面这个代码片段: @@ -192,9 +192,9 @@ JavaScript允许在函数的任意地方写任意多个`var`语句,但它们 } func(); -> 这里有必要对“变量提前”做进一步补充,实际上从JavaScript引擎的工作机制上看,这个过程稍微有点复杂。代码处理经过了两个阶段:第一阶段是创建变量、函数和形参,也就是预编译的过程,它会扫描整段代码的上下文;第二阶段是在代码的运行时(runtime),这一阶段将创建函数表达式和一些非法的标识符(未声明的变量)。(译注:这两个阶段并没有包含代码的执行,是在执行前的处理过程。)从实用性角度来讲,我们更愿意将这两个阶段归成一个概念“变量提前”,尽管这个概念并没有在ECMAScript标准中定义,但我们常常用它来解释预编译的行为过程。 +> 这里有必要对“变量提前”做进一步补充,实际上从 JavaScript 引擎的工作机制上看,这个过程稍微有点复杂。代码处理经过了两个阶段:第一阶段是创建变量、函数和形参,也就是预编译的过程,它会扫描整段代码的上下文;第二阶段是在代码的运行时( runtime ),这一阶段将创建函数表达式和一些非法的标识符(未声明的变量)。(译注:这两个阶段并没有包含代码的执行,是在执行前的处理过程。)从实用性角度来讲,我们更愿意将这两个阶段归成一个概念“变量提前”,尽管这个概念并没有在 ECMAScript 标准中定义,但我们常常用它来解释预编译的行为过程。 -## for循环 +## for 循环 在`for`循环中,可以对数组或类似数组的对象(比如`arguments`和`HTMLCollection`对象)进行遍历,通常`for`循环模式形如: @@ -203,23 +203,23 @@ JavaScript允许在函数的任意地方写任意多个`var`语句,但它们 // 访问myarray[i]… } -这种模式的问题是,每次遍历都会访问数组的length属性,这会降低代码运行效率,特别是当`myarray`不是一个数组而是一个`HTMLCollection`对象的时候。 +这种模式的问题是,每次遍历都会访问数组的 length 属性,这会降低代码运行效率,特别是当`myarray`不是一个数组而是一个`HTMLCollection`对象的时候。 -`HTMLCollection`是由DOM方法返回的对象集合,比如: +`HTMLCollection`是由 DOM 方法返回的对象集合,比如: - document.getElementsByName() - document.getElementsByClassName() - document.getElementsByTagName() -还有一些`HTMLCollection`是在DOM标准诞生之前就已经在用了并且现在仍然可用,包括: +还有一些`HTMLCollection`是在 DOM 标准诞生之前就已经在用了并且现在仍然可用,包括: - document.images - 页面中所有的IMG元素 + 页面中所有的 IMG 元素 - document.links - 页面中所有的A元素 + 页面中所有的 A 元素 - document.forms @@ -229,7 +229,7 @@ JavaScript允许在函数的任意地方写任意多个`var`语句,但它们 页面中第一个表单的所有字段 -这些对象的问题在于,它们都会实时查询文档(HTML页面)中的对象。也就是说每次通过它们访问集合的`length`属性时,总是都会去查询DOM,而DOM操则是很耗资源的。 +这些对象的问题在于,它们都会实时查询文档( HTML 页面)中的对象。也就是说每次通过它们访问集合的`length`属性时,总是都会去查询 DOM,而 DOM 操做则是很耗资源的。 更好的办法是在`for`循环中缓存要遍历的数组的长度,比如下面这段代码: @@ -239,7 +239,7 @@ JavaScript允许在函数的任意地方写任意多个`var`语句,但它们 通过这种方法只需要获取`length`一次,然后在整个循环过程中使用它。 -不管在什么浏览器中,在遍历`HTMLCollection`时缓存`length`都可以让程序执行的更快,可以提速2倍(Safari3)到190倍(IE7)不等。更多细节可以参照Nicholas Zakas的《高性能JavaScript》,这本书也是由O'Reilly出版。 +不管在什么浏览器中,在遍历`HTMLCollection`时缓存`length`都可以让程序执行的更快,可以提速2倍(Safari3)到 190 倍(IE7)不等。更多细节可以参照Nicholas Zakas 的《高性能JavaScript》,这本书也是由O'Reilly出版。 需要注意的是,当你在循环过程中需要修改这个元素集合(比如增加DOM元素)时,你可能需要更新`length`。 @@ -257,17 +257,17 @@ JavaScript允许在函数的任意地方写任意多个`var`语句,但它们 当你越来越依赖“单`var`模式”时,带来的好处就是提高了代码的一致性。而缺点则是在重构代码的时候不能直接复制粘贴一个循环体,比如,你正在将某个循环从一个函数复制至另外一个函数中,那么必须确保`i`和`max`也复制到新函数里,并且需要从旧函数中将这些没用的变量删除掉。 -最后一个需要对循环做出调整的地方是将i++替换成为下面两者之一: +最后一个需要对循环做出调整的地方是将 i++ 替换成为下面两者之一: i = i + 1 i += 1 -JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读性,如果你觉得无所谓,可以将JSLint的`plusplus`选项设为`false`(默认为`true`)。稍后,在本书所介绍的最后一个模式中用到了:`i += 1`。 +JSLint 提示你这样做,是因为`++`和`--`实际上降低了代码的可读性,如果你觉得无所谓,可以将 JSLint 的`plusplus`选项设为`false`(默认为`true`)。稍后,在本书所介绍的最后一个模式中用到了:`i += 1`。 关于这种`for`模式还有两种变化的形式,做了少量改进,原因有二: - 减少一个变量(没有`max`) -- 减量循环至0,这种方式速度更快,因为和零比较要比和非零数字或数组长度比较要高效的多 +- 减量循环至 0,这种方式速度更快,因为和零比较要比和非零数字或数组长度比较要高效的多 第一种变化形式是: @@ -276,7 +276,7 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 // 访问myarray[i]… } -第二种变化形式用到了while循环: +第二种变化形式用到了 while 循环: var myarray = [], i = myarray.length; @@ -284,13 +284,13 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 // 访问myarray[i]… } -这些小改进只能体现在对性能要求比较苛刻的地方,此外,JSLint不推荐使用`i--`。 +这些小改进只能体现在对性能要求比较苛刻的地方,此外,JSLint 不推荐使用`i--`。 -## for-in循环 +## for-in 循环 `for-in`循环用于对非数组对象进行遍历。通过`for-in`进行循环也被称作“枚举”(enumeration)。 -从技术上讲,`for-in`循环同样可以用于数组(JavaScript中数组也是对象),但不推荐这样做。当数组对象被扩充了自定义函数时,可能会产生逻辑错误。另外,`for-in`循环中属性的遍历顺序是不固定的,所以最好数组使用普通的`for`循环,对象使用`for-in`循环。 +从技术上讲,`for-in`循环同样可以用于数组( JavaScript 中数组也是对象),但不推荐这样做。当数组对象被扩充了自定义函数时,可能会产生逻辑错误。另外,`for-in`循环中属性的遍历顺序是不固定的,所以最好数组使用普通的`for`循环,对象使用`for-in`循环。 可以使用对象的`hasOwnProperty()`方法过滤来自原型链中继承来的属性,这一点非常重要。看一下这段代码: @@ -307,7 +307,7 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 在这个例子中,我们使用对象字面量定义了一个名叫`man`的对象。在代码中的某个地方(可以是`man`定义之前也可以是之后),给`Object`的原型增加了一个方法`clone()`。原型链是实时的,这意味着所有的对象都可以访问到这个新方法。要想在枚举`man`的时候避免枚举出`clone()`方法,就需要调用`hasOwnProperty()`来过滤来自原型的属性。如果不做过滤,`clone()`也会被遍历到,这是我们不希望看到的: - // 1.for-in循环 + // 1.for-in 循环 for (var i in man) { if (man.hasOwnProperty(i)) { // filter console.log(i, ":", man[i]); @@ -321,7 +321,7 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 */ // 2.反模式: - // 不使用hasOwnProperty()过滤的for-in循环 + // 不使用hasOwnProperty()过滤的 for-in 循环 for (var i in man) { console.log(i, ":", man[i]); } @@ -351,11 +351,11 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 } } -> 严格说来,省略`hasOwnProperty()`并不是一个错误。根据具体的任务以及你对代码的自信程度,你可以省略掉它以提高一些程序执行效率。但当你对当前要遍历的对象不确定的时候,添加hasOwnProperty()则更加保险些。 +> 严格说来,省略`hasOwnProperty()`并不是一个错误。根据具体的任务以及你对代码的自信程度,你可以省略掉它以提高一些程序执行效率。但当你对当前要遍历的对象不确定的时候,添加 hasOwnProperty() 则更加保险些。 -这里介绍一种格式上的变种(这种写法无法通过JSLint检查),这种写法在`for`循环所在的行加入了`if`判断条件,他的好处是能让循环语句读起来更完整和通顺(“如果元素包含属性X,则对X做点什么”): +这里介绍一种格式上的变种(这种写法无法通过 JSLint 检查),这种写法在`for`循环所在的行加入了`if`判断条件,他的好处是能让循环语句读起来更完整和通顺(“如果元素包含属性 X,则对 X 做点什么”): - // 警告:无法通过JSLint检查 + // 警告:无法通过 JSLint 检查 var i, hasOwn = Object.prototype.hasOwnProperty; for (i in man) if (hasOwn.call(man, i)) { // 过滤 @@ -366,14 +366,14 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 我们可以扩充构造函数的`prototype`属性来为构造函数增加功能,这个特性非常强大,但有时会强大到超过我们的掌控。 -给内置构造函数如`Object()`、`Array()`、`Function()`扩充原型看起来非常诱人,但这种做法会严重降低代码的可维护性,因为它会让你的代码变得难以预测。对于那些基于你的代码来做开发的开发者来说,他们更希望使用原生的JavaScript方法来保持代码的一致性,而不愿意使用你所添加的方法。 +给内置构造函数如`Object()`、`Array()`、`Function()`扩充原型看起来非常诱人,但这种做法会严重降低代码的可维护性,因为它会让你的代码变得难以预测。对于那些基于你的代码来做开发的开发者来说,他们更希望使用原生的 JavaScript 方法来保持代码的一致性,而不愿意使用你所添加的方法。 另外,如果将属性添加至原型中,很可能导致原型上的属性在那些不使用`hasOwnProperty()`做过滤的循环中被遍历出来,从而造成混乱。 因此,不扩充内置对象的原型是最好的,你也可以自己定义一个规则,仅当下列条件满足时才考虑扩充内置对象的原型: -1. 未来的ECMAScript版本或者JavaScirpt会将你将要实现的方法添加为内置方法。比如,你可以实现ECMAScript5定义的一些方法,直到浏览器升级至支持ES5。这样,你只是提前定义了这些方法。 -2. 当某个属性或者方法是你在其它地方实现过的,或者是某个JavaScript引擎或浏览器的一部分,而你检查时又发现它不存在时。 +1. 未来的 ECMAScript 版本或者 JavaScirpt 会将你将要实现的方法添加为内置方法。比如,你可以实现 ECMAScript5 定义的一些方法,直到浏览器升级至支持 ES5。这样,你只是提前定义了这些方法。 +2. 当某个属性或者方法是你在其它地方实现过的,或者是某个 JavaScript 引擎或浏览器的一部分,而你检查时又发现它不存在时。 3. 在有充分的文档说明,并且和团队其他成员做了沟通的时候。 如果你遇到这三种情况之一,你可以给内置原型添加自定义方法,写法如下: @@ -384,7 +384,7 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 }; } -## switch模式 +## switch 模式 你可以通过下面这种模式来增强`switch`语句的可读性和健壮性: @@ -406,29 +406,29 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 - 每个`case`和`switch`对齐(这里不考虑花括号相关的缩进规则)。 - 每个`case`中的代码整齐缩进。 - 每个`case`都以`break`作为结束。 -- 避免连续执行多个case语句块(省略break时),如果你坚持认为连续执行多个`case`语句块是最好的方法,请务必补充文档说明,对于其他人来说,会觉得这种情况是错误的写法。 +- 避免连续执行多个 case 语句块(省略 break 时),如果你坚持认为连续执行多个`case`语句块是最好的方法,请务必补充文档说明,对于其他人来说,会觉得这种情况是错误的写法。 - 以`default`结束整个`switch`,以确保即便是在找不到匹配项时也有合理的结果。 ## 避免隐式类型转换 -在JavaScript对变量进行比较时会有一些隐式的数据类型转换。比如诸如`false == 0`或`"" == 0`之类的比较都返回`true`。 +在 JavaScript 对变量进行比较时会有一些隐式的数据类型转换。比如诸如`false == 0`或`"" == 0`之类的比较都返回`true`。 为了避免隐式类型转换对程序造成干扰,推荐使用`===`和`!==`运算符,它们除了比较值还会比较类型: var zero = 0; if (zero === false) { - // 不会执行,因为zero是0,不是false + // 不会执行,因为 zero 是 0 ,不是 false } // 反模式 if (zero == false) { // 代码块会执行… } -有一种观点认为当`==`够用的时候就不必使用`===`。比如,当你知道`typeof`的返回值是一个字符串,就不必使用全等运算符。但JSLint却要求使用全等运算符,这无疑会提高代码风格的一致性,并减少了阅读代码时的思考量(“这里使用`==`是故意的还是无意的?”)。 +有一种观点认为当`==`够用的时候就不必使用`===`。比如,当你知道`typeof`的返回值是一个字符串,就不必使用全等运算符。但 JSLint 却要求使用全等运算符,这无疑会提高代码风格的一致性,并减少了阅读代码时的思考量(“这里使用`==`是故意的还是无意的?”)。 -### 避免使用eval() +### 避免使用 eval() -当你想使用`eval()`的时候,不要忘了那句话“`eval()` is evil”(`eval()`是魔鬼)。这个函数的参数是一个字符串,它会将传入的字符串作为JavaScript代码执行。如果用来解决问题的代码是事先知道的(在运行之前),则没有理由使用`eval()`。如果需要在运行时动态生成并执行代码,那一般都会有更好的方式达到同样的目的,而非一定要使用`eval()`。例如,访问动态属性时可以使用方括号: +当你想使用`eval()`的时候,不要忘了那句话“`eval()` is evil”(`eval()`是魔鬼)。这个函数的参数是一个字符串,它会将传入的字符串作为 JavaScript 代码执行。如果用来解决问题的代码是事先知道的(在运行之前),则没有理由使用`eval()`。如果需要在运行时动态生成并执行代码,那一般都会有更好的方式达到同样的目的,而非一定要使用`eval()`。例如,访问动态属性时可以使用方括号: // 反模式 var property = "name"; @@ -437,9 +437,9 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 var property = "name"; alert(obj[property]); -`eval()`还有安全隐患,因为你有可能会运行一些被干扰过的代码(比如一段来自于网络的代码)。这是一种在处理Ajax请求所返回的JSON数据时比较常见的反模式。这种情况下最好使用浏览器的内置方法来解析JSON数据,以确保代码的安全性和数据的合法性。如果浏览器不支持`JSON.parse()`,你可以使用JSON.org所提供的库。 +`eval()`还有安全隐患,因为你有可能会运行一些被干扰过的代码(比如一段来自于网络的代码)。这是一种在处理 Ajax 请求所返回的 JSON 数据时比较常见的反模式。这种情况下最好使用浏览器的内置方法来解析 JSON 数据,以确保代码的安全性和数据的合法性。如果浏览器不支持`JSON.parse()`,你可以使用JSON.org 所提供的库。 -值得一提的是,多数情况下,给`setInterval()`、`setTimeout()`和`Function()`构造函数传入字符串的情形和`eval()`类似,这种用法也是应当避免的,因为这些情形中JavaScript最终还是会执行传入的字符串参数: +值得一提的是,多数情况下,给`setInterval()`、`setTimeout()`和`Function()`构造函数传入字符串的情形和`eval()`类似,这种用法也是应当避免的,因为这些情形中 JavaScript 最终还是会执行传入的字符串参数: // 反模式 setTimeout("myFunc()", 1000); @@ -488,33 +488,33 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 ## 使用parseInt()进行数字转换 -你可以使用`parseInt()`将字符串转换为数字。函数的第二个参数是进制参数,这个参数应该被指定,但却通常被省略。当字符串以0为前缀时转换就会出问题,例如,在表单中输入日期的一个字段。ECMAScript3中以0为前缀的字符串会被当作八进制数处理,这一点在ES5中已经有了改变。为了避免转换类型不一致而导致的意外结果,应当总是指定第二个参数: +你可以使用`parseInt()`将字符串转换为数字。函数的第二个参数是进制参数,这个参数应该被指定,但却通常被省略。当字符串以 0 为前缀时转换就会出问题,例如,在表单中输入日期的一个字段。ECMAScript3 中以 0 为前缀的字符串会被当作八进制数处理,这一点在 ES5 中已经有了改变。为了避免转换类型不一致而导致的意外结果,应当总是指定第二个参数: var month = "06", year = "09"; month = parseInt(month, 10); year = parseInt(year, 10); -在这个例子中,如果省略掉parseInt的第二个参数,比如`parseInt(year)`,返回的值是0,因为“09”被认为是八进制数(等价于`parseInt(year,8)`),但09是非法的八进制数。 +在这个例子中,如果省略掉 parseInt 的第二个参数,比如`parseInt(year)`,返回的值是 0,因为“09”被认为是八进制数(等价于`parseInt(year,8)`),但 09 是非法的八进制数。 字符串转换为数字还有两种方法: - +"08" // 结果为8 - Number("08") // 结果为8 + +"08" // 结果为 8 + Number("08") // 结果为 8 -这两种方法要比`parseInt()`更快一些,因为顾名思义`parseInt()`是一种“解析”而不是简单的“转换”。但当你期望将“08 hello”这类字符串转换为数字,则必须使用`parseInt()`,其他方法都会返回NaN。 +这两种方法要比`parseInt()`更快一些,因为顾名思义`parseInt()`是一种“解析”而不是简单的“转换”。但当你期望将“08 hello”这类字符串转换为数字,则必须使用`parseInt()`,其他方法都会返回 NaN。 ## 代码规范 确立并遵守代码规范非常重要,这会让你的代码风格一致、可预测,并且可读性更强。团队新成员通过学习代码规范可以很快进入开发状态,并写出让团队其他成员易于理解的代码。 -在开源社区和邮件组中关于编代风格的争论一直不断。(比如关于代码缩进,用tab还是空格?)因此,如果你打算在团队内推行某种编码规范时,要做好应对各种反对意见的心理准备,而且要吸取各种意见。确定并遵守代码规范非常重要,任何一种规范都可以,这甚至比代码规范中的具体约定是怎么样的还要重要。 +在开源社区和邮件组中关于编代风格的争论一直不断。(比如关于代码缩进,用 tab 还是空格?)因此,如果你打算在团队内推行某种编码规范时,要做好应对各种反对意见的心理准备,而且要吸取各种意见。确定并遵守代码规范非常重要,任何一种规范都可以,这甚至比代码规范中的具体约定是怎么样的还要重要。 ### 缩进 代码如果没有缩进就几乎不能读了,而不一致的缩进会使情况更加糟糕,因为它看上去像是遵守了规范,但真正读起来却没那么顺利。因此规范地使用缩进非常重要。 -有些开发者喜欢使用tab缩进,因为每个人都可以根据自己的喜好来调整tab缩进的空格数,有些人则喜欢使用空格缩进,通常是四个空格。这都无所谓,只要团队每个人都遵守同一个规范即可,本书中所有的示例代码都采用四个空格的缩进写法,这也是JSLint所推荐的。(译注:电子版中看到的是用tab缩进,本译文也保留使用tab缩进。) +有些开发者喜欢使用 tab 缩进,因为每个人都可以根据自己的喜好来调整 tab 缩进的空格数,有些人则喜欢使用空格缩进,通常是四个空格。这都无所谓,只要团队每个人都遵守同一个规范即可,本书中所有的示例代码都采用四个空格的缩进写法,这也是 JSLint 所推荐的。(译注:电子版中看到的是用 tab 缩进,本译文也保留使用 tab 缩进。) 那么到底什么时候应该缩进呢?规则很简单,花括号里的内容应当缩进,包括函数体、循环(`do`、`while`、`for`和`for-in`)体、`if`语句、`switch`语句和对象字面量里的属性。下面的代码展示了如何正确地使用缩进: @@ -562,7 +562,7 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 alert(i); } -同理,if条件句也应当如此: +同理,if 条件句也应当如此: // 不好的方式 if (true) @@ -593,7 +593,7 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 } -在这个例子中,这个问题只是个人偏好问题。但有时候花括号位置的不同会影响程序的执行,因为JavaScript会“自动插入分号”。JavaScript对行尾是否有分号并没有要求,它会自动将分号补全。因此,当函数的`return`语句返回了一个对象字面量,而对象的左花括号和`return`又不在同一行时,程序的执行就和预期的不同了: +在这个例子中,这个问题只是个人偏好问题。但有时候花括号位置的不同会影响程序的执行,因为 JavaScript 会“自动插入分号”。JavaScript 对行尾是否有分号并没有要求,它会自动将分号补全。因此,当函数的`return`语句返回了一个对象字面量,而对象的左花括号和`return`又不在同一行时,程序的执行就和预期的不同了: // 警告:返回值和预期的不同 function func() { @@ -603,7 +603,7 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 }; } -可以看出程序作者的意图是返回一个包含了`name`属性的对象,但实际情况不是这样。因为return后会填补一个分号,函数的返回值就是undefined。这段代码等价于: +可以看出程序作者的意图是返回一个包含了`name`属性的对象,但实际情况不是这样。因为 return 后会填补一个分号,函数的返回值就是 undefined 。这段代码等价于: // 警告:返回值和预期的不同 function func() { @@ -622,16 +622,16 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 }; } -> 关于分号也值得注意:和花括号一样,应当总是使用分号,尽管在JavaScript解析代码时会补全行末省略的分号,但严格遵守这条规则,可以让代码更加严谨,同时可以避免前面例子中所出现的歧义。 +> 关于分号也值得注意:和花括号一样,应当总是使用分号,尽管在 JavaScript解析代码时会补全行末省略的分号,但严格遵守这条规则,可以让代码更加严谨,同时可以避免前面例子中所出现的歧义。 ### 空格 -空格的使用同样有助于改善代码的可读性和一致性。在写英文句子的时候,在逗号和句号后面会使用间隔,在JavaScript中,你可以按照同样的逻辑在表达式(相当于逗号)和语句结束(相对于完成了某个“想法”的表达)后面添加间隔。 +空格的使用同样有助于改善代码的可读性和一致性。在写英文句子的时候,在逗号和句号后面会使用间隔,在 JavaScript 中,你可以按照同样的逻辑在表达式(相当于逗号)和语句结束(相对于完成了某个“想法”的表达)后面添加间隔。 适合使用空格的地方包括: -- for循环中的分号之后,比如`for (var i = 0; i < 10; i += 1) {...}` -- for循环中初始化多个变量,比如`for (var i = 0, max = 10; i < max; i += 1) {...}` +- for 循环中的分号之后,比如`for (var i = 0; i < 10; i += 1) {...}` +- for 循环中初始化多个变量,比如`for (var i = 0, max = 10; i < max; i += 1) {...}` - 用于分隔数组元素的逗号之后,比如`var a = [1, 2, 3];` - 对象属性后的逗号以及名值对之间的冒号之后,比如`var o = {a: 1, b: 2};` - 函数参数中,比如`myFunc(a, b, c)` @@ -671,13 +671,13 @@ JSLint提示你这样做,是因为`++`和`--`实际上降低了代码的可读 ### 构造函数命名中的大小写 -JavaScript中没有类,但有构造函数,可以通过`new`来调用构造函数: +JavaScript 中没有类,但有构造函数,可以通过`new`来调用构造函数: var adam = new Person(); 由于构造函数毕竟还是函数,如果只通过函数名就可分辨出它是构造函数还是普通函数是非常有用的。 -首字母大写可以提示你这是一个构造函数,而首字母小写的函数一般只认为它是普通的函数,不应该通过new来调用它: +首字母大写可以提示你这是一个构造函数,而首字母小写的函数一般只认为它是普通的函数,不应该通过 new 来调用它: function MyConstructor() {...} function myFunction() {...} @@ -692,13 +692,13 @@ JavaScript中没有类,但有构造函数,可以通过`new`来调用构造 那么对于那些不是函数的变量应当如何命名呢?变量名通常采用小驼峰式命名,还有一个不错的做法是,变量所有字母都是小写,单词之间用下划线分隔,比如,`first_name`、`favorite_bands`和`old_company_name`,这种方法可以帮助你区分函数和其他标识符如原始类型数据或对象。 -ECMAScript的属性和方法均使用驼峰式命名,尽管包含多个单词的属性名称并不多见(正则表达式对象的`lastIndex`和`ignoreCase`属性)。 +ECMAScript 的属性和方法均使用驼峰式命名,尽管包含多个单词的属性名称并不多见(正则表达式对象的`lastIndex`和`ignoreCase`属性)。 ### 其他命名风格 有时开发人员使用命名规范来弥补或代替语言特性的不足。 -比如,JavaScript中无法定义常量(尽管有一些内置常量比如`Number.MAX_VALUE`),所以开发者都采用了一种命名规范,对于那些程序运行周期内不会更改的变量使用全大写字母来命名。比如: +比如,JavaScript 中无法定义常量(尽管有一些内置常量比如`Number.MAX_VALUE`),所以开发者都采用了一种命名规范,对于那些程序运行周期内不会更改的变量使用全大写字母来命名。比如: // 常量,请勿修改 var PI = 3.14, @@ -706,7 +706,7 @@ ECMAScript的属性和方法均使用驼峰式命名,尽管包含多个单词 除了使用大写字母的命名方式之外,还有另一种命名规范:全局变量全大写。这种命名方式和“减少全局变量”的约定相辅相成,并让全局变量很容易辨认。 -除了常量和全局变量的命名规范,这里讨论另外一种命名规范,即私有变量的命名。尽管在JavaScript是可以实现真正的私有变量的,但开发人员更喜欢在私有成员或方法名之前加上下划线前缀,比如下面的例子: +除了常量和全局变量的命名规范,这里讨论另外一种命名规范,即私有变量的命名。尽管在 JavaScript 是可以实现真正的私有变量的,但开发人员更喜欢在私有成员或方法名之前加上下划线前缀,比如下面的例子: var person = { getName: function () { @@ -720,13 +720,13 @@ ECMAScript的属性和方法均使用驼峰式命名,尽管包含多个单词 } }; -在这个例子中,`getName()`是一个公有方法,是确定的API的一部分,而`_getFirst()`和`_getLast()`则是私有方法。尽管这两个方法本质上和公有方法没有区别,但在方法名前加下划线前缀就是为了告知用户不要直接使用这两个私有方法,因为不能保证它们在下一个版本中还能正常工作。JSLint会对私有方法作检查,除非设置了JSLint的`nomen`选项为`false`。 +在这个例子中,`getName()`是一个公有方法,是确定的 API 的一部分,而`_getFirst()`和`_getLast()`则是私有方法。尽管这两个方法本质上和公有方法没有区别,但在方法名前加下划线前缀就是为了告知用户不要直接使用这两个私有方法,因为不能保证它们在下一个版本中还能正常工作。JSLint 会对私有方法作检查,除非设置了 JSLint 的`nomen`选项为`false`。 下面介绍一些`_private`风格写法的变种: - 在名字尾部添加下划线以表明私有,比如`name_`和`getElements_()` - 使用一个下划线前缀表明受保护的属性`_protected`,用两个下划线前缀表明私有属性`__private` -- 在Firefox中实现了一些非标准的内置属性,这些属性在开头和结束都有两个下划线,比如`__proto__`和`__parent__` +- 在 Firefox 中实现了一些非标准的内置属性,这些属性在开头和结束都有两个下划线,比如`__proto__`和`__parent__` ## 写注释 @@ -738,17 +738,17 @@ ECMAScript的属性和方法均使用驼峰式命名,尽管包含多个单词 在下一小节我们会讲到,利用注释可以自动生成文档。 -## 写API文档 +## 写 API 文档 很多人都觉得写文档是一件很枯燥而且吃力不讨好的事情,但实际情况并不是这样。我们可以通过代码注释自动生成文档,这样就不用再去专门写文档了。很多人觉得这是一个不错的点子,因为根据某些关键字和特定的格式自动生成可阅读的参考手册本身就是“某种编程”。 -最早利用注释生成API文档的工具诞生自Java业界,这个工具名叫“javadoc”,和Java SDK(软件开发工具包)一起提供,但这个创意迅速被其他语言借鉴。JavaScript领域有两个非常优秀的开源工具,它们是JSDoc Toolkit()和YUIDoc()。 +最早利用注释生成 API 文档的工具诞生自 Java 业界,这个工具名叫“javadoc”,和 Java SDK(软件开发工具包)一起提供,但这个创意迅速被其他语言借鉴。JavaScript 领域有两个非常优秀的开源工具,它们是 JSDoc Toolkit()和YUIDoc()。 -生成API文档的过程: +生成 API 文档的过程: - 以特定的格式来写代码 - 运行工具来对代码和注释进行解析 -- 发布工具运行的结果,通常是HTML页面 +- 发布工具运行的结果,通常是 HTML 页面 这种语法包括十几种标签(tag),写法类似于: @@ -773,19 +773,19 @@ ECMAScript的属性和方法均使用驼峰式命名,尽管包含多个单词 ### 示例:YUIDoc -YUIDoc的初衷是为YUI(Yahoo! User Interface)库生成文档,但其实它也可以应用于任何项目。为了更充分的使用YUIDoc,你需要学习它的注释规范,比如模块和类的写法。(尽管在JavaScript中其实是没有类的概念的)。 +YUIDoc 的初衷是为 YUI(Yahoo! User Interface)库生成文档,但其实它也可以应用于任何项目。为了更充分的使用 YUIDoc,你需要学习它的注释规范,比如模块和类的写法。(尽管在 JavaScript 中其实是没有类的概念的)。 -让我们看一个用YUIDoc生成文档的完整例子。 +让我们看一个用 YUIDoc 生成文档的完整例子。 -图2-1展示了最终生成的文档的样子,你可以根据项目需要定制HTML模板,让生成的文档更加友好和个性化。 +图2-1展示了最终生成的文档的样子,你可以根据项目需要定制 HTML 模板,让生成的文档更加友好和个性化。 -这里提供了在线的demo,请参照。 +这里提供了在线的 demo,请参照。 -这个例子中所有的应用作为一个模块(myapp)放在一个文件里(app.js),后续的章节会更详细的介绍模块,现在只需知道用可以用一个YUIDoc的标签来表示模块即可。 +这个例子中所有的应用作为一个模块(myapp)放在一个文件里(app.js),后续的章节会更详细的介绍模块,现在只需知道用可以用一个 YUIDoc 的标签来表示模块即可。 -图2-1 YUIDoc生成的文档 +图2-1 YUIDoc 生成的文档 -![YUIDoc生成的文档](./Figure/chapter2/2-1.jpg) +![ YUIDoc 生成的文档](./Figure/chapter2/2-1.jpg) `app.js`的开始部分: @@ -840,7 +840,7 @@ YUIDoc的初衷是为YUI(Yahoo! User Interface)库生成文档,但其实 - `@class` - 代表一个对象或构造函数(JavaScript中没有类) + 代表一个对象或构造函数( JavaScript 中没有类) - `@method` @@ -852,9 +852,9 @@ YUIDoc的初衷是为YUI(Yahoo! User Interface)库生成文档,但其实 - `@return` - 和@param类似,用以描述方法的返回值,可以不带名字 + 和 @param 类似,用以描述方法的返回值,可以不带名字 -我们来实现第二个“类”,使用一个构造函数,并给这个构造函数的原型添加一个方法,看看YUIDoc在面对不同的对象创建方式时是如何工作的: +我们来实现第二个“类”,使用一个构造函数,并给这个构造函数的原型添加一个方法,看看 YUIDoc 在面对不同的对象创建方式时是如何工作的: /** * Constructs Person objects @@ -893,11 +893,11 @@ YUIDoc的初衷是为YUI(Yahoo! User Interface)库生成文档,但其实 - `@constructor` 说明这个“类”其实是一个构造函数 - `@prototype` 和 `@type` 用来描述对象的属性 -YUIDoc工具是与语言无关的,只解析注释块,而不是JavaScript代码。它的缺点是必须要在注释中指定属性、参数和方法的名字,比如,`@property first_name`。好处是一旦你熟练掌握YUIDoc,就可以用它对任何语言源码生成文档。 +YUIDoc 工具是与语言无关的,只解析注释块,而不是 JavaScript 代码。它的缺点是必须要在注释中指定属性、参数和方法的名字,比如,`@property first_name`。好处是一旦你熟练掌握 YUIDoc,就可以用它对任何语言源码生成文档。 ## 编写易读的代码 -这种编写注释块来生成API文档的做法可不仅仅是为了偷懒,它还有另外一个作用,就是通过回头重看代码来提高代码质量。 +这种编写注释块来生成 API 文档的做法可不仅仅是为了偷懒,它还有另外一个作用,就是通过回头重看代码来提高代码质量。 随便一个作者或者编辑都会告诉你“编辑非常重要”,甚至是写一本好书或好文章最最重要的步骤。将想法落实在纸上形成草稿只是第一步,草稿确实可以给读者提供不少信息,但往往还不是重点最明晰、结构最合理、最符合阅读习惯的呈现形式。 @@ -905,7 +905,7 @@ YUIDoc工具是与语言无关的,只解析注释块,而不是JavaScript代 你往往会在写注释文档的时候发现很多问题,也会重新思考代码中的不合理之处,比如,某个方法中的第三个参数比第二个参数更常用,第二个参数多数情况下取值为`true`,因此就需要对这个方法进行适当的改造和包装。 -写出易读的代码(或API),是指写代码时要有让别人能轻易读懂的意识。带着这个意识,你就需要不断思考采用更好的方法来解决手头的问题。 +写出易读的代码(或 API ),是指写代码时要有让别人能轻易读懂的意识。带着这个意识,你就需要不断思考采用更好的方法来解决手头的问题。 说回“草稿”的问题,也算是“抱佛脚”的权宜之计,一眼看上去是有点“草”,不过至少是有用的,特别是当你处理的是一个关键项目时(比如人命关天时)。一个合适的思路是,你应当始终扔掉你所给出的第一个解决方案,虽然它是可以正常工作的,但毕竟是一个草稿,是一种仅用于验证解决问题可行性的方案。事实上,第二个方案往往会更好,因为这时你对问题的理解会更加透彻。在产生第二个方案的过程中,不要允许自己去复制粘贴之前的代码,这有助于阻止自己投机取巧利用之前的捷径,最后产生不完美的方案。 @@ -913,46 +913,46 @@ YUIDoc工具是与语言无关的,只解析注释块,而不是JavaScript代 另外一种可以提高代码质量的方法是组织相互评审。同事评审可以用一些工具辅助,可以很正式很规范,也是一种开发流程中值得提倡的步骤。你可能觉得没有时间去作代码互相评审,没关系,你可以让坐在你旁边的同事读一下你的代码,或者和她(译注:注意是“她”而不是“他”)一起过一遍你的代码。 -同样,当你在写API文档或者其他文档的时候,同事评审能让你的产出物更加清晰,因为你写的文档是本来就是让别人读的,你得让别人通过文档知道你所做的东西。 +同样,当你在写 API 文档或者其他文档的时候,同事评审能让你的产出物更加清晰,因为你写的文档是本来就是让别人读的,你得让别人通过文档知道你所做的东西。 同事评审是一种很好的实践,不仅仅是因为它能让代码变得更好,更重要的是,在评审的过程中,评审人和代码作者通过分享和讨论,两人都能取长补短、相互促进。 如果你的团队只有你一个开发人员,找不出第二个人能给你作代码评审,这也没关系。你可以通过将你的代码片段开源,或把有意思的代码片段贴在博客中,让全世界的人为你评审。 -另外一个很好的实践是使用版本管理工具(CVS、SVN或Git),一旦有人修改并提交了代码,就会发邮件通知组内成员。虽然大部分邮件都进入了垃圾箱,但总是会碰巧有人在工作间隙看到你所提交的代码,并对代码做出一些评价。 +另外一个很好的实践是使用版本管理工具( CVS 、SVN 或 Git ),一旦有人修改并提交了代码,就会发邮件通知组内成员。虽然大部分邮件都进入了垃圾箱,但总是会碰巧有人在工作间隙看到你所提交的代码,并对代码做出一些评价。 ## 发布时的代码压缩(Minify) -这里所说的代码压缩(Minify)是指去除JavaScript代码中的空格、注释以及其他不必要的部分,用以减少JavaScript文件的体积,降低网络带宽消耗。我们通常使用压缩工具来进行压缩,比如YUICompressor(Yahoo!)或Closure Compiler(Google),这可以减少页面加载时间。压缩用于发布的的脚本是很重要的,压缩后的文件体积能减少至原来的一半以下。 +这里所说的代码压缩(Minify)是指去除 JavaScript 代码中的空格、注释以及其他不必要的部分,用以减少 JavaScript 文件的体积,降低网络带宽消耗。我们通常使用压缩工具来进行压缩,比如 YUICompressor(Yahoo!)或 Closure Compiler(Google),这可以减少页面加载时间。压缩用于发布的的脚本是很重要的,压缩后的文件体积能减少至原来的一半以下。 -下面这段代码是压缩后的样子(这段代码是YUI2库中的事件模块): +下面这段代码是压缩后的样子(这段代码是 YUI2 库中的事件模块): YAHOO.util.CustomEvent=function(D,C,B,A){this.type=D;this.scope=C||window;this.silent =B;this.signature=A||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent) {}var E="_YUICEOnSubscribe";if(D!==E){this.subscribeEvent=new YAHOO.util.CustomEvent(E,this,true);}... -除了去除空格、空行和注释之外,压缩工具还能缩短命名的长度(在保证代码安全的前提下),比如这段代码中的参数`A`、`B`、`C`、`D`。压缩工具只会重命名局部变量,因为更改全局变量会破坏代码的逻辑,这也是要尽量使用局部变量的原因。如果你使用的全局变量是对DOM节点的引用,而且程序中多次用到,那么最好将它赋值给一个局部变量,这样能提高查找速度,代码也会运行的更快,此外还能提高压缩比、加快下载速度。 +除了去除空格、空行和注释之外,压缩工具还能缩短命名的长度(在保证代码安全的前提下),比如这段代码中的参数`A`、`B`、`C`、`D`。压缩工具只会重命名局部变量,因为更改全局变量会破坏代码的逻辑,这也是要尽量使用局部变量的原因。如果你使用的全局变量是对 DOM 节点的引用,而且程序中多次用到,那么最好将它赋值给一个局部变量,这样能提高查找速度,代码也会运行的更快,此外还能提高压缩比、加快下载速度。 -补充说明一下,Goolge Closure Compiler还会为了更高的压缩比对全局变量进行压缩(在“高级”模式中),这是很危险的,且对编程规范的要求非常苛刻。 +补充说明一下,Goolge Closure Compiler 还会为了更高的压缩比对全局变量进行压缩(在“高级”模式中),这是很危险的,且对编程规范的要求非常苛刻。 对用于生产环境的脚本做压缩是非常重要的步骤,因为它能提升页面性能,但你应当将这个过程交给工具来完成。千万不要试图手写“压缩好的”代码,你应当在编写代码时坚持使用语义化的变量命名,并保留足够的空格、缩进和注释。你写的代码是需要被人阅读的,所以应当将注意力放在代码可读性和可维护性上,将代码压缩的工作交给工具去完成。 ## 运行JSLint -在上一章我们已经介绍了JSLint,本章中也提到了数次。到现在你应该已经相信用JSLint检查你的代码是一种好的编程模式了。 +在上一章我们已经介绍了 JSLint,本章中也提到了数次。到现在你应该已经相信用 JSLint 检查你的代码是一种好的编程模式了。 -JSLint的检查点都有哪些呢?它会对本章讨论过的一些模式(单`var`模式、`parseInt()`的第二个参数、总是使用花括号)做检查。JSLint还包括其他方面的检查: +JSLint 的检查点都有哪些呢?它会对本章讨论过的一些模式(单`var`模式、`parseInt()`的第二个参数、总是使用花括号)做检查。JSLint 还包括其他方面的检查: - 不可达代码(译注:指永远不可能运行的代码) - 变量在声明之前被使用 -- 不安全的UTF字符 +- 不安全的 UTF 字符 - 使用`void`、`with`或者`eval` - 无法正确解析的正则表达式 -JSLint是基于JavaScript实现的(它自己的代码是可以通过JSLint检查的),它提供了在线工具,也可以下载使用,可以运行于很多种平台的JavaScript解析器。你可以将源码下载后在本地运行,支持的环境包括WSH(Windows Scripting Host,Windows)、JSC(JavaScriptCore,MacOSX)或Rhino(Mozilla开发的JavaScript引擎)。 +JSLint 是基于 JavaScript 实现的(它自己的代码是可以通过 JSLint 检查的),它提供了在线工具,也可以下载使用,可以运行于很多种平台的 JavaScript 解析器。你可以将源码下载后在本地运行,支持的环境包括 WSH(Windows Scripting Host,Windows)、JSC(JavaScriptCore,MacOSX)或Rhino(Mozilla 开发的 JavaScript 引擎)。 -将JSLint下载后和你的代码编辑器配置在一起是个很不错的主意,这样每次你保存代码的时候都会自动执行代码检查。(为它配置一个快捷键也很有用)。 +将 JSLint 下载后和你的代码编辑器配置在一起是个很不错的主意,这样每次你保存代码的时候都会自动执行代码检查。(为它配置一个快捷键也很有用)。 ## 小结 @@ -963,4 +963,4 @@ JSLint是基于JavaScript实现的(它自己的代码是可以通过JSLint检 - `for`循环、`for-in`循环、`switch`语句、“避免使用`eval()`”、不要扩充内置原型 - 遵守统一的编码规范(在任何必要的时候保持空格、缩进、花括号和分号)和命名规范(构造函数、普通函数和变量)。 -本章还讨论了其他一些和代码本身无关的实践,这些实践和编码过程紧密相关,包括写注释、写API文档、组织同事评审、不要试图去手动“压缩”(minify)代码而牺牲代码可读性、坚持使用JSLint来对代码进行检查。 +本章还讨论了其他一些和代码本身无关的实践,这些实践和编码过程紧密相关,包括写注释、写 API 文档、组织同事评审、不要试图去手动“压缩”(minify)代码而牺牲代码可读性、坚持使用 JSLint 来对代码进行检查。