JavaScript高效图形编程

978-7-115-27881-4
作者: [美]Raffaele Cecco
译者: 徐鹏飞
编辑: 龚昕岳傅道坤

图书目录:

详情

本书讲解了如何使用JavaScript、jQuery、DHTML和HTML5的Canvas元素分别为计算机和移动设备创建富Web应用程序。通过本书,读者可以掌握设计街机游戏、DHTML特效等的方法,并掌握如何利用JavaScript创造新的用户体验。

图书摘要

版权信息

书名:JavaScript高效图形编程

ISBN:978-7-115-27881-4

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。


著    [英] Raffaele Cecco

译    徐鹏飞

责任编辑 傅道坤

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


Copyright © 2011 by O’Reilly Media, Inc.

Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2012. Authorized translation of the English edition, 2011 O’Reilly Media, Inc., the owner of all rights to publish and sell the same.

All rights reserved including the rights of reproduction in whole or in part in any form.

本书中文简体字版由O’Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。


本书是一本具有很强实操性的JavaScript 图书,全书共分10 章,涵盖的主要内容有:JavaScript 的面向对象机制、JavaScript 性能优化、jQuery 和ExtJS库、高级UI 设计、Web 游戏开发、面向移动设备的开发、图形编程知识等。

本书适合有一定 Web 开发经验和JavaScript 基础的开发人员学习。


O’Reilly Media通过图书、杂志、在线服务、调查研究和会议等方式传播创新知识。自1978年开始,O’Reilly一直都是前沿发展的见证者和推动者。超级极客们正在开创着未来,而我们关注真正重要的技术趋势——通过放大那些“细微的信号”来刺激社会对新科技的应用。作为技术社区中活跃的参与者,O’Reilly的发展充满了对创新的倡导、创造和发扬光大。

O’Reilly为软件开发人员带来革命性的“动物书”;创建第一个商业网站(GNN);组织了影响深远的开放源代码峰会,以至于开源软件运动以此命名;创立了《Make》杂志,从而成为DIY革命的主要先锋;公司一如既往地通过多种形式缔结信息与人的纽带。O’Reilly的会议和峰会集聚了众多超级极客和高瞻远瞩的商业领袖,共同描绘出开创新产业的革命性思想。作为技术人士获取信息的选择,O’Reilly现在还将先锋专家的知识传递给普通的计算机用户。无论是通过书籍出版、在线服务或者面授课程,每一项O’Reilly的产品都反映了公司不可动摇的理念——信息是激发创新的力量。

“O’Reilly Radar博客有口皆碑。”

      ——Wired

“O’Reilly凭借一系列(真希望当初我也想到了)非凡想法建立了数百万美元的业务。”

      ——Business 2.0

“O’Reilly Conference是聚集关键思想领袖的绝对典范。”

      ——CRN

“一本O’Reilly的书就代表一个有用、有前途、需要学习的主题。”

      ——Irish Times

“Tim是位特立独行的商人,他不光放眼于最长远、最广阔的视野并且切实地按照Yogi Berra的建议去做了:‘如果你在路上遇到岔路口,走小路(岔路)。’回顾过去Tim似乎每一次都选择了小路,而且有几次都是一闪即逝的机会,尽管大路也不错。”

      ——Linux Journal


Raffaele Cecco 是欧洲视频游戏产业的资深程序员。他曾在伦敦King of the Jungle 软件工作室任技术总监,其客户包括美国孩之宝玩具(Hasbro)公司和英国维珍(Virgin)集团。他使用过各种Web 开发技术,并开发过零售电子商务系统。


本书封面的动物是一只蛮羊,或称鬃羊、巴贝里绵羊。

蛮羊是一类相对较大的羊科动物。它源于北非,现在还分布在西班牙东南部、美国西南部以及墨西哥部分区域。这些居住在沙漠之中的食草动物也被称为鬣羊。

蛮羊栖息在炎热荒凉的岩石和沙土地带,身体所需水分多来自于吃的各种植物。它们硕大的弯弯的角储有丰富的血液供应,使它可以在炎热干燥的沙漠中降温。除了特殊的角外,蛮羊从它的喉部到前胸再到前肢都长着柔软的长毛,其毛色为黄褐色。

蛮羊和其他沙漠中的动物一样,在白天寻找遮阳处,在较凉爽的黎明和傍晚最为活跃。它们擅长攀登和跳跃,可以在极端陡峭的斜坡上攀登跳跃,这使得它们极难被捕猎。由于它们的生活区域内少有高大植物藏身,因此它们主要依靠其表皮颜色骗过捕食者。在北非,它们曾经的捕食者有狞猫、狮子、北非豹,而如今人类才是它们的主要威胁。

