编写高质量代码:改善JavaScript程序的188个建议(txt+pdf+epub+mobi电子书下载)


发布时间:2020-08-06 06:40:52

点击下载

作者:成林

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

编写高质量代码:改善JavaScript程序的188个建议

编写高质量代码:改善JavaScript程序的188个建议试读:

前言

为什么要写这本书

JavaScript是目前比较流行的Web开发语言。随着移动互联网、云计算、Web 3.0和客户端开发概念的升温,JavaScript语言不断成熟和普及,并被广泛应用于各种B/S架构的项目和不同类型的网站中。对于JavaScript初学者、网页设计爱好者以及Web应用开发者来说,熟练掌握JavaScript语言是必需的。

JavaScript语言的最大优势在于灵活性好,适应能力强。借助各种扩展技术、开源库或框架,JavaScript能够完成Web开发中各种复杂的任务,提升客户端用户体验。

作为资深的Web开发人员,笔者已经习惯了与高性能的编程语言和硬件打交道,因此一开始并没有对JavaScript编程有太高的期望。后来才发现,JavaScript实际上是一种优秀且高效的编程语言,而且随着浏览器对其更好的支持、JavaScript语言本身的性能提升,以及新的工具库加入,JavaScript不断变得更好。JavaScript结合HTML 5等为Web开发人员提供了真正可以发挥想象力的空间。Node.js等新技术则为使用JavaScript对服务器进行编程描绘了非常美好的未来。

但是,在阅读网上大量散存的JavaScript代码时,笔者能明显感觉到很多用户正在误入“歧途”:编写的代码逻辑不清,结构混乱,缺乏编程人员应有的基本素养。这种现状一般都是用户轻视JavaScript语言所致。还有很多用户属于“半路出家”,误认为JavaScript就是一种“玩具语言”,没有以认真的态度对待和学习这门语言,书写代码也很随意。因此,笔者萌生了写一本以提高JavaScript代码编写质量为目的的书籍,在机械工业出版社华章公司杨福川编辑的鼓励和指导下,经过近半年的策划和准备,终于鼓起勇气动笔了。本书特色

❑深。本书不是一本语法书,它不会教读者怎么编写JavaScript代码,但它会告诉读者,为什么Array会比String类型效率高,闭包的自增是如何实现的,为什么要避免DOM迭代……不仅仅告诉读者How(怎么做),而且还告诉读者Why(为什么要这样做)。

❑广。涉及面广。从编码规则到编程思想,从基本语法到系统框架,从函数式编程到面向对象编程,都有涉及,而且所有的建议都不是“纸上谈兵”,都与真实的场景相结合。

❑点。从一个知识点展开讲解,比如继承,这里不提供继承的解决方案,而是告诉读者如何根据需要使用继承,如何设置原型,什么时候该用类继承,什么时候该用原型继承等。

❑精。简明扼要。一个建议就是对一个问题的解释和说明,以及相关的解决方案,不拖泥带水,只针对一个问题进行讲解。

❑洁。虽然笔者尽力把每个知识点写得生动,但代码就是代码,很多时候容不得深加工,最直接也就是最简洁的。

这是一本建议书。有这样一本书籍在手边,对如何编写出优雅而高效的代码提供指导,将是一件多么惬意的事情啊!读者对象

本书适合以下读者阅读:

❑打算学习JavaScript的开发人员。

❑有意提升自己网站水平和Web应用程序开发能力的Web开发人员。

❑希望全面深入理解JavaScript语言的初学者。

此外,本书也适合熟悉下列相关技术的读者阅读:

❑PHP/ASP/JSP

❑HTML/XML

❑CSS

对于没有计算机基础知识的初学者,以及只想为网站添加简单特效和交互功能的读者,阅读本书前建议先阅读JavaScript基础教程类图书。如何阅读本书

本书将改善JavaScript编程质量的188个建议以9章内容呈现:

❑第1章 JavaScript语言基础

JavaScript中存在大量的问题,这些问题会妨碍读者编写优秀的程序。应该避免JavaScript中那些糟糕的用法,因此本章主要就JavaScript语言的一些基本用法中容易犯错误的地方进行说明,希望能够引起读者的重视。

❑第2章 字符串、正则表达式和数组

JavaScript程序与字符串操作紧密相连,在进行字符串处理时无时无刻不需要正则表达式的帮忙。如何提高字符串操作和正则表达式运行效率是很多开发者最易忽视的问题。同时,数组是所有数据序列中运算速度最快的一种类型,但很多初学者忽略了这个有用的工具。本章将就这3个技术话题展开讨论,通过阅读这些内容相信读者能够提高程序的执行效率。

❑第3章 函数式编程

函数式编程已经在实际应用中发挥了巨大作用,越来越多的语言不断地加入对诸如闭包、匿名函数等的支持。从某种程度上来讲,函数式编程正在逐步同化命令式编程。当然,用好函数并非易事,需要“吃透”函数式编程的本质,本章帮助读者解决在函数式编程中遇到的各种问题。

❑第4章 面向对象编程

JavaScript采用的是以对象为基础,以函数为模型,以原型为继承机制的开发模式。因此,对于习惯于面向对象开发的用户来说,需要适应JavaScript语言的灵活性和特殊性。本章将就JavaScript类、对象、继承等抽象的问题进行探索,帮助读者走出“误区”。

❑第5章 DOM编程

DOM操作代价较高,在富网页应用中通常是一个性能瓶颈。因此,在Web开发中,需要特别注意性能问题,尽可能地降低性能损耗。本章将为读者提供一些好的建议,帮助读者优化自己的代码,让程序运行得更快。

