转载自:http://book.csdn.net/bookfiles/362/10036213882.shtml

随需下载的JavaScript

On-Demand JavaScript

●●○

行为,启动(Bootstrap),跨域(CrossDomain),动态,JavaScript,延迟加载(LazyLoading),随需应变(OnDemand),ScriptTag

图6-13:随需下载的Ja vaScript

目标故事

Bill正登录到他的银行网站,登录表单立刻出现,并且随着他键入用户名称和密码,浏览器安静地下载应用其余部分所需要的JavaScript。

问题

怎样部署大量的JavaScript程序代码?

先决条件

l   Ajax应用大量使用JavaScript。越丰富的浏览器行为就意味着越多的JavaScript需要下载。

l   下载JavaScript对性能有冲击。直到全部初始化JavaScript被下载,交互才能完全开始。

l   大量的JavaScript内容会造成带宽的问题。往往,并非所有下载的JavaScript实际上都能被用到,从而导致带宽的浪费。

方案

下载并执行JavaScript片段。初始页面下载的JavaScript代码包含能进一步下载其他 JavaScript 的启动代码(bootstrapping code)。有两种有效技术:Script Tag Creation(Script 标签建立)和 Service Eval(服务评估)。我们将分别描述这两种技术,然后介绍这个模式的 3 个主要应用。

采用Script Tag Creation技术,使用DOM操作把新的脚本(script)元素注入到页面。这种方式也许让人吃惊,但是这种效果的确完全等同于一开始就已经存在<script>标签:引用到的JavaScript将被下载并自动执行。标签可以被附加在头部消息(head)或主体部分(body)上,然而,前者较常见,因为那通常是你发现script标签的位置:

var head = document.getElementsByTagName("head")[0];

script = document.createElement('script');

script.id = 'importedScriptId';

script.type = 'text/javascript';

script.src = "http://path.to.javascript/file.js";

head.appendChild(script);