尽管不易被捕猎,但在非洲,人类的捕猎还是让蛮羊的数量急剧减少。20世纪50年代,蛮羊被引入美国西南部,曾使其数量有短暂回升。蛮羊的数量目前在5000到10000之间,而且由于捕猎和丧失栖息地,预计在未来15年内数量将下降10%。因此,蛮羊已被列入国际自然和自然资源保护联合会的濒危物种名单。

封面图片来自于Riverside Natural History。


作为资深的视频游戏开发人员,我已经习惯于和高性能的编程语言和硬件打交道,因此刚开始我并没有对JavaScript进行图形编程有太高的期望。不过后来发现,实际上JavaScript是一个优秀和高效的编程语言,而且随着更好的浏览器支持、本身的性能提升,以及新的工具库加入,JavaScript还在不断变好。JavaScript结合了HTML5 Canvas等特性,给Web开发人员提供了真正可以不用Adobe Flash等插件的方案。而WebGL等特性则为使用JavaScript和浏览器进行图形编程描绘了非常美好的未来。

这本书的目标读者需要具备一定的JavaScript知识,并且想要学习真正的Web图形编程,而不仅仅依赖于jQuery这样的库做一些动画特效。本书中涵盖了下面这些内容:

本书将带你了解不同的图形编程技术,你可以进一步探索自己感兴趣的领域。

多做尝试,你会获得很多乐趣!

打算阅读本书的读者应该对网站和Web应用开发,特别是JavaScript,具有一定知识和使用经验。

为了方便开发和表达,本书的许多示例代码都使用了jQuery。一般来说,本书中用到的所有外部库及其文件都可以从谷歌等可靠的内容分发网络获取。

另外,本书用到了一些基础的数学知识,包括向量和三角函数。

本书节奏较快,读者从第1章中就可以看到第一个图形编程的示例。

剩余章节涉及多个图形相关的技术,这些技术可以给你的Web应用增加视觉冲击力和交互性。

讨论交互式图形的书无法避开视频游戏。本书中将开发一个完整的视频游戏应用,并讨论相关的子图和滚动等技术。

本书每章的内容可以总结如下。

第1章,代码重用和优化

本章讨论JavaScript面向对象编程技术,以及图形应用中涉及的代码优化(包括jQuery优化)。本章甚至将介绍如何使用鲜为人知的JavaScript位操作符进行性能优化。

第2章,DHTML基础

本章展示了如何使用普通的DOM操作(DHTML)创建图形应用。我们将在本章开发一个适用于游戏和其他场景的子图系统,并将其以jQuery插件的形式封装。

第3章,滚动

本章首先讨论了CSS滚动技术,包括视差特效。然后本章将介绍基于JavaScript的滚动技术,以及基于块的视差卷轴特效。我们还将介绍一个强大的地图编辑器,用于创建基于块的地图。

第4章,高级UI

本章覆盖了jQuery UI和Ext JS两个UI库。我们将探讨两个库的不同工作方式和各自适合的应用类型。另外,我们还将构建一个三维旋转木马的示例。

第5章,JavaScript游戏介绍

本章演示了如何用开放Web技术,而不是Flash插件来构建有趣的Web游戏。我们将通过开发一个怀旧的视频游戏来说明我们讨论的技术。

第6章,HTML5画布

本章通过许多示例来深入介绍Canvas元素,包括如何使用Canvas和WebSockets创建一个图形化的聊天应用。其中涉及的画布主题包括:绘制、描边、填充、渐变、递归绘制、位图和动画。

第7章,游戏和模拟中的向量

本章介绍图形应用和游戏中广为使用的二维向量。代码示例包括大炮和火箭的模拟。

第8章,谷歌可视化

本章使用谷歌图表工具来对多种数据进行可视化,从基本的饼图到仪表图。本章不仅介绍了静态的可视化图形,而且覆盖了交互式的可视化图表,以及必要的数据格式化技术。

第9章,使用jQuery Mobile为移动设备开发

本章描述了jQuery Mobile,一个基于jQuery的、面向移动设备的开发框架。jQuery Mobile可以将普通的HTML页面转化为交互式和动画式的手机体验。本章中的主要例子是一个使用jQuery UI、面向移动设备的图形化滑动解谜游戏。