❑第6章 客户端编程

在JavaScript开发中,很多交互效果都需要CSS的配合才能够实现,因此CSS的作用不容忽视。本章主要介绍JavaScript+CSS脚本化编程,以及JavaScript事件控制技巧。

❑第7章 数据交互和存储

数据交互和存储是Web开发中最重要的,也是最容易被忽视的问题,它也是高性能JavaScript的基石,是提升网站可用性的最大要素。本章主要介绍如何使用JavaScript提升数据交互的反应速度,以便更好地让数据在前、后台传递。

❑第8章 JavaScript引擎与兼容性

JavaScript兼容性是Web开发的一个重要问题。为了实现浏览器解析的一致性,需要找出不同引擎的分歧点在哪里。本章主要介绍各主流引擎在解析JavaScript代码时的分歧,使读者能够编写出兼容性很高的代码。

❑第9章 JavaScript编程规范和应用

每种语言都存在缺陷。事实证明代码风格在编程中是非常重要的,好的风格促使代码能被更好地阅读,更为关键的是,它能够提高代码的执行效率。本章主要介绍如何提升JavaScript代码编写水平,主要包括风格、习惯、效率、协同性等问题,希望能够给读者带来帮助。本书的期望

您是否曾经为了提供一个简单的应用解决方案而彻夜地查看源代码?

您是否曾经为了理解某个框架而冥思苦想、阅览群书?

您是否曾经为了提升0.1s的DOM性能而对多种实现方案进行严格测试和对比?

您是否曾经为了避免兼容问题而遍寻高手共同“诊治”?

……

在学习和使用JavaScript的过程中,您是否在原本可以很快掌握或解决的问题上耗费了大量的时间和精力?本书的很多内容都是笔者曾经付出代价换来的,希望它们能够给您带来一些帮助!

代码是一切的基石,一切都是以编码实现为前提的,通过阅读本书,期望为读者带来如下帮助:

❑能写出简单、清晰、高效的代码。

❑能架构一个稳定、健壮、快捷的应用框架。

❑能回答一个困扰很多人的技术问题。

❑能修复一个应用开发中遇到的大的Bug。

❑能非常熟悉某个开源产品。

❑能提升客户端应用性能。

……

但是,“工欲善其事,必先利其器”,在“善其事”之前,先检查“器”是否已经磨得足够锋利了,是否能够在前进的路上披荆斩棘。无论将来的职业发展方向是架构师、设计师、分析师、管理者,还是其他职位,只要还与软件打交道,就有必要打好技术基础。本书所涉及的全部是核心的JavaScript编程技术,如果能全部理解并付诸实践,一定可以夯实JavaScript编程基础。勘误和支持

除封面署名外,对本书编写提供帮助的还有:马本连、吴建华、江淑军、李斌、李经键、郑伟、田蜜、陆颖、王慧明、张炜、陈锐、王幼平、杨龙贵、苏震巍、崔鹏飞等。由于作者的水平有限,加之编写时间仓促,书中难免会出现一些错误或不准确的地方,恳请读者批评指正。书中的全部源文件可以从华章网站(www.hzbook.com)下载。如果您有任何意见建议,欢迎发送邮件至邮箱js_code@126.com,期待得到您的真挚反馈。致谢

感谢机械工业出版社华章公司的杨福川编辑在这一年多的时间中始终支持我的写作,他的鼓励和帮助让我顺利完成了本书的编写工作。

最后感谢我的父母,感谢他们的养育之恩,感谢他们时时刻刻给我信心和力量!

谨以此书献给我最亲爱的家人,以及众多热爱JavaScript的朋友们!成林第1章JavaScript语言基础

对于任何语言来说,如何选用代码的写法和算法最终会影响到执行效率。与其他语言不同,由于JavaScript可用资源有限,所以规范和优化更为重要。代码结构是执行速度的决定因素之一:代码量少,运行速度不一定快;代码量多,运行速度也不一定慢。性能损失与代码的组织方式及具体问题的解决办法直接相关。

程序通常由很多部分组成,具体表现为函数、语句和表达式,它们必须准确无误地按照顺序排列。优秀的程序应该拥有前瞻性的结构,可以预见到未来所需要的修改。优秀的程序也有一种清晰的表达方式。如果一个程序被表达得很好,那么它更容易被理解,进而能够成功地被修改或修复。JavaScript代码经常被直接发布,因此它应该自始至终具备发布质量。整洁是会带来价值的,通过在一个清晰且始终如一的风格下编写的程序会更易于阅读。

JavaScript的弱类型和过度宽容特征,没有为程序质量带来安全编译时的保证,为了弥补这一点,我们应该按严格的规范进行编码。JavaScript包含大量脆弱的或有问题的特性,这些会妨碍编写优秀的程序。我们应该避免JavaScript中那些糟糕的特性,还应该避免那些通常很有用但偶尔有害的特性。这样的特性让人既爱又恨,避免它们就能避免日后开发中潜在的错误。建议1:警惕Unicode乱码

ECMA标准规定JavaScript语言基于Unicode标准进行开发,JavaScript内核完全采用UCS字符集进行编写,因此在JavaScript代码中每个字符都使用两个字节来表示,这意味着可以使用中文来命名变量或函数名,例如:

var人名="张三";

function睡觉(谁){

alert(谁+":快睡觉!都半夜三更了。");

}

睡觉(人名);

虽然ECMAScript v3标准允许Unicode字符出现在JavaScript程序的任何地方,但是在v1和v2中,ECMA标准只允许Unicode字符出现在注释或引号包含的字符串直接量中,在其他地方必须使用ASCII字符集,在ECMAScript标准化之前,JavaScript通常是不支持Unicode编码的。考虑到JavaScript版本的兼容性及开发习惯,不建议使用汉字来命名变量或函数名。

