深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点

来源:互联网 发布:网络缩写英文字母 编辑:程序博客网 时间:2024/05/19 19:40

书写可维护的代码(Writing Maintainable Code )

软件bug的修复是昂贵的,并且随着时间的推移,这些bug的成本也会增加,尤其当这些bug潜伏并慢慢出现在已经发布的软件中时。当你发现bug 的时候就立即修复它是最好的,此时你代码要解决的问题在你脑中还是很清晰的。否则,你转移到其他任务,忘了那个特定的代码,一段时间后再去查看这些代码就需要:

  • 花时间学习和理解这个问题
  • 花时间去了解应该解决的问题代码

还有问题,特别对于大的项目或是公司,修复bug的这位伙计不是写代码的那个人(且发现bug和修复bug的不是同一个人)。因此,必须降低理解代 码花费的时间,无论是一段时间前你自己写的代码还是团队中的其他成员写的代码。这关系到底线(营业收入)和开发人员的幸福,因为我们更应该去开发新的激动 人心的事物而不是花几小时几天的时间去维护遗留代码。

另一个相关软件开发生命的事实是,读代码花费的时间要比写来得多。有时候,当你专注并深入思考某个问题的时候,你可以坐下来,一个下午写大量的代码。

你的代码很能很快就工作了,但是,随着应用的成熟,还会有很多其他的事情发生,这就要求你的进行进行审查,修改,和调整。例如:

  • bug是暴露的
  • 新功能被添加到应用程序
  • 程序在新的环境下工作(例如,市场上出现新型浏览器)
  • 代码改变用途
  • 代码得完全从头重新,或移植到另一个架构上或者甚至使用另一种语言

由于这些变化,很少人力数小时写的代码最终演变成花数周来阅读这些代码。这就是为什么创建可维护的代码对应用程序的成功至关重要。

可维护的代码意味着:

  • 可读的
  • 一致的
  • 可预测的
  • 看上去就像是同一个人写的
  • 已记录


最小全局变量(Minimizing Globals)

JavaScript通过函数管理作用域。在函数内部声明的变量只在这个函数内部,函数外面不可用。另一方面,全局变量就是在任何函数外面声明的或是未声明直接简单使用的。

每个JavaScript环境有一个全局对象,当你在任意的函数外面使用this的时候可以访问到。你创建的每一个全部变量都成了这个全局对象的属 性。在浏览器中,方便起见,该全局对象有个附加属性叫做window,此window(通常)指向该全局对象本身。

myglobal = "hello";// 不推荐写法console.log(myglobal); // "hello"console.log(window.myglobal); // "hello"console.log(window["myglobal"]); // "hello"console.log(this.myglobal); // "hello"

全局变量的问题

全局变量的问题在于,你的JavaScript应用程序和web页面上的所有代码都共享了这些全局变量,他们住在同一个全局命名空间,所以当程序的两个不同部分定义同名但不同作用的全局变量的时候,命名冲突在所难免。

由于JavaScript的两个特征,不自觉地创建出全局变量是出乎意料的容易。首先,你可以甚至不需要声明就可以使用变量;第二,JavaScript有隐含的全局概念,意味着你不声明的任何变量都会成为一个全局对象属性。参考下面的代码:

function sum(x, y) {   // 不推荐写法: 隐式全局变量    result = x + y;   return result;}

另一个创建隐式全局变量的反例就是使用任务链进行部分var声明。下面的片段中,a是本地变量但是b却是全局变量,这可能不是你希望发生的:
// 反例,勿使用 function foo() {   var a = b = 0;   // ...}

此现象发生的原因在于这个从右到左的赋值,首先,是赋值表达式b = 0,此情况下b是未声明的。这个表达式的返回值是0,然后这个0就分配给了通过var定义的这个局部变量a。换句话说,就好比你输入了:
var a = (b = 0);

如果你已经准备好声明变量,使用链分配是比较好的做法,不会产生任何意料之外的全局变量,如:
function foo() {   var a, b;   // ... a = b = 0; // 两个均局部变量}

忘记var的副作用(Side Effects When Forgetting var)

隐式全局变量和明确定义的全局变量间有些小的差异,就是通过delete操作符让变量未定义的能力。

  • 通过var创建的全局变量(任何函数之外的程序中创建)是不能被删除的。
  • 无var创建的隐式全局变量(无视是否在函数中创建)是能被删除的。
这表明,在技术上,隐式全局变量并不是真正的全局变量,但它们是全局对象的属性。属性是可以通过delete操作符删除的,而变量是不能的:

// 定义三个全局变量var global_var = 1;global_novar = 2; // 反面教材(function () {  global_fromfunc = 3; // 反面教材}());// 试图删除delete global_var; // falsedelete global_novar; // truedelete global_fromfunc; // true// 测试该删除alert(typeof global_var); // "number"alert(typeof global_novar); // "undefined"alert(typeof global_fromfunc); // "undefined"

访问全局对象(Access to the Global Object)


单var形式(Single var Pattern)

在函数顶部使用单var语句是比较有用的一种形式,其好处在于:

  • 提供了一个单一的地方去寻找功能所需要的所有局部变量
  • 防止变量在定义之前使用的逻辑错误
  • 帮助你记住声明的全局变量,因此减少了全局变量//zxx:此处我自己是有点晕乎的…
  • 少代码(类型啊传值啊单线完成)
function func() {   var a = 1,       b = 2,       sum = a + b,       myobject = {},       i,       j;   // function body...}


您可以使用一个var语句声明多个变量,并以逗号分隔。像这种初始化变量同时初始化值的做法是很好的。这样子可以防止逻辑错误(所有未初始化但声明的变量的初始值是undefined)和增加代码的可读性。在你看到代码后,你可以根据初始化的值知道这些变量大致的用途,例如是要当作对象呢还是当作整数来使。

预解析:var散布的问题(Hoisting: A Problem with Scattered vars)

JavaScript中,你可以在函数的任何位置声明多个var语句,并且它们就好像是在函数顶部声明一样发挥作用,这种行为称为 hoisting(悬置/置顶解析/预解析)。当你使用了一个变量,然后不久在函数中又重新声明的话,就可能产生逻辑错误。对于JavaScript,只 要你的变量是在同一个作用域中(同一函数),它都被当做是声明的,即使是它在var声明前使用的时候。看下面这个例子:

// 反例myname = "global"; // 全局变量function func() {    alert(myname); // "undefined"    var myname = "local";    alert(myname); // "local"}func();
在这个例子中,你可能会以为第一个alert弹出的是”global”,第二个弹出”loacl”。这种期许是可以理解的,因为在第一个alert 的时候,myname未声明,此时函数肯定很自然而然地看全局变量myname,但是,实际上并不是这么工作的。第一个alert会弹 出”undefined”是因为myname被当做了函数的局部变量(尽管是之后声明的),所有的变量声明当被悬置到函数的顶部了。因此,为了避免这种混 乱,最好是预先声明你想使用的全部变量。

for循环(for Loops)


for-in循环(for-in Loops)


(不)扩展内置原型((Not) Augmenting Built-in Prototypes)

扩增构造函数的prototype属性是个很强大的增加功能的方法,但有时候它太强大了。

增加内置的构造函数原型(如Object(), Array(), 或Function())挺诱人的,但是这严重降低了可维护性,因为它让你的代码变得难以预测。使用你代码的其他开发人员很可能更期望使用内置的 JavaScript方法来持续不断地工作,而不是你另加的方法。

另外,属性添加到原型中,可能会导致不使用hasOwnProperty属性时在循环中显示出来,这会造成混乱。

switch模式(switch Pattern)


避免隐式类型转换(Avoiding Implied Typecasting )

JavaScript的变量在比较的时候会隐式类型转换。这就是为什么一些诸如:false == 0 或 “” == 0 返回的结果是true。为避免引起混乱的隐含类型转换,在你比较值和表达式类型的时候始终使用===和!==操作符。

var zero = 0;if (zero === false) {   // 不执行,因为zero为0, 而不是false}// 反面示例if (zero == false) {   // 执行了...}


避免(Avoiding) eval()


parseInt()下的数值转换(Number Conversions with parseInt())

使用parseInt()你可以从字符串中获取数值,该方法接受另一个基数参数,这经常省略,但不应该。当字符串以”0″开头的时候就有可能会出问 题,例如,部分时间进入表单域,在ECMAScript 3中,开头为”0″的字符串被当做8进制处理了,但这已在ECMAScript 5中改变了。为了避免矛盾和意外的结果,总是指定基数参数。
var month = "06",    year = "09";month = parseInt(month, 10);year = parseInt(year, 10);

此例中,如果你忽略了基数参数,如parseInt(year),返回的值将是0,因为“09”被当做8进制(好比执行 parseInt( year, 8 )),而09在8进制中不是个有效数字。

替换方法是将字符串转换成数字,包括:

+"08" // 结果是 8Number("08") // 8



编码规范(Coding Conventions)


缩进(Indentation)


花括号{}(Curly Braces)


左花括号的位置(Opening Brace Location)


空格(White Space)


分隔单词(Separating Words)


其它命名形式(Other Naming Patterns)

JavaScript中没有定义常量的方法(尽管有些内置的像Number, MAX_VALUE),所以开发者都采用全部单词大写的规范来命名这个程序生命周期中都不会改变的变量,如:
// 珍贵常数,只可远观var PI = 3.14,    MAX_WIDTH = 800;

还有另外一个完全大写的惯例:全局变量名字全部大写。全部大写命名全局变量可以加强减小全局变量数量的实践,同时让它们易于区分。

注释(Writing Comments)


关于作者(About the Author )


同步与结束语