第10章,用PhoneGap创建Android应用

本章介绍如何使用PhoneGap将Web应用转换为手机的本地应用。本章解释了如何安装和配置PhoneGap来创建本地Android应用。在此之后,我们将把第9章的滑动解谜游戏转换为可以部署到移动设备上的本地应用。

 

 提示

这个图标用来强调一个提示、建议或一般说明。

 

 

 

 警告

这个图标用来说明一个警告或注意事项。

 

本书中提到一些有用的网站和页面,通常除了页面URL外,还会提供页面名称。

因此你可以选择直接输入URL或者通过搜索引擎搜索页面名称,找到相关页面,

可以在地址比较复杂,或页面地址被改变时使用后者。

本书包含许多代码片段、示例和一些完整充实的应用。有时手动输入代码很麻烦,因此推荐从本书的代码库中复制代码。本书的许多代码中穿插了普通文本,直接从代码库复制代码可以避免你去拼接不同位置的代码。

在本书的HTML页面示例中,大部分使用HTML5文档类型:

<!DOCTYPE html>

为方便起见,示例中的所有CSS样式都被直接嵌入HTML页面。在实际Web应用开发中,还是推荐使用外部文件保存CSS样式。本书的示例代码可以在 http://www.professorcloud. com/supercharged中找到。

本书绝大部分示例代码都可以在较新的浏览器上工作,比如:

Firefox 3.6x+

Safari 4.0x+

Opera 10.x+

Chrome 5.x+

Internet Explorer 8+

有些例子甚至可以在IE6和IE7上工作。

这些例子在Windows XP、Windows Vista和Windows 7上进行了完整测试,在iOS上进行了部分测试。理论上,这些例子也应能在上述浏览器的Linux版本上工作。

画布(Canvas)标签的使用则限于支持画布的浏览器,对IE来说,只有IE9可以(无需额外插件或库)直接支持。

有少量的例子需要特殊的环境,比如手机开发环境(PhoneGap)、服务器语言(PHP)或特殊浏览器。

如果是这种情况,书中会提到相关环境的设置和配置。

Safari在线图书是一个按需订阅的数字图书馆。它有不少于7500本技术和创意相关的书籍和视频供你参考和搜索。

通过订阅,你可以在线阅读任何页面或视频,甚至可以从手机或移动设备上在线阅读。

你可以在书籍出版前访问到它们,并给作者发送反馈。其他功能还包括:复制和赋值代码、组织收藏夹、下载和标记章节、做笔记、打印等。

O’Reilly Media已经将本书英文版上传到Safari在线图书服务。在http://my.safaribook sonline.com上免费注册,你就可以访问本书所有章节以及类似主题的书籍。

如果你想就本书发表评论或有任何疑问,敬请联系出版社:

美国:

O’Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

我们还为本书建立了一个网页,其中包含了勘误表、示例和其他额外的信息。你可以通过如下网址访问该网页:

http://www.oreilly.com/catalog/9781449393632

关于本书的技术性问题或建议,请发邮件到:

bookquestions@oreilly.com