由于JavaScript脚本一般都“寄宿”在网页中,并最终由浏览器来解析和执行,因此在考虑到JavaScript语言编码的同时,还要顾及嵌入页面的字符编码,以及浏览器支持的编码。不过现在的浏览器一般都支持不同类型的字符集,只需要考虑页面字符编码与JavaScript语言编码一致即可,否则就会出现乱码现象。

当初设计JavaScript时,预计最多会有65 536个字符,从那以后慢慢增长到了一百万个字符。JavaScript字符是16位的,这足够覆盖原有的65 536个字符,剩下的百万字符中的每一个都可以用一对字符来表示。

Unicode把一对字符视为一个单一的字符,而JavaScript认为一对字符是两个不同的字符,这将会带来很多问题,考虑到代码的安全性,我们应该尽量使用基本字符进行编码。建议2:正确辨析JavaScript句法中的词、句和段

JavaScript语法包含了合法的JavaScript代码的所有规则和特征,它主要分为词法和句法。词法包括字符编码、名词规则、特殊词规则等。词法侧重语言的底层实现(如语言编码问题等),以及基本规则的定义(如标识符、关键字、注释等)。它们都不是最小的语义单位,却是构成语义单位的组成要素。例如,规范字符编码集合、命名规则、标识符、关键字、注释规则、特殊字符用法等。

句法定义了语言的逻辑和结构,包括词、句和段的语法特性,其中段体现逻辑的结构,句表达可执行的命令,词演绎逻辑的精髓。

段落使用完整的结构封装独立的逻辑。在JavaScript程序中,常用大括号来划分结构,大括号拥有封装代码和逻辑的功能,由此形成一个独立的段落结构。例如,下面这些结构都可以形成独立的段落。

{

//对象

}

function(){

//函数

}

if(){

//条件

}

for(){

//循环

}

while(){

//循环

}

switch(){

//多条件

}

with(){

//作用域

}

try{

//异常处理

}

