第七章 Ajax 异步JavaScript和XML
Ajax可以通过延迟下载大量资源使页面加载更快。它通过在客户端和服务器之间异步传送数据,避免页面集体加载。还用于在一次HTTP请求中获取整个页面的资源。
数据传输
Ajax,在它最基本的层面,是一种与服务器通讯而不重载当前页面的方法,数据可从服务器获得或发送给服务器。
请求数据
有以下五种常用技术用于向服务器请求数据:
XMLHttpRequest(XHR);
动态脚本标签插入;
iframes;
Comet;
多部分的XHR。
XMLHttpRequest
目前最常用的方法中,XMLHttpRequest(XHR)用来异步收发数据。所有现代浏览器都能够很好地支持它,而且能够精细地控制发送请求和数据接收。你可以向请求报文中添加任意的头信息和参数(包括GET和POST),并读取从服务器返回的头信息,以及响应文本自身。
根据readyState的属性值来判别当前响应的状态;readyState等于4表示接收完并可用于操作;等于3时表示此时正在与服务器交互,响应报文还在传输中。这就是所谓的“流”,它是提高数据请求性能的强大工具。
老版本的 IE 也不提供 readyState 3,它不支持流。从请求返回的数据像一个字符串或者一个 XML 对象那样对待,这意味着处理大量数据将相当缓慢。
使用XHR时,应使用POST还是GET
如果 请求不改变服务器状态 只是取回数据(又称作 幂等动作)则使用GET操作。GET请求被缓存起来,如果你多次提取相同的数据可提高性能。
只有当URL和参数长度超过了2048个字符时才使用POST提取数据。因为IE限制了URL的长度,过长将导致请求(参数)被截断。
动态脚本标签插入
动态脚本标签插入这以技术克服了XHR的最大限制:它可以从不同域的服务器上获取数据。
动态脚本标签插入与XHR相比只提供更少的控制。 你不能通过请求发送信息头。 参数只能通过GET方法传递,不能用POST。你必须等待所有数据返回之后才可以访问它们。你不能访问响应信息头或者像访问字符串那样访问整个响应报文。
因为响应报文 被用作脚本标签的源码,它必须是可执行的JavaScript。你不能使用裸XML,或裸JSON,任何数据,无论什么格式,必选在一个回调函数之中被组装起来。
其响应结果是运行JavaScript。
请小心使用这种技术从你不能直接控制的服务器上请求数据。JavaScript 没有权限或访问控制的概念,所以你的页面上任何使用动态脚本标签插入的代码都可以完全控制整个页面。包括修改任何内容、将用户重定向到另一个站点,或跟踪他们在页面上的操作并将数据发送给第三方。使用外部来源的代码时务必非常小心。
多部分XHR
多部分XHR允许你一个HTTP请求就可以从服务器端获取多个资源。它通过将资源(可以是CSS文件,HTML片段,JavaScript代码或base64编码的图片)打包成一个由特定分隔符界定的大字符串,从服务器端发送到客户端。
让我们从头到尾跟随这个过程。首先,发送一个请求向服务器索取几个图像资源:
上述代码中,图像不是从 base64 转换成二进制,而是使用 data:URL 并指定 image/jpeg 媒体类型。
下面的函数用于将 JavaScript 代码、CSS 样式表和图片转换为浏览器可用的资源:
由于MXHR响应报文越来越大,有必要在每个资源收到时立即处理,而不是等待整个响应报文接收完成。
通过监听readyState3实现:
当 readyState 3 第一次发出时,启动了一个定时器。每隔 15 毫秒检查一次响应报文中的新数据。数据片
段被收集起来直到发现一个分隔符,然后一切都作为一个完整的资源处理。
使用MXHR的最大缺点是:以此方法 获得的资源不能被浏览器缓存。
此外,在IE7及其更低版本不支持readyState或data:URL。
尽管有这些缺点,但某些情况下 MXHR 仍然显著提高了整体页面的性能:
网页包含许多其他地方不会用到的资源(所以不需要缓存),尤其是图片;
网站为每个页面使用了独一无二的打包的JavaScript或CSS文件以减少HTTP请求,因为它们对每个页面都是独一无二的,所以不需要从缓存中读取,除非重新载入特定页面。
由于HTTP请求是Ajax中最极端的瓶颈之一,减少其需求数量对整个页面性能有很大影响。
发送数据
当数据只需发送服务器时,有两种广泛应用的技术:XHR和灯标。
XMLHttpRequest
XHR主要用于从服务器获取数据, 它也可以用来将数据发回。 数据可以用 GET 或 POST 方式发回,
以及任意数量的 HTTP 信息头。当你向服务器发回的数据量超过浏览器的最大URL长度时XHR特别有用。
当使用 XHR 将数据发回服务器时,它比使用 GET 要快。这是因为对少量数据而言,向服务器发送一个
GET 请求要占用一个单独的数据包。另一方面,一个 POST 至少发送两个数据包,一个用于信息头。另一
个用于 POST 体。
灯标
此技术与动态脚本标签插入非常相似。JavaScript用于创建一个新的Image对象,将src设置为服务器上一个脚本文件的URL.此URL包含我们打算通过GET格式传回的键值对数据。注意,并没有创建img元素或者将它们插入到DOM中。
这是将信息返回服务器最有效的方法。可以通过监听Image对象的load事件,它可以告知服务器是否成功接收了数据。
还可以检查服务器返回图片的宽度和高度(如果返回了一张图片),并用这些数字通知你服务器的状态。例如,宽度为1表示“成功”,2表示“重试”。
如果你不需要为此响应返回数据,那么你应当发送一个 204 No Content 响应代码,无消息正文。它将阻止客户端继续等待永远不会到来的消息体。
灯标是向服务器回送数据最快和最有效的方法。唯一的缺点是接收到的响应类型是受限的。
如果需要向客户端发送大量数据,那么使用XHR。如果只关心将数据发送到服务器端,那么使用图像灯标。
数据格式
在考虑数据格式时,唯一需要比较的尺度的就是速度。
XML
当Ajax开始流行时,它选择了XML数据格式。很多事情是围绕着它做的:极端的互通性(服务器端和客户端都能够良好保持),格式严格,易于验证。
与其他格式相比,XML极其冗长。每个离散的数据片断需要大量结构,所以有效数据的比例非常低。此外,解析XML也比较繁琐。
一个更有效的方式是将每个值都存储为<user>标签的属性。数据相同而文件尺寸却更小。
JSON
JSON是一个轻量级并易于解析的数据格式,它按照JavaScript对象和数组字面语法所编写。
在解析JSON数据的过程中,需要保持数据的顺序。
使用()可以将字符串转换为JavaScript代码。
数组形式的json数据文件尺寸最小,下载最快,平均解析时间最短。
JSON-P
当使用XHR时JSON数据作为一个字符串返回,该字符串使用()转换为一个本地对象。然而,当使用动态脚本标签插入时,
JSON数据被视为另一个JavaScript文件并作为本地代码执行。为做到这一点,数据必须被包装在回调函数之中。这就是
“JSON填充”或JSON-P。
JSON-P 因为回调包装的原因略微增加了文件尺寸,但与其解析性能的改进相比这点增加微不足道。由于数据作为本地 JavaScript 处理,它的解析速度像本地 JavaScript 一样快。
最快的JSON格式是:使用数组的JSON-P格式。
还有一个与性能无关的原因要避免使用 JSON-P:因为 JSON-P 必须是可执行的 JavaScript,它使用动态脚本标签注入技术可在任何网站中被任何人调用。从另一个角度说,JSON 在运行之前并不是有效的。
JavaScript,使用 XHR 时只是被当作字符串获取。不要将任何敏感的数据编码为 JSON-P,因为你无法确定它是否包含私密信息,或者包含随机的 URL 或 cookie。
HTML
JavaScript能够比较快地将一个大数据结构转化为简单的HTML,但是服务器完成同样工作更快。
一种技术考虑是在服务器端构建整个 HTML 然后传递给客户端,JavaScript 只是简单地下载它然后放入 innerHTML。
此技术的问题在于,HTML是一种详细的数据格式,比XML更加冗长。因此,只有当客户端 CPU 比带宽更受限时才使用此技术。
字符串操作是JavaScript最慢的操作之一。
自定义格式
最理想的数据格式只包含必要的结构,使你能够分解出每个字段。可以自定义一种格式只是简单地用一个分隔符将数据连接起来。然后调用字符串的split()方法将分隔符作为参数传入,即可分解数据。
split()是最快的字符串操作之一。
对于非常大的数据集,它是最快的传输格式,甚至可以在解析速度和下载时间上击败本机执行的JSON。用此格式向客户端传送大量数据只用很少的时间。
数据格式总结
最好的格式是JSON和字符分隔的自定义格式。
JSON-P数据,用动态脚本标签插入法获取。它将数据视为可运行的JavaScript代码而不是字符串,解析速度极快,并且可实现跨域。
字符分隔的自定义格式,使用XHR或动态脚本标签插入技术提取,使用split()解析。
Ajax性能向导
缓存数据
最快的Ajax就是你不要用它。有两种主要方法避免发出一个不必要的请求:
在服务器端,设置HTTP头,确保返回报文将被缓存在浏览器中;
在客户端,于本地缓存已获取的数据,不要多次请求同一个数据;
设置HTTP头
如果希望Ajax响应报文能够被浏览器所缓存,你必须在发起请求时使用GET方法,并且必须在响应报文中发送正确的HTTP头。Expires头告诉浏览器应当缓存响应报文多长时间。
什么是Expires头?
Expires存储的是一个用来控制缓存失效的日期。当浏览器看到响应中有一个Expires头时,它会和相应的组件一起保存到其缓存中,只要组件没有过期,浏览器就会使用缓存版本而不会进行任何的HTTP请求。Expires设置的日期格式必须为GMT(格林尼治标准时间)。
Expires 头示例如下:
Expires: Mon, 28 Jul 2014 23:30:00 GMT
一个 Expires 头是确保浏览器缓存 Ajax 响应报文最简单的方法。
本地存储数据
除了依赖浏览器处理缓存之外,还可以使用本地存储数据的方法,将从服务器接收到的响应报文存储。
将响应报文存放在一个对象中,以URL为键值索引。
了解Ajax库的限制
所有 JavaScript 库允许你访问一个 Ajax 对象,它屏蔽浏览器之间的差异,给你一个一致的接口。大多数情况下这非常好,因为它使你可以关注你的项目,而不是那些古怪的浏览器上 XHR 的工作细节。
然而,为了给你一个统一的接口,这些库必须简化接口,因为不是所有浏览器都实现了每个功能。这使得你不能访问 XMLHttpRequest的完整功能。
总结
高性能Ajax包括:知道你项目的具体需求,选择正确的数据格式和与之相配的传输技术。
作为数据格式,纯文本和 HTML 是高度限制的,但它们可节省客户端的 CPU 周期。XML 被广泛应用普遍支持,
但它非常冗长且解析缓慢。JSON 是轻量级的,解析迅速(作为本地代码而不是字符串),交互性与 XML 相当。
字符分隔的自定义格式非常轻量,在大量数据集解析时速度最快,但需要编写额外的程序在服务器端构造格式,
并在客户端解析。
进一步提高Ajax的速度的方法:
减少请求数量,可通过JavaScript和CSS文件打包,或者使用MXHR;
缩短页面加载时间,在页面其他内容加载之后,使用Ajax获取少量重要文件;
确保代码错误不要直接显示给用户,并在服务器端处理错误。
学会何时使用一个健壮的 Ajax 库,何时编写自己的底层 Ajax 代码。
第八章 编程实践
避免二次解析
JavaScript与其他的许多脚本语言一样,允许你在程序中获取一个包含代码的字符串然后运行它。
有四种方法可以实现:eval(),Function()构造器,setTimeout()和setInterval()。每个函数运行传入
一串JavaScript代码,然后运行它。
当在JavaScript代码中执行(另一段)JavaScript代码时,需要付出二次解析的代价。
此代码先被解析为正常代码,然后在运行过程中,运行字符串中的代码时发生另一次解析。
二次解析是非常昂贵的操作,与直接包含相应代码相比将占用更多的时间。
每次调用eval()/Function()构造器/setTimeout()/setInterval()时要创建一个新的解释/编译实例,使得代码执行速度变慢。
因此,应尽量避免使用eval()和Function()构造器,在使用setTimeout()和 setInterval()时,建议第一个参数传入一个函数而不是一个字符串。
使用对象/数组直接量
在JavaScript中有多种方法创建对象和数组,但没有什么比创建对象和数组直接量速度更快了。
直接量赋值很快,并且在代码中占用较少空间,所以整个文件尺寸可以更小。
不要重复工作
在计算机科学领域最重要的性能优化技术之一是避免工作。
避免工作的概念实际上意味着两件事:不要做不必要的工作;不要做重复做已经完成的工作。
最常见的重复工作类型是浏览器检测。
有多种办法可以避免重复工作:
延迟加载(lazy loading)
延迟加载意味着在信息被使用之前不做任何工作。
上述两个方法第一次被调用时,检查一次并决定使用哪种方法附加或分离事件句柄。然后原始函数就被包含适当操作的
新函数覆盖了。
延迟加载适用于函数不会在页面上立即被用到的场合。
条件预加载
条件预加载,在脚本加载之前提前检查,而不等待函数调用。
条件预加载确保所有函数调用时间相同。适用于一个函数马上就会被用到,而且在整个页面生命周期中经常使用的场合。
使用速度快的部分
虽然 JavaScript 速度慢很容易被归咎于引擎,然而引擎通常是处理过程中最快的部分,实际上速度慢的是你的代码。
位操作运算符
JavaScript中有四种位逻辑操作符
位与:两个操作数的位都是1,结果才是1;
位或:有一个操作数的位是1,结果就是1;
位异或:两个操作数的位中只有一个1,结果才是1;
位非:遇0返回1,反之亦然。
有许多方法可以使用位运算提高JavaScript的速度。首先可以用位运算替代纯数学操作。
例如,通常采用对2取模运算实现表行颜色交替显示。
在32 位数字的底层(二进制)表示法,偶数的最低位是 0, 奇数的最低位是 1。 如果此数为偶数, 那么它和 1 进行位与操作的结果就是 0;如果此数为奇数,那么它和 1 进行位与操作的结果就是 1。
第二种使用位操作的技术称作位掩码。位掩码可以同时判断多个布尔选项,快速地将数字转换为布尔标志数组。
掩码中每个选项的值等于2的幂。
原生方法
JavaScript 的原生部分——在你写代码之前它们已经存在于浏览器之中了——都是用低级语言写的,速度非常快。
例如JavaScript中的Math对象,处理复杂的数学计算应该优先考虑。
当原生方法可用时,尽量使用它们,尤其是数学运算和DOM操作。
总结
通过避免使用 eval()和 Function()构造器避免二次解析。此外,给 setTimeout()和 setInterval()传递函数参数而不是字符串参数;
创建新对象和数组时使用对象直接量和数组直接量;
避免重复进行相同工作。当需要检测浏览器时,使用延迟加载或条件预加载;
当执行数学运算时,考虑使用位操作,它直接在数字底层进行操作;
原生方法总是比 JavaScript 写的东西要快。尽量使用原生方法。
第九章 创建并部署高性能JavaScript应用程序
Apache Ant
Apache Ant 是一个自动构建软件的工具。类似于make,在Java中实现,并使用XML来描述生成。
Ant具有可移植性。默认的 Ant 开发文件为 XMl 格式的 build.xml。每个开发文件只包含一个项目和至少一个目标体。一个
Ant 目标体可依赖于其他目标体。
合并JavaScript文件
第一个也是最重要的提高速度的准则是:减少渲染页面所需的HTTP请求。合并资源文件能够减少HTTP请求。
Apache Ant 通过 concat 任务提供合并几个文件的能力
预处理JavaScript文件
预处理器的任务是将输入数据处理成另一种编程语言所使用的数据。
没有专门为 JavaScript 设计的预处理器。
JavaScript紧凑
JavaScript 紧凑指的是剔除一个 JavaScript 文件中一切运行无关内容的过程。包括注释和不必要的空格。
常用YUI压缩器对JavaScript进行压缩。
除了剔除注释和不必要的空格,YUI 压缩器还提供以下功能:
将局部变量名替换以更短的形式(1 个,2 个,或 3 个字符),以优化后续的 gzip 压缩工作;
尽可能将中括号操作符替换为点操作符(例如 foo:[“bar”]变成 foo.bar);
尽可能替换引号直接量属性名(例如{“foo”:”bar”}变成{foo:”bar”});
替换字符串中的转义引号(例如’aaa\’bbb’变成”aaa’bbb”);
常量折叠(例如”foo”+”bar”变成”foobar”)。
通过以下步骤,还可以进一步优化,节省空间:
将局部引用存储在对象/值中,用闭包封装代码,使用常量替代重复值,避免 eval(以及相似的 Function 构造器,
setTimeout 和 setInterval 使用字符串直接量作为第一个参数),with 关键字,JScript 条件注释,都有助于进一步精缩文件。
开发过程中的编译时和运行时
连接,预处理,和紧凑既可以在编译时发生,也可以在运行时发生。
开发高性能应用程序的一个普遍规则是,只要能够在编译时完成的工作,就不要在运行时去做。
JavaScript 压缩
当网页浏览器请求一个资源时,它通常发送一个Accept-Encoding的HTTP头(以HTTP/1.1开始)让网页服务器知道传输所支持的编码类型。
Accept-Encoding 的取值范围是:gzip,compress,deflate和 identity。
gzip通常可将有效载荷减少到70%,主要用于文本报文,包括JavaScript文件。
缓存JavaScript文件
使用HTTP组件可缓存将大大提高用户再次访问网站时的用户体验。
网页服务器使用 Expires 响应报文 HTTP 头让客户端知道缓存资源的时间。
另一种技术是使用HTML5离线应用程序缓存。此技术依赖一个配置文件,列出应当被缓存的资源。此配置文件通过<html>标签的manifest属性。
关于缓存的问题
充分利用缓存控制可真正提高用户体验,但它有一个缺点:当应用程序更新之后,你希望确保用户得到
静态内容的最新版本。这通过对改动的静态资源进行重命名实现。
使用内容分发网络(cdn)
内容分发网络(CDN)是按照地理位置分布的计算机网络,通过以太网负责向最终用户分发内容。
使用CDN的主要原因是可靠性,可扩展性,但更主要的是性能。事实上,通过地理位置上最近的位置向用户提供服务,CDN可以极大地减少网络延迟。
总结
开发和部署过程对基于 JavaScript 的应用程序可以产生巨大影响,最重要的几个步骤如下:
合并 JavaScript 文件,减少 HTTP 请求的数量;
使用 YUI 压缩器紧凑处理 JavaScript 文件;
以压缩形式提供 JavaScript 文件(gzip 编码);
通过设置 HTTP 响应报文头使 JavaScript 文件可缓存,通过向文件名附加时间戳解决缓存问题;
使用内容传递网络(CDN)提供 JavaScript 文件,CDN 不仅可以提高性能,它还可以为你管理压缩和缓存。
第十章 工具
当确定脚本加载和运行时的瓶颈所在时,合手的工具是必不可少的。
性能分析
在脚本运行期定时执行不同函数和操作,找出需要优化的部分。性能分析工具确保优化工作花费在系统中最慢,
影响大多数浏览器的地方,而不是去判定那些函数或操作缓慢。
网络分析
检查图片,样式表和脚本的加载过程,汇报它们对整个页面加载和渲染的影响
YUI分析器
YUI 分析器是用 JavaScript 编写的JavaScript 分析器。除了计时功能,它还提供了用于函数、
对象、和构造器的性能分析接口,还包括性能分析数据的详细报告。它可以跨浏览器工作,
其输出数据可提供更强大的报告和分析。
Firebug
Firebug 提供了一个控制台日志输出, 当前页面的 DOM 树显示, 样式信息, 能够反观 DOM 和 JavaScript
对象,以及更多功能。它还包括一个性能和网络分析器,这是本节的重点。Firebug 易于扩展,可添加自定
义面板。
脚本阻塞
浏览器每次只能发出一个脚本请求。这样做是为了管理文件之间的以来关系。只要一个文件依赖于
另一个在源码中靠后的文件,它所依赖的文件将保证在它运行之前被准备好。脚本之间的差距表明脚
本被阻塞了。
脚本阻塞将因为一个或多个文件初始化缓慢而变得更加严重,值得对它做某些类型的分析,并有可能优
化或重构。脚本加载会减慢或停止页面渲染,造成用户等待。
总结
使用网络分析器找出加载脚本和其它页面资源的瓶颈所在,这有助于决定哪些脚本需要延迟加载,或者进行进一步分析;
应尽量减少 HTTP 请求的数量,尽量延迟加载脚本以使页面渲染速度更快,向用户提供更好的整体体验;
使用性能分析器找出脚本运行时速度慢的部分,检查每个函数所花费的时间,以及函数被调用的次数,
通过调用栈自身提供的一些线索来找出哪些地方应当努力优化。