欢迎登录我们的网站(http://www.oreilly.com),查看更多我们的书籍、课程、会议和最新动态等信息。

Facebook: http://facebook.com/oreilly

Twitter: http://twitter.com/oreillymedia

YouTube: http://www.youtube.com/oreillymedia

以作者一己之力出版一本书几乎是一件不可能的事情,在此我想特别感谢为本书做出贡献的人们。


JavaScript受到了许多不公平的评价。许多人说JavaScript在面向对象编程上存在局限,甚至有人认为JavaScript不能归为面向对象编程(OOP)语言。尽管JavaScript和C++、Java有许多相似之处,但它没有等价Class的声明,也没有显而易见的方式去实现流行的OOP技术,如继承(代码复用)和封装。JavaScript的类型非常松散,也没有编译器,因此在运行出错前只能提供很少的错误或警告。JavaScript是把双刃剑,一方面给了程序员很大的自由,另一方面也给程序员带来一些陷阱。

JavaScript中充满了对传统编程“过失”的忽略,传统的程序员可能对此颇为郁闷。比如,在JavaScript中全局函数和变量是默认行为,而忘记加分号是完全可接受的。对JavaScript的工作方式缺乏了解,往往导致程序员无比郁闷。如果首先了解一些基础事实,将有助于你编写JavaScript应用:

有些编程人员尝试用JavaScript 写C++风格的代码。尽管在某种程度上可以达到目标,但最终结果往往让人感觉不自然。

没有任何编程语言是完美的,人们有理由争论某个编程语言或OOP本身的优越性是否仅仅是皇帝的新衣。根据我的个人经验,用C++、Java或PHP编写的软件生成的bug和问题,并不比用JavaScript编写的软件生成的少。我认为JavaScript的灵活性和表达力,可以使你更快地进行项目开发。

幸运的是,大部分JavaScript的缺点都不是无药可医。解决之道并不是一味模仿其他语言,而是扬长避短:利用Javascript的灵活性,而小心避开难处理的部分。基于类的其他语言容易引起笨拙的类层次和臃肿的代码,JavaScript则提供了同样有效但更轻量级的继承模式。

JavaScript可以有许多种方法来实现继承。下面的代码使用原型继承来创建一个Pet对象,和一个继承它的Cat对象。JavaScript教程中常常能见到这种“经典”的继承模式。

上述代码可以工作,但不是特别优雅。如果你熟悉其他OOP语言比如C++或Java,new声明是好理解的。但关键字prototype显得很啰嗦,并且没有隐私;注意外部代码将petCat的legs属性改成了一个不合理的值:7。这种继承方法没有提供对外部继承的保护,在涉及多个程序员的复杂项目中这个缺点也许会影响很大。

另一个选项无需使用prototype或new,而是利用JavaScript的“函数继承(functional inheritance)”特性来吸收和增强对象实例(object instances):

这里没有可笑的prototype,而且所有东西都封装得很漂亮。最重要的是:legs变量是私有的。如果尝试从cat外部修改不存在的公共legs属性,仅导致创建一个没有用过的legs属性。真正的legs值安全地保存在pet的getDetails()方法创建的闭包(Closure)内部。闭包在函数执行结束后,保持了函数的局部变量。在这个例子中这个函数指的是pet()。

事实上,用JavaScript实现继承并没有所谓“正确”的方法。但我个人认为函数继承方式非常自然。你和你的应用也许倾向其他方法。通过搜索“JavaScript Inheritance”你可以找到许多在线资源。

 

 提示

使用原型继承的好处之一是内存效率;不管它被继承多少次,对象的原型属性和方法只被保存一次。

函数继承则相反:每个新的实例都会创建重复的属性和方法。如果你要创建许多(如上千个)大对象的实例,内存消耗可能会成为一个问题。不过这个问题很容易解决:可以将较大的属性或方法保存在一个对象中,并将其作为参数传给构建函数。这样所有实例就可以共同使用一个对象资源,而不是创建自己的版本。

 

“快节奏的JavaScript图形编程”的概念也许听起来很矛盾(oxymoron)。

老实说,尽管JavaScript和Wed浏览器的组合不太可能创作出最尖端的游戏软件, 但还是有很大空间来创建漂亮、快节奏和图形丰富的应用,包括游戏。可用的工具虽然不是最快的,但却免费、灵活、而且容易上手。

作为一种解释性语言,JavaScript不能像C++等语言那样从编译优化中获益。尽管现代浏览器已经大大提升了JavaScript效率,还是有很多空间来提高JavaScript应用的执行效率。这需要程序员去决定使用什么算法,优化哪段代码,如何以高效的方式操作DOM。目前还没有一个健壮的优化工具可以为你完成这件事。

除非代码实在太烂,否则一般情况下简单的JavaScript应用(如仅处理少量鼠标单击或进行零散AJAX调用)是没必要进行代码优化的。而本书中涉及的应用则需要高效的代码才能保证用户体验——好的动画不应该慢或抖动。

本章剩余部分将不讨论如何提高从服务器加载页面的速度,而是考虑服务器资源加载之后的代码执行。具体来说,它讨论适用于JavaScript图形编程的优化技术。

和优化技术同等重要的是:知道什么时候不优化。过早优化会带来晦涩的代码和bug,优化很少执行的代码区域也没有必要。以帕莱托法则(即80-20法则)来看,20%的代码将占用80%的CPU周期。程序员应该集中于优化这20%、10%或5%,而忽略其他部分。这样bug会更少,大部分代码都保持了可读性,也保证了你的头脑清醒。

你可以用Firebug等性能测试工具,来了解哪些函数花费了绝大部分执行时间;然后检查这些函数并决定要优化的代码段。Firebug性能测试器依赖火狐(Firefox)浏览器,有些浏览器有自己的性能测试器。而老版本的浏览器则不一定有类似的工具。

图1-1是Firebug性能测试器的界面。在Console菜单,选择Profile来开始性能测试,然后再选择Profile来停止测试。然后Firebug会显示所有在开始和结束点之间被调用的JavaScript函数分析。所显示的信息如下所示。

图1-1 运行中的Firebug性能测试器

Function

被调用的函数名

Percent

在函数中所花费的时间和总时间的比例

Call

函数被调用的次数

Own time

在函数中所花费的时间(不包括对其他函数的调用)

Time

在函数中所花费的时间(包括对其他函数的调用)

Average

Own time的平均值

Min

函数的最快执行时间

Max

函数的最慢执行时间

File

函数所在的JavaScript文件

如果你能自己创建适合所有浏览器的性能测试集,可以提高开发效率,并在没有测试工具的浏览器上使用。它只是将相同的测试页面载入到每一个浏览器,然后阅读测试结果。它也可以用来迅速检查函数内的细微优化。“自定义代码性能测试”一节将讨论如何创建自己的性能测试集。

 

 警告

类似Firebug的调试器会给时间数值带来不小的误差。在执行性能测试之前,要确保关闭调试器。

 

“优化”是一个很宽泛的词,程序员可以从不同方面,以不同方式来对一个Web应用进行优化。

算法

应用程序是使用最有效的方法来处理数据的吗?代码优化没法修正一个差劲的算法。实际上,找对算法和DOM操作的高效一样,是保证应用快速运行最重要的因素之一。

有时,如果应用要求不高,一个慢但容易实现的算法就足够了。但如果性能是个问题,你也许需要检查研究一下当前所使用的算法。

本书不会讨论常见的搜索和排序等具体算法,因为读者可以在相关的计算机书籍和网络资源中找到许多关于它们的讨论。即使是游戏中涉及的3D图形学、物理和碰撞检测等这些更专业的问题和算法,也有很多书籍可以参考。

JavaScript

仔细检查调用得非常频繁的代码,在应用的某些关键区域中,对频繁执行部分的一个小小优化都会有不错的收益。

DOMjQuery

DOM加jQuery是操纵Web页面非常方便的一种方式。但如果你没有注意到一些简单规则的话,也会成为性能重灾区。DOM搜索和操作比较慢,应该尽量避免。

浏览器并不是运行准确代码性能测试的完美环境。短时间的定时器不够准确、事件的要求、零散的垃圾回收和系统上运行的其他进程都会导致结果偏差。一般可以这样来测试JavaScript代码的性能。

这种方法虽然理论上可行,但由于前面提到的原因,现实中它不能给出准确的结果,尤其是当被测试代码只有几毫秒执行时间的情况下。

更好的方法是让被测试代码循环运行较长的时间(比如1秒),然后用在那段时间内完成的循环次数来评价性能。如果你要计算均值(mean)和中值(median)等统计信息,可以重复测试几次。

为保证测试运行较长时间,使用这个代码:

无论系统性能如何,这些测试都会运行相同的时间。更快的系统会完成更多的循环次数。在实践中采用这种方法能得到较为一致的结果。

你可以运行5次这样的性能测试,每次1秒,循环次数的中值可作为最终的衡量标准。

严格来说,任何用于JavaScript的优化也适用于其他语言。到了CPU层,道理都是一样的:尽量少做工作。在JavaScript中,CPU层的工作和程序员距离太远,以至于很难确定到底CPU层进行了哪些工作。使用一些前人证实过可行的方法,一般来说对你的代码是有益处的,尽管只有通过实验测试才能明确证明。

高开销的计算可以预先进行,并将值存在一个查找表(lookup table)中,使用时给出简单的整型下标(index)就可以取出查找表中的值。只要查找表访问的代价比从头计算的代价低,你的应用程序就能因此获得更好的性能。比如,JavaScript的三角函数就可以利用查找表加速。在这节中,将用一个查找表取代Math.Sin()函数,并用它来构建一个图形动画的应用。

Math.sin()函数接受一个参数:角度(以弧度为单位),并返回一个−1~1之间的值。角度参数的有效范围是0~2π(约6.283 18)弧度。这个范围对索引一个查找表没什么帮助,因为只有6个可能的整数。与其这样,不如完全不用弧度,而是让查找表接受0~4 095的整数索引。这个粒度对大多数应用来说足够了,但你可以通过给参数steps设置更大的值来得到更精密的查找表。

fastSin()函数将2π弧度分为参数中定义的步数,并将每一步得到的结果保存在数组中。

图1-2为Math.sin()和查找表的性能测试结果的比较。

图1-2 Math.sin()和查找表的性能测试对比。数值越大,性能越好

大多数浏览器上的性能提高大约有20%,而Google Chrome上的提高幅度更大。如果查找表里面的值是由比Math.sin()更复杂的函数计算出来的,查找表方法的性能优势将更明显;因为不论计算值的时间多长,查找表的访问时间保持不变。

下面的应用使用fastSin()查找表来创建一个动画,其显示结果如图1-3所示。

图1-3 在一个动画应用中使用的sine查找表

下面的代码调用fastSin()函数创建一个sine的查找表,保存在变量sinTable[]中。

下面的drawGraph()函数通过更新许多1像素宽的div的高度和位置,画出一个正弦波。表1-1列出了相关参数。

表1-1 传递给drawGraph()的参数

参  数

描  述

ang

正弦波的开始角度

freq

正弦波的频率,定义了波的“紧密度”

height

正弦波的幅度,也影响画线的宽度

下面的循环创建480个1像素宽的div元素。这些div被添加到$drawTarget中。

drawGraph()函数通过bars[]数组来引用这些div。

setInterval()函数以连续变化的参数,重复调用drawGraph(),创造出动画效果:

在JavaScript中,所有数都以浮点数形式表示。和C++和Java等语言不同,JavaScript语言中无法显示声明int和float类型。这个惊人的遗漏是由于JavaScript早期只是面向Web设计者和业余爱好者的简单语言。虽然JavaScript的单个数值类型帮程序员避免了许多数值类型错误,但毕竟整数更快,CPU更容易处理,在许多情况下是其他语言的首选数值类型。

 

 提示

ECMAScript规范中定义JavaScript的数值表示为“双精度64位的IEEE 754格式,即IEEE二进制浮点数算术标准”。其表示范围很广,大约从大数(±1.797 693 134 862 315 7 × 10308)到小数(±5×10−324)。不过需要注意的是:浮点数是有误差的,比如alert(0.1+0.2)会显示0.300 000 000 000 000 04,而不是0.3。

 

不过,仔细阅读ECMAScript标准会发现JavaScript有几个内部操作可以处理整数:

ToInteger

转为整数

ToInt32

转为有符号32位整数

ToUint32

转为无符号32位整数

ToUint16

转为无符号16位整数

你不能直接使用这些操作,而是在执行位操作时被自动调用,使得数字被预先转为合适的整型。虽然这些操作看起来和Web编程不相关,但实际上它们可用于优化。

 

 警告

位操作将数字转为32位整数,数字范围为−2 147 483 648~2 147 483 647。超过这个范围的数字也会被调整到这个范围。

 

1.二进制数的快速回顾

曾几何时,程序员经常要与二进制数打交道。使用彼时计算机所需的底层编程要求对二进制和十六进制有很好的理解。如今,二进制数很少被用在Web编程,但在硬件驱动和网络等领域仍有一席之地。

每个人都熟悉十进制数系统。在表1-2的第一行,从右到左每列所表示的权重从小到大是10的幂。将第二行的数字和对应的权重乘起来,并将所有乘积相加,就得到了最终数字为:

(3×1 000) + (9×1) = 3 009

表1-2 十进制数系统

10 000

1 000

100

10

1

0

3

0

0

9

二进制数系统也是类似的,不同的是每列的权重为2的幂,而不是10的幂。第二行中的数字只能是0或1,也称比特或位(bit)。二进制数简单的开关特性使其非常适合在数字电路中模拟。表1-3显示了十进制数69的二进制表示:

(1 × 64) + (1 × 4) + (1 × 1) = 69

表1-3 十进制数69的8位二进制数表示

128

64

32

16

8

4

2

1

0

1

0

0

0

1

0

1

二进制数如何取反?一般采用一个叫做补码的系统:

1.将二进制数中的每位取反,因此01000101变为10111010。

2.加1,因此10111010变为10111011(−69)。

最左边的比特叫做符号位,0代表正,1代表负。使用同样的步骤,我们可以从−69回到+ 69。

2.JavaScript的位操作

JavaScript的位操作在整数的二进制数字(或位)上进行。

位与(x&y):对操作数进行二进制与的操作,如果两个操作数的某一位都为1,将对应的结果位设为1。因此0x0007&0x0003的结果为0x003。此操作可用于检查一个对象是否有一组属性或标记。表1-4显示了一个宠物对象的标记。一个小型、年老、棕色的狗可以用64 + 16 + 8 + 2 = 90来标记。

表1-4 一个宠物对象的二进制标记

大型

小型

年轻

年老

棕色

白色

128

64

32

16

8

4

2

1

搜索一个有特定标记的宠物,只需要和搜索值进行位与操作。下面的代码搜索大型、年轻和白色的宠物(猫狗都可以):

整型有32位来表示不同的标记,而相比之下其他方法,如分开表示标记或其他类型的条件测试,要慢许多。比如:

&运算符也可达到类似取余运算符(%)的效果,也就是返回除法后的余数。下面的代码将保证变量value总是在0到7之间:

不过这种等价性只有在&后面的值是2的幂−1(1,3,6,15,31,...)时才成立。

位或(x|y):对操作数进行二进制或的操作,如果两个操作数的某一位至少有一个为1,将对应的结果位设为1。因此0x0007|0x0003的结果为0x0007。

位异或(x^y):对操作数进行二进制异或的操作,如果两个操作的某一位只有一个为1,将对应的结果位设为1。因此0x0000^0x0001的结果是0x0001,而0x0001^0x0001的结果是0x0000。这可以用于方便地切换变量:

每次执行toggle^=1;,toggle值将在1和0值之间转换(假设原来的值是1或0)。下面是等价的if-else代码:

或者:

位非(~x):对所有位进行取反。例如11100111将变为00011000。如果操作数是有符号整数(最左位为符号位),则~操作符等于取负减1(前面提过补码中取负对应各位取反加1)。

位左移(x<<numBits):对x的二进制向左移numBits位。所有位向左移,最左的位丢失,0填补最右的位。这等价于无符号整数的乘法 x*2^numBits。例如:

测试显示左移位运算和对应的乘法运算符(*)相比没有性能提升。

算术位右移(x>>numBits):对x的二进制向右移numBits位。除了(最左)符号位,所有位向右移,最右位丢失。这相当于有符号整数除法 x/2^numBits。例如:

测试显示右移位运算和对应的除法运算符(/)相比没有性能提升。

下面的代码看起来毫无用处:

但是它使得JavaScript调用其内部的整数转换函数,剔除数字的小数部分。这实际上是一个快速的Math.floor()函数。图1-4显示其在IE8、Google Chrome和Safari 5.0中都有速度提升。

图1-4 Math.floor()与位移(bitshift)的对比。数值越大,性能越好

逻辑位右移(x>>>y):很少用到,类似>>操作符,但符号位不保留而填补为0。对正数来说,这和>>操作符没两样;对负数来说,逻辑位右移的结果将成正数。例如:

3.循环展开:麻烦的真相

任何编程语言中的循环都会增加额外的开销。循环通常需要维护一个计数器和/或检查结束条件,这两者都花费时间。

移除循环开销将提供一些性能提升。一个典型的JavaScript循环如下:

如果替换成下面的代码,你可以完全去除循环开销:

不过,对只有8次迭代的循环,性能的提升不大。假设循环体是一个简单语句(如x++),循环展开可能会快300%,但只是在毫秒级的;3毫秒比1毫秒不会有很大的差别。如果循环体花费时间较长,那可能是0.100 003秒和0.100 001秒的差别,也不太值得去优化。

有两个因素决定了循环展开是否会带来可观的好处:

要完全展开成千上百的迭代并不现实。现实的解决方案是使用达夫设备经典算法的变种,部分展开循环。比如,1 000个迭代的循环可以分成125个展开8次的迭代:

第一个while循环处理了不能被8整除的部分迭代。比如1 004次迭代需要1个4次(1 004%8)普通迭代的循环,然后跟着125个(parseInt(1 004/8))展开的8次迭代。下面是一个稍稍改进的版本:

 

 提示

达夫设备指的是由Tom Duff在1983年开发的一种循环展开的C语言优化技术。循环展开是汇编语言中常用的技术,细小的优化就可以在内存复制等领域发挥作用。具有优化功能的编译器也可能进行自动的循环展开。

 

对一个循环体很少的10 000次循环的迭代,这会得到很大的性能提升。图1-5显示了结果。那我们应该像这样优化所有循环吗?不。这个测试是不现实的:循环体只有一个局部变量自增的操作是比较少见的。

图1-5 展开一个循环体很少的10 000次循环。结果不错,但是不要激动。数值越大,性能越好

一个更好的测试是迭代一个数组并用数组内容调用函数。下面是一个更接近现实的应用:

图1-6显示了性能的提升。注意,一个更现实的循环体使得循环展开的作用大大降低。这就好像点了一个4 000卡路里的超级汉堡套餐,而希望低糖汽水可以帮助减肥。对于10 000次的迭代来说,图中的结果让人失望。

图1-6 展开一个循环体很少的10 000次循环。结果令人失望。数值越大越好

通过实验我们发现JavaScript循环实际上很高效,你需要在实际应用的背景下去测试循环展开这种优化技术,来实际测试它们的好处。

jQuery是一个被广泛使用的JavaScript库,它能用简洁方便灵活的方式来访问和操作DOM元素,还可以减轻跨浏览器问题,使得你可以集中于核心应用开发而不是浏览器兼容问题。jQuery以选择器引擎为核心,是你能以熟悉的CSS样式的选择器语言来找到DOM元素。例如,下面的代码返回一个jQuery对象(一种数组)包含所有具有“big” CSS类的图像元素:

或jQuery简写方式:

$images变量只是一个普通变量,前面的$仅仅是提醒它引用一个jQuery对象。

有一点需要特别注意的是:一个看似简单无害的jQuery语句会在幕后做很多工作。如果只偶尔访问少量的元素,还没有太大关系。不过,如果要连续访问许多元素,比如在一个动画感很强的页面,就会严重地影响性能。

用DHTML创建JavaScript图形的一个基本操作就是快速操作DOM元素的CSS样式属性。在jQuery中,你可以这么做:

这个语句会找到id是element1的元素,并修改其CSS颜色样式为红色。

这个语句分解开来做了这么些事:

连续进行这类的工作将会很慢,不管jQuery如何高效:

因为这里每行进行一次id为element1元素的搜索,这样很没有效率。

一个更快的方法是指定jQuery应该搜索的范围。jQuery默认情况下要从document根或DOM层次的最上层开始搜索。而在许多情况下,这是没有必要的。如果你指定一个范围,会减少jQeury的搜索工作,更快地返回结果。

下面的例子搜索所有具有alien CSS类的元素,从环境参数(container)内的DOM元素中开始搜索:

环境参数的类型是灵活的,可以是另一个jQuery对象或CSS选择器:

当然要确保搜索环境不比搜索元素本身慢。如果可能的话,应尽量直接引用DOM元素。

理想情况下,一旦元素被找到,你不应该再重新搜索它们。我们可以将搜索结果存起来:

不过上面的代码中jQuery的css()函数调用还是做了额外的工作,我们可以直接引用到DOM元素的实际式样对象中:

图1-7显示了前面3种方式的性能结果。在页面更复杂、CSS选择器更慢的情况,这种差别会更加明显。

图1-7 3种方式的速度比较(原始方式、保存元素对象的方式、直接写元素CSS式样的方式)

直接操作元素的属性本身比使用jQuery更快。比如,jQuery.html()方法要比直接使用一个元素的innerHTML对象要慢许多。

图1-7的结果意味着我们应该完全不使用jQuery吗?不是,jQuery是不容错过的,在某些环境下慢些是可以理解和接受的。但在速度很关键的代码区内应注意jQuery的使用方式。这通常只是所有代码中的一小部分。你的大部分应用程序可以(也应该)使用jQuery来快速方便地进行开发。

如果你的应用需要加入大量的元素到DOM中,可能会影响性能。DOM是一个复杂的数据结构,应尽量少去修改。这在动态Web页面中当然是不太可能的,因此你需要一个高效的方式来插入元素。

你可以通过jQuery来插入一个元素到DOM中:

这对几个元素来说是足够了,但当你需要插入成百上千个元素时,单个插入这些元素会太慢。

更好的方式是将所有要插入的元素构建为一个大的字符串,然后一次插入。对每个元素,这防止了jQuery调用和进行各种内部测试的开销。

如果你想扩展自己的JavaScript知识,可以阅读下面两本经典书籍:


相关图书

HTML+CSS+JavaScript完全自学教程
HTML+CSS+JavaScript完全自学教程
JavaScript面向对象编程指南(第3版)
JavaScript面向对象编程指南(第3版)
JavaScript全栈开发
JavaScript全栈开发
HTML CSS JavaScript入门经典 第3版
HTML CSS JavaScript入门经典 第3版
HTML+CSS+JavaScript网页制作 从入门到精通
HTML+CSS+JavaScript网页制作 从入门到精通
JavaScript重难点实例精讲
JavaScript重难点实例精讲

相关文章

相关课程