段落结构包含的内容可以是一条或多条语句。可以在段落起始标记({)前面添加修饰词,如域谓词(with、catch)、逻辑谓词(if、while、for、switch等)、函数谓词(function fn(arg))等。

语句是由多个词构成的完整逻辑。在JavaScript中,常用分号(;)来划分语句,有时也可以省略分号,默认使用换行符表示完整的语句。

一条语句可以包含一个或多个词。例如,在下面两条语句中,第一条语句只有一个词,这是一个指令词,该指令只能位于循环体或switch结构体内。第二条语句包含3个词,alert表示函数名(即变量),小括号表示运算符,而“"提示信息"”表示字符串直接量。

break;

alert("提示信息");

一条语句也可以包含一个或多个段落。例如,在下面这条语句中,直接把一个函数当做一个变量进行调用。

(function(i){

alert(i);

})("提示信息");

还可以把函数包含在一个闭包中形成多个结构嵌套,这个嵌套结构体就构成了一个复杂的语句,例如:

(function(i){

return function(){

alert(i);

};

})("提示信息")();

语句一般至少包含一个词或段落,但是语句也可以什么都不包含,仅由一个分号进行标识,这样的句子称为空语句。空语句常用做占位符。例如,在下面这个循环体内就包含了一个空语句。

for(var i;i<100;i++){

;

}

词语是JavaScript句法结构中的最小语义单位,包括指令(或称语句)、变量、直接量(或常量)、运算符等。在JavaScript中,词语之间必须使用分隔符进行分隔,否则JavaScript就会错误解析。下面的代码块是一个简单的求两个数平均值的方法。

var a=34;

var b=56;

function aver(c,d){

return(c+d)/2;

}

alert(aver(a,b));

其中var、function、return是指令,这些指令也是JavaScript默认的关键字;a、b、c、d、aver、alert为变量;34、56是数值直接量;=、(、)、{、}、/、+、,是运算符。建议3:减少全局变量污染

定义全局变量有3种方式:

❑在任何函数外面直接执行var语句。

var f='value';

❑直接添加一个属性到全局对象上。全局对象是所有全局变量的容器。在Web浏览器中,全局对象名为window。

window.f='value';

❑直接使用未经声明的变量,以这种方式定义的全局变量被称为隐式的全局变量。

f='value';

为方便初学者在使用前无须声明变量而有意设计了隐式的全局变量,然而不幸的是忘记声明变量成了一个非常普遍的现象。JavaScript的策略是让那些被忘记预先声明的变量成为全局变量,这导致在程序中查找Bug变得非常困难。

JavaScript语言最为糟糕的就是它对全局变量的依赖性。全局变量就是在所有作用域中都可见的变量。全局变量在很小的程序中可能会带来方便,但随着程序越来越大,它很快变得难以处理。因为一个全局变量可以被程序的任何部分在任意时间改变,使得程序的行为被极大地复杂化。在程序中使用全局变量降低了程序的可靠性。

全局变量使在同一个程序中运行独立的子程序变得更难。如果某些全局变量的名称与子程序中的变量名称相同,那么它们将会相互冲突并可能导致程序无法运行,而且通常还使程序难以调试。

实际上,这些全局变量削弱了程序的灵活性,应该避免使用全局变量。努力减少使用全局变量的方法:在应用程序中创建唯一一个全局变量,并定义该变量为当前应用的容器。

var My={};

My.name={

"first-name":"first",

"last-name":"last"

};

My.work={

number:123,

one:{

name:"one",

time:"2012-9-14 12:55",

city:"beijing"

},

two:{

name:"two",

time:"2012-9-12 12:42",

city:"shanghai"

}

};

只要把多个全局变量都追加在一个名称空间下,将显著降低与其他应用程序产生冲突的概率,应用程序也会变得更容易阅读,因为My.work指向的是顶层结构。当然也可以使用闭包体将信息隐藏,它是另一种有效减少“全局污染”的方法。

在编程语言中,作用域控制着变量与参数的可见性及生命周期。这为程序开发提供了一个重要的帮助,因为它减少了名称冲突,并且提供了自动内存管理。

var foo=function(){

var a=1,b=2;

var bar=function(){

var b=3,c=4;//a=1,b=3,c=4

a+=b+c;//a=8,b=3,c=4

};//a=1,b=2,c=undefined

bar();//a=21,b=2,c=undefined

};

大多数采用C语言语法的语言都拥有块级作用域。对于一个代码块,即包括在一对大括号中的语句,其中定义的所有变量在代码块的外部是不可见的。定义在代码块中的变量在代码块执行结束后会被释放掉。但是,对于JavaScript语言来说,虽然该语言支持代码块的语法形式,但是它并不支持块级作用域。

JavaScript支持函数作用域,定义在函数中的参数和变量在函数外部是不可见的,并且在一个函数中的任何位置定义的变量在该函数中的任何地方都可见。

其他主流编程语言都推荐尽可能迟地声明变量,但是在JavaScript中就不能够这样,因为它缺少块级作用域,最好的做法是在函数体的顶部声明函数中可能用到的所有变量。建议4:注意JavaScript数据类型的特殊性

1.防止浮点数溢出

二进制的浮点数不能正确地处理十进制的小数,因此0.1+0.2不等于0.3。

num=0.1+0.2;//0.30000000000000004

这是JavaScript中最经常报告的Bug,并且这是遵循二进制浮点数算术标准(IEEE 754)而导致的结果。这个标准适合很多应用,但它违背了数字基本常识。幸运的是,浮点数中的整数运算是精确的,所以小数表现出来的问题可以通过指定精度来避免。例如,针对上面的相加可以这样进行处理:

a=(1+2)/10;//0.3

这种处理经常在货币计算中用到,在计算货币时当然期望得到精确的结果。例如,元可以通过乘以100而全部转成分,然后就可以准确地将每项相加,求和后的结果可以除以100转换回元。

2.慎用JavaScript类型自动转换

在JavaScript中能够自动转换变量的数据类型,这种转换是一种隐性行为。在自动转换数据类型时,JavaScript一般遵循:如果某个类型的值被用于需要其他类型的值的环境中,JavaScript就自动将这个值转换成所需要的类型,具体说明见表1.1。

如果把非空对象用在逻辑运算环境中,则对象被转换为true。此时的对象包括所有类型的对象,即使是值为false的包装对象也被转换为true。

如果把对象用在数值运算环境中,则对象会被自动转换为数字,如果转换失败,则返回值为NaN。

当数组被用在数值运算环境中时,数组将根据包含的元素来决定转换的值。如果数组为空数组,则被转换为数值0。如果数组仅包含一个数字元素,则被转换为该数字的数值。如果数组包含多个元素,或者仅包含一个非数字元素,则返回NaN。

当对象用于字符串环境中时,JavaScript能够调用toString()方法把对象转换为字符串再进行相关计算。当对象与数值进行加号运算时,则会尝试将对象转换为数值,然后参与求和运算。如果不能够将对象转换为有效数值,则执行字符串连接操作。

3.正确检测数据类型

使用typeof运算符返回一个用于识别其运算数类型的字符串。对于任何变量来说,使用typeof运算符总是以字符串的形式返回以下6种类型之一:

❑"number"

❑"string"

❑"boolean"

❑"object"

❑"function"

❑"undefined"

不幸的是,在使用typeof检测null值时,返回的是“object”,而不是“null”。更好的检测null的方式其实很简单。下面定义一个检测值类型的一般方法:

function type(o){

return(o===null)?"null":(typeof o);

}

这样就可以避开因为null值影响基本数据的类型检测。注意:typeof不能够检测复杂的数据类型,以及各种特殊用途的对象,如正则表达式对象、日期对象、数学对象等。

对于对象或数组,可以使用constructor属性,该属性值引用的是原来构造该对象的函数。如果结合typeof运算符和constructor属性,基本能够完成数据类型的检测。表1.2所示列举了不同类型数据的检测结果。

使用constructor属性可以判断绝大部分数据的类型。但是,对于undefined和null特殊值,就不能使用constructor属性,因为使用JavaScript解释器会抛出异常。此时可以先把值转换为布尔值,如果为true,则说明不是undefined和null值,然后再调用constructor属性,例如:

var value=undefined;

alert(typeof value);//"undefined"

alert(value&&value.constructor);//undefined

var value=null;

alert(typeof value);//"object"

alert(value&&value.constructor);//null

对于数值直接量,也不能使用constructor属性,需要加上一个小括号,这是因为小括号运算符能够把数值转换为对象,例如:

alert((10).constructor);

使用toString()方法检测对象类型是最安全、最准确的。调用toString()方法把对象转换为字符串,然后通过检测字符串中是否包含数组所特有的标志字符可以确定对象的类型。toString()方法返回的字符串形式如下:

[object class]

其中,object表示对象的通用类型,class表示对象的内部类型,内部类型的名称与该对象的构造函数名对应。例如,Array对象的class为“Array”,Function对象的class为“Function”,Date对象的class为“Date”,内部Math对象的class为“Math”,所有Error对象(包括各种Error子类的实例)的class为“Error”。

客户端JavaScript的对象和由JavaScript实现定义的其他所有对象都具有预定义的特定class值,如“Window”、“Document”和“Form”等。用户自定义对象的class值为“Object”。

class值提供的信息与对象的constructor属性值相似,但是class值是以字符串的形式提供这些信息的,而不是以构造函数的形式提供这些信息的,所以在特定的环境中是非常有用的。如果使用typeof运算符来检测,则所有对象的class值都为“Object”或“Function”,所以此时的class值不能够提供有效信息。

但是,要获取对象的class值的唯一方法是必须调用Object对象定义的默认toString()方法,因为不同对象都会预定义自己的toString()方法,所以不能直接调用对象的toString()方法。例如,下面对象的toString()方法返回的就是当前UTC时间字符串,而不是字符串“[object Date]”。

var d=new Date();

alert(d.toString());//当前UTC时间字符串

要调用Object对象定义的默认toString()方法,可以先调用Object.prototype.toString对象的默认toString()函数,再调用该函数的apply()方法在想要检测的对象上执行。结合上面的对象d,具体实现代码如下:

var d=new Date();

var m=Object.prototype.toString;

alert(m.apply(d));//"[object Date]"

下面是一个比较完整的数据类型安全检测方法。

//安全检测JavaScript基本数据类型和内置对象

//参数:o表示检测的值

/*返回值:返回字符串"undefined"、"number"、"boolean"、"string"、"function"、"regexp"、"array"、"date"、"error"、"object"或"null"*/

function typeOf(o){

var_toString=Object.prototype.toString;

//获取对象的toString()方法引用

//列举基本数据类型和内置对象类型,可以进一步补充该数组的检测数据类型范围

var_type={

"undefined":"undefined",

"number":"number",

"boolean":"boolean",

"string":"string",

"[object Function]":"function",

"[object RegExp]":"regexp",

"[object Array]":"array",

"[object Date]":"date",

"[object Error]":"error"

}

return_type[typeof o]||_type[_toString.call(o)]||(o?"object":"null");

}

应用示例:

var a=Math.abs;

alert(typeOf(a));//"function"

上述方法适用于JavaScript基本数据类型和内置对象,而对于自定义对象是无效的。这是因为自定义对象被转换为字符串后,返回的值是没有规律的,并且不同浏览器的返回值也是不同的。因此,要检测非内置对象,只能够使用constructor属性和instaceof运算符来实现。

4.避免误用parseInt

parseInt是一个将字符串转换为整数的函数,与parseFloat(将字符串转换为浮点数)对应,这两种函数是JavaScript提供的两种静态函数,用于把非数字的原始值转换为数字。

在开始转换时,parseInt会先查看位置0处的字符,如果该位置不是有效数字,则将返回NaN,不再深入分析。如果位置0处的字符是数字,则将查看位置1处的字符,并重复前面的测试,依此类推,直到发现非数字字符为止,此时parseInt()函数将把前面分析合法的数字字符转换为数值并返回。

parseInt("123abc");//123

parseInt("1.73");//1

parseInt(".123");//NaN

浮点数中的点号对于parseInt来说属于非法字符,因此它不会被转换并返回,这样,在使用parseInt时,就存在潜在的误用风险。例如,我们并不希望parseInt("16")与parseInt("16 tons")产生相同的结果。如果该函数能够提醒我们出现额外文本就好了,但它不会那么做。

对于以0为开头的数字字符串,parseInt()函数会把它作为八进制数字处理,先把它转换为数值,然后再转换为十进制的数字返回。对于以0x开头的数字字符串,parseInt()函数则会把它作为十六进制数字处理,先把它转换为数值,然后再转换为十进制的数字返回。例如:

var d="010";//八进制

var e="0x10";//十六进制

parseInt(d);//8

parseInt(e);//16

如果字符串的第一个字符是0,那么该字符串将基于八进制而不是十进制来求值。在八进制中,8和9不是数字,所以parseInt("08")和parseInt("09")的结果为0,这个错误导致了在程序解析日期和时间时经常会出现问题。幸运的是,parseInt可以接受一个基数作为参数,这样parseInt("08",10)结果为8,parseInt("09",10)结果为9。因此,建议读者在使用parseInt时,一定要提供这个基数参数。

通过在parseInt中提供基数参数,可以把二进制、八进制、十六进制等不同进制的数字字符串转换为整数。例如,下面把十六进制数字字符串"123abc"转换为十进制整数。

parseInt("123abc",16);//1194684

再如,把二进制、八进制和十进制数字字符串转换为整数:

parseInt("10",2);//把二进制数字10转换为十进制整数为2

parseInt("10",8);//把八进制数字10转换为十进制整数为8

parseInt("10",10);//把十进制数字10转换为十进制整数为10建议5:防止JavaScript自动插入分号

JavaScript语言有一个机制:在解析时,能够在一句话后面自动插入一个分号,用来修改语句末尾遗漏的分号分隔符。然而,由于这个自动插入的分号与JavaScript语言的另一个机制发生了冲突,即所有空格符都被忽略,因此程序可以利用空格格式化代码。

这两种机制的冲突,很容易掩盖更为严重的解析错误。有时会不合时宜地插入分号。例如,在return语句中自动插入分号将会导致这样的后果:如果return语句要返回一个值,这个值的表达式的开始部分必须和return在同一行上,例如:

var f=function(){

return

{

status:true

};

}

看起来这里要返回一个包含status成员元素的对象。不幸的是,JavaScript自动插入分号让它返回了undefined,从而导致下面真正要返回的对象被忽略。

当自动插入分号导致程序被误解时,并不会有任何警告提醒。如果把{放在上一行的尾部而不是下一行的头部,就可以避免该问题,例如:

var f=function(){

return{

status:true

};

}

为了避免省略分号引起的错误,建议养成好的习惯,不管一行内语句是否完整,只要是完整的语句都必须增加分号以表示句子结束。

为了方便阅读,当长句子需要分行显示时,在分行时应确保一行内不能形成完整的逻辑语义。例如,下面代码是一条连续赋值的语句,通过分行显示可以更清楚地查看它们的关系。这种分行显示,由于一行内不能形成独立的逻辑语义,因此JavaScript不会把每一行视为独立的句子,从而不会产生歧义。

var a=

b=

c=4;

以上语句在一行内显示如下:

var a=b=c=4;

对于下面这条语句,如果不能正确分行显示,就很容易产生歧义。该句子的含义:定义一个变量i,然后为其赋值,如果变量a为true,则赋值为1,否则就判断变量b,如果b为true,则赋值为2,否则就判断变量c,如果c为true,则赋值为3,否则赋值为4。

var i=a?1:b?2:c?3:4;

下面的分行显示就是错误的,因为表达式a?1:b能够形成独立的逻辑语义,所以JavaScript会自动在其后添加分号来表示一个独立的句子。

var i=a?1:b

?2:c

?3:4;

安全的方法应该采用如下的分行显示,这样每一行都不能形成独立的语义。

var i=a?1

:b?2

:c?3

:4;

总之,在编写代码时,应养成使用分号结束句子的良好习惯,凡是完整的句子就应该使用分号进行分隔。分行显示的句子应该确保单行不容易形成独立的合法的逻辑语义。建议6:正确处理JavaScript特殊值

1.正确使用NaN和Infinity

NaN是IEEE 754中定义的一个特殊的数量值。它不表示一个数字,尽管下面的表达式返回的是true。

typeof NaN==='number'//true

该值可能会在试图将非数字形式的字符串转换为数字时产生,例如:

+'0'//0

+'oops'//NaN

如果NaN是数学运算中的一个运算数,那么它与其他运算数的运算结果就会是NaN。如果有一个表达式产生出NaN的结果,那么至少其中一个运算数是NaN,或者在某个地方产生了NaN。

可以对NaN进行检测,但是typeof不能辨别数字和NaN的区别,并且NaN不等同于它自己,所以,下面的代码结果令人惊讶。

NaN===NaN//false

NaN!==NaN//true

为了方便检测NaN值,JavaScript提供isNaN静态函数,以辨别数字与NaN区别。

isNaN(NaN)//true

isNaN(0)//false

isNaN('oops')//true

isNaN('0')//false

判断一个值是否可用做数字的最佳方法是使用isFinite函数,因为它会筛除掉NaN和Infinity。Infinity表示无穷大。当数值超过浮点数所能够表示的范围时,就要用Infinity表示。反之,负无穷大为-Infinity。

使用isFinite函数能够检测NaN、正负无穷大。如果是有限数值,或者可以转换为有限数值,那么将返回true。如果只是NaN、正负无穷大的数值,则返回false。

不幸的是,isFinite会试图把它的运算数转换为一个数字。因此,如果值不是一个数字,使用isFinite函数就不是一个有效的检测方法,这时不妨自定义isNumber函数。

var isNumber=function isNumber(value){

return typeof value==='number'&&isFinite(value);

}

2.正确使用null和undefined

JavaScript有5种基本类型:String、Number、Boolean、Null和Undefined。前3种都比较好理解,后面两种就稍微复杂一点。Null类型只有一个值,就是null;Undefined类型也只有一个值,即undefined。null和undefined都可以作为字面量在JavaScript代码中直接使用。

null与对象引用有关系,表示为空或不存在的对象引用。当声明一个变量却没有向它赋值的时候,它的值就是undefined。undefined的值会在如下情况中出现:

❑从一个对象中获取某个属性,如果该对象及其prototype链中的对象都没有该属性,该属性的值为undefined。

❑一个函数如果没有显式通过return语句将返回值返回给其调用者,其返回值就是undefined,但在使用new调用函数时例外。

❑JavaScript的函数可以声明任意多个形参,当该函数实际被调用时,传入的参数的个数如果小于声明的形式参数的个数,那么多余的形式参数的值为undefined。

如果对值为null的变量使用typeof检测,得到的结果是“object”,而typeof undefined的值为“undefined”。null==undefined,null!==undefined。

与null不同,undefined不是JavaScript的保留字,在ECMAScript v3标准中才定义undefined为全局变量,初始值为undefined。因此,在使用undefined值时就存在一个兼容问题(早期浏览器可能不支持undefined)。除了直接赋值和使用typeof运算符外,其他任何运算符对undefined的操作都会引发异常。不过,可以声明undefined变量,然后查看它的值,如果它的值为undefined,则说明浏览器支持undefined值。例如:

var undefined;

alert(undefined);

如果浏览器不支持undefined关键字,可以自定义undefined变量,并将其赋值为undefined。例如:

var undefined=void null;

声明变量为undefined,将其初始化为表达式void null的值,由于运算符void在执行其后的表达式时会忽略表达式的结果值,而总是返回值undefined,因此利用这种方法可以定义一个变量为undefined,并将其赋值为undefined。既然是将变量undefined赋值为undefined,还可以使用如下方式:

var undefined=void 1;

或者使用没有返回值的函数:

var undefined=function(){}();

alert(undefined);//"undefined"

可以使用typeof运算符来检测某个变量的值是否为undefined:

var a;

if(typeof a=="undefined"){

}

3.使用假值

JavaScript的类型系统是非常混乱的,类型特性不明显,而且交叉错乱。JavaScript语法系统拥有一大组假值,如以下代码所示。这些值的布尔值都是false。

0//Number

NaN//Number

''//String

false//Boolean

null//Object

undefined//Undefined

这些值全部都等同于false,但它们是不可互换的。例如,下面用法是错误的。

value=myObject[name];

if(value==null){

}

这是在用一种错误的方式去确定一个对象是否缺少一个成员属性。undefined是缺失的成员属性值,而上面代码片段用null来测试,使用了会强制类型转换的==运算符,而不是更可靠的===运算符。正确的用法如下:

value=myObject[name];

if(!value){

}

undefined和NaN并不是常见,它们是全局变量,还可以改变它们的值,虽然在程序设计中不应该采取这种做法,但可以改变它们的值。建议7:小心保留字的误用

JavaScript语言中定义了很多备用或已经使用的保留字,按首字母顺序列出的保留字见表1.3。

这些单词中的大多数并没有在语言中使用,但是根据JavaScript语法规则,这些单词是不能用来命名变量或参数的。当保留字作为对象字面量的键值时,必须用引号括起来。保留字不能用在点语法中,所以有时必须使用中括号表示法。例如,下面的用法是合法的。

var method;

object={box:value};

object={'case':value};

object.box=value;

object['case']=value;

但是,下面的用法就是非法的。

var class;

object={case:value};

object.case=value;

各个浏览器对保留字的使用限制不同。例如,下面代码在Firefox中是合法的,而在其他浏览器中就是不合法的。

object={case:value};

此外,不同的保留字也各不相同。例如,下面代码在Firefox和Opera 9.5中是合法的,但在IE和Safari中依然是不合法的。

object={class:value};

对于int、long、float等保留字,它们在各浏览器中都可以用做变量名及对象字面量的键值。尽管如此,在这些场合依然不建议使用任何保留字。建议8:谨慎使用运算符

1.用===,而不用==

JavaScript有两组相等运算符:===和!==、==和!=。===和!==这一组运算符会按照期望的方式工作。如果两个运算数类型一致且拥有相同的值,那么===返回true,而!==返回false。==和!=只有在两个运算数类型一致时才会做出正确的判断,如果两个运算数是不同的类型,会试图强制转换运算数的类型。转换的规则复杂且难以记忆,具体规则如下:

''=='0'//false

0==''//true

0=='0'//true

false=='false'//false

false=='0'//true

false==undefined//false

false==null//false

null==undefined//true

上面表达式如果全部使用===运算符,则都会返回true。

==和!=运算符缺乏传递性,需要引起警惕。所谓传递性就是:如果a==b为true,b==c为true,则a==c也为true。因此,在JavaScript开发中,建议永远不要使用==和!=,而选用===和!==运算符。

下面分别介绍一下===和==运算符的算法。(1)===运算符的算法

在使用===来判断两个值是否相等时,如判断x===y,会先比较两个值的类型是否相同,如果不相同,直接返回false。如果两个值的类型相同,则接着根据x的类型展开不同的判断逻辑:

❑如果x的类型是Undefined或Null,则返回true。

❑如果x的类型是Number,只要x或y中有一个值为NaN,就返回false;如果x和y的数字值相等,就返回true;如果x或y中有一个是+0,另外一个是-0,则返回true。

❑如果x的类型是String,当x和y的字符序列完全相同时返回true,否则返回false。

❑如果x的类型是Boolean,当x和y同为true或false时返回true,否则返回false。

❑当x和y引用相同的对象时返回true,否则返回false。(2)==运算符的算法

在使用==来判断两个值是否相等时,如判断x==y,如果x和y的类型一样,判断逻辑与===一样;如果x和y的类型不一样,==不是简单地返回false,而是会进行一定的类型转换。

❑如果x和y中有一个是null,另外一个是undefined,返回true,如null==undefined。

❑如果x和y中有一个类型是String,另外一个类型是Number,会将String类型的值转换成Number来比较,如3=="3"。

❑如果x和y中有一个类型是Boolean,会将Boolean类型的值转换成Number来比较,如true==1、true=="1"。

❑如果x和y中有一个类型是String或Number,另外一个类型是Object,会将Object类型的值转换成基本类型来比较,如[3,4]=="3,4"。

2.谨慎使用++和--

递增(++)和递减(--)运算符使程序员可以用非常简洁的风格去编码,如在C语言中,它们使得通过一行代码实现字符串的复制成为可能,例如:

for(p=src,q=dest;!*p;p++,q++)*q=*p;

事实上,这两个运算符容易形成一种不谨慎的编程风格。大多数的缓冲区溢出错误所造成的安全漏洞都是由于这种编码导致的。

当使用++和--时,代码往往变得过于紧密、复杂和隐晦。因此,在JavaScript程序设计中不建议使用它们,从而使代码风格变得更为整洁。

++和--运算符只能够作用于变量、数组元素或对象属性。下面的用法是错误的。

4++;

正确的用法如下:

var n=4;

n++;

++和--运算符的位置不同所得运算结果也不同。例如,下面的递增运算符是先执行赋值运算,然后再执行递加运算。

var n=4;

n++;//4

而下面的递增运算符是先执行递加运算,再进行赋值运算。

var n=4;

++n;

3.小心逗号运算符

逗号在JavaScript语言中表示连续运算,并返回最后运算的结果。例如,在下面这个示例中,JavaScript先运算第一个数值直接量,再运算第二个数值直接量,然后运算第三个数值直接量,最后运算第四个数值直接量,并返回最后一个运算值4。

var a=(1,2,3,4);

alert(a);//4

再如:

a=1,b=2,c=3;

等价于:

a=1;

b=2;

c=3;

作为运算符,逗号一般用在特殊环境中,即在只允许出现一个句子的地方,把几个不同的表达式句子合并成一个长句。在JavaScript实际开发中,逗号运算符常与for循环语句联合使用。例如,在下面这个简单的for循环结构中,通过连续的运算符在参数表达式中运算多个表达式,以实现传递或运算多个变量或表达式。

for(var a=10,b=0;a>b;a++,b+=2){

document.write("a="+a+"b="+b+"<br>");

}

逗号运算符比较“怪异”,稍不留心就会出错。例如,在下面这个简单的示例中,变量a的返回值为1,而不是连续运算后的返回值4。

a=1,2,3,4;

alert(a);//1

第一个数值1先赋值给变量a,然后a再参与连续运算,整个句子的返回值为4,而变量a的返回值为1,代码演示如下:

alert((a=1,2,3,4));//4

alert(a=(1,2,3,4));//4

要确保变量a的值为连续运算,可以使用小括号逻辑分隔符强迫4个数值先进行连续运算,然后再赋值。

a=(1,2,3,4);

alert(a);//4

当使用var关键字来定义变量时,会发现a最终没有返回值。

var a=1,2,3,4;

alert(a);//null

要确保var声明的变量正确返回连续运算的值,需要使用小括号先强迫数值进行计算,最后再把连续运算的值赋值给变量a。

var a=(1,2,3,4);

alert(a);//4

4.警惕运算符的副作用

JavaScript运算符一般不会对运算数本身产生影响,如算术运算符、比较运算符、条件运算符、取逆、“位与”等。例如,a=b+c,其中的运算数b和c不会因为加法运算而导致自身的值发生变化。

但在JavaScript中还有一些运算符能够改变运算数自身的值,如赋值、递增、递减运算等。由于这类运算符自身的值会发生变化,在使用时会具有一定的副作用,特别是在复杂表达式中,这种副作用更加明显,因此在使用时应该时刻保持警惕。例如,在下面代码中,变量a经过赋值运算和递加运算后,其值发生了两次变化。

var a;

a=0;

a++;

alert(a);//1

再如:

var a;

a=1;

a=(a++)+(++a)-(a++)-(++a);

alert(a);//-4

如果直观地去判断,会错误地认为返回值为0,实际上变量a在参与运算的过程中,变量a的值是不断发生变化的。这种变化很容易被误解。为了方便理解,进一步拆解(a++)+(++a)-(a++)-(++a)表达式:

var a;

a=1;

b=a++;

c=++a;

d=a++;

e=++a;

alert(b+c-d-e);//-4

如果表达式中还包含其他能够引起自身值发生变化的运算,那么整个表达式的逻辑就无法用人的直观思维来描述了。因此,从代码可读性角度来考量:在一个表达式中不能够对相同操作数执行两次或多次引起自身值变化的运算,除非表达式必须这样执行,否则应该避免产生歧义。这种歧义在不同编译器中会产生完全不同的解析结果。例如,下面的代码虽然看起来让人头疼,但由于每个运算数仅执行了一次引起自身值变化的运算,所以不会产生歧义。

a=(b++)+(++c)-(d++)-(++e);建议9:不要信任hasOwnProperty

hasOwnProperty方法常被用做一个过滤器,用来消除for in语句在枚举对象属性时的弊端。考虑到hasOwnProperty是一个方法,而不是一个运算符,因此,在任何对象中,它可能会被一个不同的函数甚至一个非函数的值所替换。

例如,在下面代码中,obj对象的hasOwnProperty成员被清空了,此时如果再利用这个方法来过滤出obj对象的本地属性就会失败。

var obj={},name;

obj.hasOwnProperty=null;

for(name in obj){

if(obj.hasOwnProperty(name)){

document.writeln(name+':'+obj[name]);

}

}建议10:谨记对象非空特性

JavaScript从来没有真正的空对象,因为每个对象都可以从原型链中取得成员,这种机制会带来很多麻烦。例如,在编写统计一段文本中每个单词的出现次数的代码时可以这样设计:先使用toLowerCase方法统一转换文本为小写格式,接着使用split方法以一个正则表达式为参数生成一个单词数组,然后可以遍历该数组中每个单词,并统计每个单词出现的次数。

var i,word;

var text="A number of W3C staff will be on hand to discuss HTML5,CSS,and other technologies of the Open Web Platform.";

var words=text.toLowerCase().split(/[\s,.]+/);

var count={};

for(i=0;i<words.length;i+=1){

word=words[i];

if(count[word]){

count[word]+=1;

}else{

count[word]=1;

}

}

在执行结果中,count["on"]的值为1,count["of"]的值是3,而count.constructor却包含着一个看上去令人不可思议的字符串。在主流浏览器上,count.constructor将会返回字符串:function Object(){[native code]}。其原因是count对象继承自Object.prototype,而Object.prototype包含一个名为constructor的成员对象。count.constructor的值是一个Object。+=运算符,就像+运算符一样,如果它的运算数不是数字时会执行字符串连接的操作而不是加法运算。因为count对象是一个函数,所以+=运算符将其转换成一个莫名其妙的字符串,然后再把一个数字1加在它的后面。

采用与处理for in中问题相同的方法来避免类似的问题:用hasOwnProperty方法检测成员关系,或者查找特定的类型。在当前情形下,可以编写如下过滤条件:

if(typeof count[word]==='number'){

}

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载