如何知道脚本已经下载完毕?在这里,不同浏览器似乎有不同的行为,有些是同步下载脚本,有些则不是。在IE 里,你可能被一种类似于XMLHttpRequest(http://www.xml.com/ lpt/a/2005/11/09/fixing-ajax-xmlhttprequest-considered-harmful.htmlonreadystatechange)的机制所通知。其他浏览器里,你可能需要持续询问某种关于下载的指示(这里的指示取决于脚本的意图)。如果你碰巧能控制服务器脚本,可以实现一个通知机制;即通过回

调侦听函数(如果存在的话),完成该脚本的下载。实际上,有一个大有可为的建议,JSONP(http://bob.pythonmac.org/archives/2005/12/05/remote-json-jsonp/),其目标是完成一个简单(像这样具有灵活性的机制)的标准,适用于整个业界。

Script Tag Creation不让你获取任何旧文本;它必须是完整有效的JavaScript,而且你的脚本不能直接读取它,因为浏览器只用它做结果评估(evaluation)。那么,你怎样获得来自外部服务器的数据?对远程JavaScript最常见的方式,是为变量分配需要的数据:

var characters = new Array("Mario", "Sonic", "Lara");

内容通常是数据结构(data structure),例如,是标准的JSON消息,但是它也需要指定(assignment),以便被代码引用到。另外,它也可以是一个返回需要值的函数。两种方式的任何一种都不尽理想,因为浏览器代码必须使用在脚本中里提到的名称。再一次,像J这种具有灵活性的脚本机制的想法值得考虑,因为它让调用者自行决定变量的名称。

Service Eval是另一种On-Demand JavaScript技术,虽然不如Script Tag Creation卓越(其原因我们稍后将讨论)。在Service Eval里,以标准的XMLHttpRequest Call调用Web服务,输出一些JavaScript作为响应内容,这些JavaScript接着以eval()调用来执行。检查XMLHttpRequest Call的responseText属性,在评估(evaluating)之前,我们能够操作它,因此,它的body内容不必是完整、有效的JavaScript(与Script Tag Creation不同)。

任何不在函数内部的代码将立刻被执行。为了增加新函数以供稍后使用,JavaScript 能够直接添加到当前的窗口,或者现存的已知对象。例如,下面内容可以被发送:

self.changePassword = function(oldPassword, newpassword) {

  ...

}

XMLHttpRequest回调函数只需要把响应当作纯文本,并且把它传给eval()。再一次提醒:这里,异步状况要特别小心处理。你不能假设新的代码在请求之后立即生效,因此,别这么做:

if (!self.changePassword) {

  requestPasswordModuleFromServer();

}

changePassword(old, new) // 第一次将无效,因为 changePassword 尚未加载

相反地,你不需要对服务器产生同步调用,增加循环持续检查新函数,或者在响应处理函数中明确的发起调用。

On-Demand JavaScript有3个不同的应用:

Lazy Loading(延迟载入)

将大量JavaScript代码的加载往后推迟。以其中一种On-Demand JavaScript技术(Service EvalScript Tag Creation)运行。

Behavior Message(行为消息)

让服务器以一种“行为消息”(Behavior Message)的形式响应,行为消息指定浏览器的下一个动作。以其中一种On-Demand JavaScript技术(Service EvalScript Tag Creation)运行。

Cross-Domain Scripting(跨域 Scripting)

使用Script Tag Creation,绕过标准“同源”(same-origin)策略(这通常使 Cross- Domain Proxy成为必要),只能使用Script Tag Creation

先来看看Lazy Loading。传统上的最佳实践是避免不唐突地通过一或多个script标签包含(include)JavaScript。

<html>

  <head>

    <script type="text/javascript" src="search.js"></script>

    <script type="text/javascript" src="validation.js"></script>

    <script type="text/javascript" src="visuals"></script>

  </head>

  ...

</html>

Lazy Loading则建议在初始HTML里,只加载最小的初始化模块(initialization module):

<html>

  <head>

    <script type="text/javascript" src="init.js"></script>

  </head>

  ...

</html>

初始化模块声明页面启动的任何必要动作,或许足以覆盖该页面的一般使用。另外,它必须执行一个初始启动的函数(bootstrapping function),再根据需要下载JavaScript。

第2个应用,Behavior Message,是HTML Message模式的变形。Lazy Loading建立代码库提供持续性的使用,而Behavior Message 接受暂时性的代码并且立刻以eval()执行——这段脚本没有封装在函数中(虽然可以定义一些调用的函数)。实际上,这意味着浏览器正在询问服务器下一步做什么。

Cross-Domain Scripting是此模式的第3种应用。script 标签一直都能包含来自外部域的JavaScript。这个规则不仅适用于静态的<script>标签,对动态建立的script标签(如Script Tag Creation技术)也适用。因此,不同于XMLHttpRequest和IFrame,你的 script 能以这种方式直接访问外部内容。并且,因为src属性可以是任何的URL,你能够传递参数作为CGI变量。这种想法正变得相当受欢迎,有些公司(像是Yahoo!)特别为这种方法(见稍后的“真实世界的实例”)提供JavaScript API。

当你信任这种方式,并且理想地控制这种方式时,执行从外部域来的script是很有用的。其他时候,它肯定是安全性的风险。Douglas Crockford,JSON的创造者,警告外部 script能够造成一定的破坏(http://www.mindsack.com/uxe/dynodes/):

这种脚本能够交付数据,但是它以基础页面(base page)上的脚本的相同授权执行,因此,它能偷取cookie或者滥用服务器用户的授权。恶意的脚本能对用户和基础服务器之间的关系进行破坏….这个不受限制的script标签妙招是浏览器内最后的大安全漏洞。它无法轻易被修复,因为整个广告基础设施依赖此漏洞。要非常小心。

决策

使用Service Eval,还是使用Script Tag Creation?

两者之间的选择取决于几项因素。Service Eval包含一些胜过Script Tag Creation的好处:

l   以XMLHttpRequest为基础,当脚本准备好时,有标准机制被通知,因此没有调用尚未存在的函数的风险。

l   能够访问未经加工的脚本代码。

l   在脚本格式上具有较多灵活性:例如,你能够发送一些在不同XML节点里的JavaScript片段,并且让浏览器脚本将它们提取出来。

Script Tag Creation有两个胜过Service Eval的好处:

l   你能够从外部域载入JavaScript。这是两种风格之间唯一重大的功能性差别。

l   当第一次遇到<script>标签时,JavaScript将自动被评估(evaluated)—— 以大致相同于静态HTML所链接的JavaScript被评估的方式。因此,你不必为了稍后的使用,特别为文档添加变量和函数;只要正常声明它们即可。

使用 Lazy Loading,你将怎样分解模块?

你需要决定怎样切割你的JavaScript。软件开发的标准原则:模块应该职责明确(well- focused),模块间依赖应尽可能避免。另外,还存在一些Web方面的特定考虑:

l   理想的,任何模块不是根本没被用到,就是充分被使用。你不想要5 000行的模块被下载,却只为了获取一个3行的函数,这是带宽的浪费,这一点优于On-Demand JavaScript 的主要目的。

l   你需要一个或多个模块在启动时就存在。至少需要一个,用于开启更进一步的下载。

l   记住,代码可能在本地端被缓存。Alexander Kirk已经对On-Demand JavaScript的缓存做了一些实验(http://alexander.kirk.at/2005/10/11/caching-of-downloaded-code-testing- results/),结果是所有主流浏览器将缓存以Script Tag Generation创建的代码。通常情况下,你能够确保来自XMLHttpRequest的响应也被缓存。

使用Lazy Loading,脚本将在什么阶段下载JavaScript?

在必须使用JavaScript之前下载它是最容易的,但是这种方式未必总是最好的。下载 JavaScript 的过程中,将有一些延迟,这表示,如果你在最后的可能时刻抓取它,用户将等待片刻。当用户的行为或者系统状态,显示某些JavaScript很快将需要被用到时,考虑立刻下载它。

预测 JavaScript 是否将被用到是Predictive Fetch(第13章)的范例——抓取某些直觉上可能需要内容。你必须在使用的可能性和延迟加载可能引起的麻烦之间,有所取舍。例如,想象你有一些需要验证完成表单的JavaScript,如果你等到最后再下载,确实不会浪费带宽,但是代价是用户的满意度。当有一个或两个字段要进行验证,或者在这个表单最初被加载时,你可能就会下载它。每增加一次浪费下载的机会,也就会增加验证程序更平顺的机会。

真实世界的实例

MapBuilder

MapBuilder(http://mapbuilder.sourceforge.net)是一个地图网站(图6-14)框架。它使用 On-Demand JavaScript,降低代码被下载的数量。这个应用基于MVC模型(Model-View- Controller),Model信息被声明在XML文档中,随同需要用于下载相应小部件(widget)的JavaScript。当页面启动时,只有必要的JavaScript被下载,而不是全部代码。因此,这个范例是这个模式的部分实例;它确保只有最小的JavaScript子集被用到,但是不是以延迟方式载入(lazy),或者随需应变(on-demand)的风格。

Delicious/Yahoo! APIs

社交性书签(social bookmarking)网站,Delicious(http://del.icio.us),以及网站的拥有者,Yahoo!,两者皆提供以JSON为基础的API,具有合适的hook,允许你通过Script Tag Creation直接从浏览器访问。Delicious API(http://del.icio.us/help/json)将以此结果建立新对象(如果已经存在,就为它填值)。Yahoo! API(http://developer.yahoo.net/ common/json.html)让你在脚本里指定回调函数,JSON 将被传递给该函数。

图6-14:Map Builder

Dojo打包(packaging)框架

Dojo(http://dojotoolkit.org/download)是针对简化JavaScript开发的一种全面性框架。就其本身而言,它提供许多脚本,个别项目可能只需要使用当中的一个子集。为了管理脚本,有类似Java的包(package)系统,让你根据需要获取新的JavaScript(http://dojo.jot.com/ WikiHome/Documents/DojoPackageSystem)。你只需要直接包含单一的 JavaScript 文档:

<script type="text/javascript" src="/dojo/dojo.js"></script>

接着使用Dojo API根据需要获取包:

dojo.hostenv.moduleLoaded("dojo.aDojoPackage.*");

运行以上命令将引起Dojo自动下载在dojo.aDojoPackage下的模块。

JSAN导入系统

JSAN(JavaScript Archive Network,http://openjsan.org)是一个在线的脚本知识库。此外,它包含一个用来导入 JavaScript 模块的代码库,使用的约定类似 Java。下列的调用:

JSAN.use('Module.To.Include');

映射到Module/To/Include.js。JSAN.includePath定义此路径可能存在的全部顶层目录。如果includePath是 ["/","/js"],JSAN将查找/Module/To/Include和/js/Module/ To/Include。

代码范例:AjaxPatterns On-Demand JavaScript Wiki

将On-Demand JavaScript引入到Wiki Demo

在Basic Wiki Demo(http://ajaxify.com/run/wiki)里,全部JavaScript被一次性下载。但是很多时候,用户仅仅从wiki读取信息——为什么要下载写入的代码呢?因此,这个demo分三阶段重构成On-Demand JavaScript:

1. uploadMessage函数被提取到第2个JavaScript文件,upload.js。还没有用到On- Demand JavaScript,因为两个文件都被包含了进来。

2. 倘若发生上传操作,通过确保只有upload.js文件被下载,进一步的重构引进On-Demand JavaScript。这个版本使用Script Tag Creation

3. 在更进一步的重构里,Script Tag Creation技术被替换成Service Eval

分离JavaScript:提取出upload.js

在第一个重构(http://ajaxlocal/run/wiki/separateJS/)中,上传函数仅仅被移到一个分离的 JavaScript文件。初始HTML包含新文件:

<script type="text/javascript" src="wiki.js"></script>

<script type="text/javascript" src="upload.js">

新的upload.js现在包含uploadMessage函数。为反映出分离,引入了两个参数,帮助从主wiki.js中解耦出这个函数:

function uploadMessages(pendingMessages, messageResponseHandler) {

  ...

}

调用代码几乎和以前一样:

uploadMessages(pendingMessages, onMessagesLoaded);

到目前为止,我们获得了一点模块化,但是对提升性能仍然没有帮助,因为这两个文件必须在一开始就被下载。

Script Tag Creation

使用On-Demand JavaScript,upload.js不再于启动时就需要,因此,它的引用不再出现于初始的HTML,只留下主要模块,wiki.js

<script type="text/javascript" src="wiki.js"></script>

增加一个新函数用于下载脚本。为避免多次下载,一个保护条件检查uploadMessages是否已经存在,如果存在,立即返回。遵循Script Tag Creation技术,它为文件头部添加脚本元素(使用upload.js的URL初始化),以及标准JavaScript的type属性:

function ensureUploadScriptIsLoaded() {

  if (self.uploadMessages) { // 已经存在

    return;

  }

  var head = document.getElementsByTagName("head")[0];

  script = document.createElement('script');

  script.id = 'uploadScript';

  script.type = 'text/javascript';

  script.src = "upload.js";

  head.appendChild(script);

}

调用的脚本必须调用这个函数。不过,正如前文“方案”中所述的,可能异步下载,因此,这里也必须作检查:

ensureUploadScriptIsLoaded();

if (self.uploadMessages) { // 如果还没有下载,等待下一次同步

  uploadMessages(pendingMessages, onMessagesLoaded);

  ....

如果脚本被浏览器异步下载,这个测试实际上在第一次将失败,因为在脚本被下载之前,下载函数将返回。但是在这个特定的应用中,实际上无关紧要——不论如何,整个同步程序每隔5秒被执行一次。如果上传程序还不存在,5秒之内,它应该会存在,这一点符合我们的目的。

Service Eval

Script Tag Creation代码改用Service Eval重构代替,在此,XMLHttpRequest Call取回 upload.js并且以eval()处理。初始的脚本——wiki.js只在取回JavaScript的实现上有所不同。文字响应被传送给eval:

function ensureUploadScriptIsLoaded() {

  if (self.uploadMessages) { // 已存在

    return;

  }

  ajaxCaller.getPlainText("upload.js", function(jsText) { eval(jsText); });

}

JavaScript响应也必须改变。如果它只是定义一个类似之前的全局函数,即:

function uploadMessages(pendingMessages, messageResponseHandler) {

  ...

}

当评估(evaluation)完成时,函数接着结束。相反地,我们能够通过声明函数如下,达到相同效果(这将把函数关联到当前的窗口上,因此在脚本被评估(evaluation)之后,它将继续存活)。

uploadMessages = function(pendingMessages, messageResponseHandler) {

  ...

}

相关模式

HTML Message

这个模式的Behavior Message用法是HTML Message(第9章)的对等物,并遵循一种类似以服务器为中心的哲学,在那里,服务器动态控制浏览器活动。

Predictive Fetch

当你预期JavaScript很快将需要用到时,通过先行下载它,将Predictive Fetch(第13章)应用到On-Demand JavaScript。

Multi-Stage Download

这个模式的Lazy Loading应用类似于Multi-Stage Download(多阶段下载),它也是递延下载。Multi-Stage Download的重点是下载的语义及显示内容,而不是下载JavaScript。另外,这个模式更多的是关于根据预定计划的顺序下载,而不是根据需要下载。

更多信息

l   Thomas Brattli提供一份教学课程,“Dynamic Data Using the DOM and Remote Scripting”(http://www.dhtmlcentral.com/tutorials/ tutorials.asp?id=11)。

posted on 2008-09-29 12:04  啊凡  阅读(433)  评论(1编辑  收藏  举报