客户端javascript涵盖在本系列的第二部分第10章,主要讲解javascript是如何在web浏览器中实现的,这些章节介绍了大量的脚本宿主对象,这些对象可以表示浏览器窗口、文档树的内容。这些章节同样涵盖重要的web应用所需要的网络编程API、本地存储和检索数据、画图等。主要包含内容有以下章节:
web浏览器中的javascript / window对象 / 脚本化文档 / 脚本化css / 事件处理 / 校本化http / jQuery类库 / 客户端存储 / 多媒体和图形编程 / HTML5API
本书的第一部分介绍了javascript语言核心,第二部分开始转向web浏览器中的javascript讨论。迄今为止,我们的大部分例子是合法的javascript代码,带是没有特定的上下文,也就是说它们运行在不明的环境总。本章节提供了一个可以允许javascript上下文。
在开始讨论javascript之前,考虑下web浏览器是怎么呈现页面的,其静态页面称为文档(document),相对于文档来说,洽谈web页面甘江更像是应用。如果需要的话,这些页面可以载入新的新想,因此看起来图形化,而非文本化,并且它们可以进行离线操作,以及保存数据倒本地,以便再次访问进行状态恢复。此外,还有其它web页面处于文档和应用的中间,结合了两者的特性。
本章以客户端javascript概述开始,包括一个简单的例子,以及对javascript如何在web文档和web应用中角色讨论。概述内容还介绍了那些内容会在后续章节中提到,接下来会详细解释javascript代码在html文档中如何嵌入,然后介绍兼容性、可访问性和安全性等问题。
1.客户端javascript
window对象是所有客户端javascript特性和API的主要接入点。它表示web浏览器的一个窗口或窗体,并且可以用window表示来引用它。window对象定义了一些属性,比如:Location对象的location属性,Location对象指定当前显示在窗口的URL,并允许脚本往窗口里载入新的URL。
//设置location属性,跳转至新的页面 window.location.href = "http://www.baidu.com" //location.href = "http://www.baidu.com"
window对象还定义了一些方法,比如alert(),可以弹出一个对话框用来显示一些信息,还有setTimeout(),可以注册一个函数,在给定的一些时间内触发一个回调
setTimeout(function(){alert("5秒跳转"),1000}); setTimeout(function(){location.href = "http://www.baidu.com"},5000)
注意上面的代码并没有显式的使用window 属性。在客户端javascript中,window对象 也是全局对象。这意味着window对象处于作用域链顶部,它的属性和方法实际上是全局变量和全局函数。window对象有一个自身引用的属性,叫做window。如果需要引用窗口对象本身,引用引用这个属性。但是如果只想引用全局窗口对象的属性,通常不需要用到window。
window对象还定义了很多其他重要的属性、方法和构造函数,参见12章查看完整细节。
window对象中一个重要的属性是document,它引用Document对象,后者表示显示在窗口中的文档。Document有一些重要的方法,比如getElementByid(),可以基于元素的id返回单一的文档元素,(表示html标签的一对开始/结束标记,以及它们之间所有的内容):
//查找id="timestamp"元素 var timestamp = document.getElementById("timestamp")
getElementById()返回的Element对象有其它重要的属性和方法,比如允许脚本获取它的内容,设置属性值等:
//如果元素为空,往里边插入的哪个区日期和事件 if (timestamp.firstChild == null) timestamp.appendChild(document.createTextNode(new Date().toString()));
查询、遍历和修改文档将在12章做介绍。
每个Element对象都有style和className属性,允许脚本指定元素css样式,或修改元素上的css类名,设置这些css相关的属性会改变文档元素的呈现:
timestamp.style.backgroundColor="red";
指定样式className
//或者修改类,让样式指定具体内容 timestamp.className = "heightlight TopDarkNav"
14章会讲解style和className属性和其它css编程技术
window、Document和Element对象上另一个重要的属性集合是事件处理程序相关的属性。可以在脚本中为止绑定一个函数,这个函数会在某个事件发生时以异步的方式调用。
事件处理程序可以让javascript代码修改窗口,文档和组成文档的元素的行为。事件处理程序是以单词"on"开始的,用法如下:
//当用户单击元素时,更新它的内容 timestamp.onclick = function(){this.innerHTML = new Date().toDateString();}
window对象的onload对象处理程序是最重要的事件处理程序之一。当显示在文档内的内容文档且可以操作时触发。javascript代码通常封装在onload事件处理程序里。15章会详细讲述事件。
下面的例子是onload处理程序的演示,并展示了客户端javascript的实例代码,包括查询文档元素,修改css类和定义事件处理程序。注意代码里的函数是在另一个函数里定义的。因为事件处理程序的广泛使用,是的嵌套函数在客户端javascript中非常普遍。
<head> <meta charset="utf-8"> <style type="text/css"> .reveal * { display: none; } .reveal *.handle { display: block; } </style> <script type="text/javascript"> window.onload = function() { //所有页面逻辑加载完毕后启动 var element = document.getElementsByClassName("reveal"); for (var i = 0; i < element.length; i++) { //对每个元素进行遍历 var elt = element[i]; //找到容器中的“handle”元素 var title = elt.getElementsByClassName("handle")[0]; addRevealHandler(title, elt); console.log(elt.className); } function addRevealHandler(title, elt) { title.onclick = function() { if (elt.className == "reveal") elt.className = "revealed"; else if (elt.className == "revealed") elt.className = "reveal"; } } }; </script> </head> <body> <div class="reveal"> <h1 class="handle">文字title(Click Here)</h1> <p>文字内容</p> </div> </body>
在本章的概要介绍到了,一些web页面感觉上像文档,而另一些则像应用。接下来的两节探讨javascript在两种页面类型里是如何使用的
i.web文档里的javascript
javascript程序可以通过Document对象和它包含的Element对象遍历和管理文档内容。它可以通过操作css样式和类,修改文章内容的呈现。并且可以通过注册事件的处理辰星来定义文档的元素行为。内容、呈现和行为的组合叫动态HTML或者DHTML,会在13-17章里介绍到
javascript可以增强用户的体验:比如以下方式:
- 创建动画和其它视觉效果,巧妙地引导和帮助用户进行页面导航。
- 对表格进行分组,让用户更容易找到所需
- 隐藏某些内容,当用户“深入”到内容时,逐渐展示详细信息。
ii.web应用里的javascript
在web文档库使用的javascript DHTML特性在web应用里都会用到,对于web应用来说,除了内容、呈现和操作api之外,还依赖web浏览器环境提供更基础的服务。‘
要真正的了解web应用,需要先认识web浏览器已经有很好的发展了,现在不仅仅是显示温度的角色了,而已经变成简易的操作系统。操作系统定义很多底层的API、提供绘制图形,保存文件等功能。web浏览器也定义了底层API(16章)、保存数据(18章),和绘制图形(19章)。
谨记web浏览器是简单的操作系统的概念,这样就可以把web定义问用javascript访问更多浏览器提供的高级服务(比如网络、图形和数据存储)的web页面。高级服务里最著名的是XMLHTTPRequest,可以对HTTP请求编程来启动网络。web里是固体这个从服务器获取新信息,而不用从新载入页面。类似这样的web应用通常胶Ajax应用,Ajax构成了web2.0的脊梁。XMLHTTPRequest会在16章详细介绍。
HTML5标准和相关标准为web应用定义了很多其他重要的API,如地理位置信息,历史管理和后台线程。使用这些API后,会开启一场web应用的功能革命。这些内容在20章会介绍。
当然,javascript在web里的应用比在文档里显得更重要。javascript增强了web文档。但是良好的设计的文档需要在禁用了javascript后还能继续工作。web应用的本质就是javascript程序。
2.在html嵌入javascript
- 内联 <script></script>之间
- 放置在 <script>标签的src属性指定的文件中
- html事件处理程序中,例如onclick和onmouseover这样的HTML属性值指定。
- 放在一个URL里,这个URL使用特殊的"javascript:"协议
接下来小节会介绍4中javascript嵌套技术。但是,值得注意的是,html事情处理程序属性和javascript:url这两种方式现代的javascript代码里已经很少使用。内联脚本(没有src)也比以前用的更少了。主张内容(html)和行为(javascript)代码应该尽量保持分离。根据这个编程哲学,javascript最好通过<script>的src属性嵌入到html文档里
i.<script>元素
<script> //javascript代码 </script>
在XHTML中<script>标签的内容被当做其它内容一样对待。如果javascript代码包含了"<"或"&"字符,那么这些字符会被解释成xml标记,因此,如果使用XHTML,最好把所有的javascript代码放到一个CDATA部分里
<script><![CDATA[ //这里是javascript代码 ]]></script>
下面的例子展示了一个简单的javascript程序。注释解释了这个辰星是做什么的。主要演示javascript代码以及css样式表是如何嵌入到html文件里。
<script type="text/javascript"> //定义一个函数显式当前时间 function displayTime() { var elt = document.getElementById("clock"); var now = new Date(); elt.innerHTML = now.toLocaleTimeString(); //显式它 setTimeout(displayTime, 1000); } window.onload = displayTime; </script> <body> <div id="clock"> </div> </body>
ii.外部文件中的脚本
外部文件中的脚本它的用法如下:
<script src="unit.js"></script>
javascript文件一般以.js结尾,它包含纯粹的javascript代码
使用src属性时,<script></script>标签之间的任何内容都会忽略掉,如果需要,可以在此处不错说明文档和版权信息,但要注意,如果有任何非空格或javascript的注释文本出现在此,html5校验器会报错。
我们通常看到以下代码
<script src="unit.js"> config = {...}; </script>
这段戴拿定义了一些配置项,有unit.js来读取,这是一种将页面传入库文件的方法,在javascript库中的开发中十分常见,其中<script></script>之间是一段纯文本,在unit.js读取时这段文本然后执行一次,浏览器不会自动执行script>中的代码。
下面是一些使用src属性的javascript的优点
- 可以把大块的javascript代码从HTML文件中删除,这有助于保持内容和行为的分离
- 如果多个javascript共有相同的javascript代码,用src属性的方式可以让你只管理一份代码,而不用再代码改变时而编辑每个HTML文件。
- 如果一个javascript代码文件是多个页面共享,那么只需下载一次,通过使用它的第一个页面,随后是页面可以从浏览器缓存检索它。
- 由于src是任意的url,因此来自一个web服务器的javascript程序或web页面可以使用另一个web服务器输出的代码。很多互联网广告依赖于此。
- 从其他网站载入脚本的能力,可以让我们更好的利用缓存(CDN方式)。
我们通常看到以下代码
从服务器之外的服务器里载入脚本有重要的安全隐患,6.ii节介绍的同源安全策略会阻止一个域文档的javascript和另外一个域的内容进行交互。但是,要注意和脚本本身的来源并没有关系,而是和脚本嵌入的文档来源有关系。因此,同源策略和并不适合用在以下的情况,即便代码和文档有不同的来源,javascript代码也可以和它嵌入的文档进行交互,当在页面中用src脚本时,就给了脚本作者(这段脚本域的网站管理员)完全控制web页面的权限。
iii.脚本类型
javascript是web元素脚本语言,而在默认的情况下,假定<script></script>包含或引用javascript代码,如果使用不标准脚本语言,就必须用type指定MIME类型:
<script type="text/vbscript"> //这里是VBScript代码 </script>
type的默认属性是"text/javascript",如果需要,可以显式的指定此类型,但完全没必要。老的浏览器在标记上用language代替type标记,这样的情况现在偶尔也看到。language属性已经废除,不应该再使用了
当web浏览器遇到<script></script>元素,并且当这个元素里包含其值不被浏览器识别的type属性时,它会解析这个元素但不会尝试显示或者执行它的内容。这意味着可以使用<script>元素来嵌入任意文本数据倒文档里,只要用type属性声明一个不可执行的类型。要获取数据,可以属于script元素(13章会介绍如果获取这些元素)HTMLElement对象的text属性。但是,要注意这些数据嵌入只对内联脚本生效(steven souder著名的Controljs就是利用这个特性来控制代码的执行。)如果src属性和一个未知的类型。这个脚本会被忽略。并且不会从url下载任何内容。
iiii.HTML中的事件处理程序
当脚本所在的HTML文件被载入浏览器时,这个脚本里的javascript代码只会执行一次。为了可交互,javascript程序必须定义事件处理程序,web浏览器先注册javascript函数,并且在之后调用它作为事件的相应(比如用户输入)。正如本章开始例子展示的,javascript代码可以通过把函数赋值给Element对象的属性(比如onclick或onmoseover)来注册事件处理程序。(还有其它注册事件程序的方法,参见15章),这个Element对象表示文档里的一个HTML元素。
类似onclick的事情处理程序属性,用相同的名字对应到HTML属性,并且还可以通过将javascript代码放置在HTML属性里来定义事件处理程序。例如:要定义用户切换表单中的复选框调用的事件处理程序,可以作为表示复选框的html元素的属性指定处理程序的代码:
<input type="checkbox" name="options" value="gifwrap" onchange="order.options.giftwarp = this.checked" />
这里的onchangge属性比较有意思,这个属性值里的javascript代码会在用户选择或取消选择复选框时执行。
HTML中定义的事件处理程序的属性可以包含任意挑javascript语句,相互之间用逗号分隔。这些语句组成一个函数体,然后这个函数称为对于事件处理程序属性的值。(15.2.ii会详细介绍HTML属性文本定义到javascript函数的转换。)但是,通常HTML事件处理程序的属性有类似上面的简单赋值或定义在其它地方的简单函数调用组成。这样可以保持大部分实际的javascript代码在脚本里,而不用把javascript和html混在一起。实际上,很多web开发者认为使用HTML事件处理程序是不好的习惯,他们更喜欢保持内容和行为的分离。
iiiii.URL中的javascript
在URL后面跟一个javascript:协议限定符,是另外一种javascript代码到客户端的方式。这种特殊的协议类型指定URL内容为任意字符串,这个字符串会被javascript解释器运行的javascript代码。它被当做单独的一行代码对待,这意味着语句之间必须用分号隔开,而//注释必须用/**/注释代替。javascript:URL能是不“资源”是转换成字符串的执行代码的返回值。如果代码返回undefiend,那么这个资源是没有内容的。
javascript:url可以用在可以使用常规URL的任意地方:比如<a>标记的href属性,<form>的action属性,甚至window.open()方法的参数。超链接里的javascript url可以是这样的。
<a href="javascript:new Date().toLocaleTimeString()">what time it is</a>
部分浏览器(比如firefox)会执行URL里的代码,并使返回的字符串作为待显新文章的内容。就像单击一个超链接。浏览器会擦除当前文档并显示新文档。其它浏览器(比如chrome和safari)不允许URL像上面一样覆盖当前文档。但是,这样的url还是支持的
<a href="javascript:alert(new Date().toLocaleTimeString())">what time it is</a>//检查时间,而不覆盖整个文档
部分浏览器载入这种类型的URL时,它会执行javascript代码,但是由于没有返回值(alert()方法返回undefined),作为新的文档显示内容。类似firefox的浏览器并不会替换当前显示的文档。要确保javascript:void不会替换当前的文档,可以用void操作符强制函数调用或给表达式赋予undefined值。
<a href="javascript:void window.open(‘http://www.baidu.com‘)">baidu</a>
和html事件处理程序一样,javascript:url也是web早期的产物。通常避免在现代的网页中使用。但javascript:url在html文档之外确实有着重要的角色。如果要测试一段短javascript代码,那么可以在浏览器地址栏里输入javascript:URL,下面会介绍javascript:URL另外一个正统且强大的用法:浏览器书签。
3.javascript里的程序的执行
客户端javascript没有严格的定义,我们可以说javascript程序是由web页面中所包含的所有的javascript代码。所有的代码共用一个全局window对象。这意味着它们可以看到相同的Document对象,可以共享全局变量或函数,那么这个变量或函数会在脚本执行之后对任意javascript可见。
如果一个页面包含嵌入窗体(通常使用<iframe>),嵌入的javascript和被嵌入的javascript代码会有不同的全局对象,它可以看做一个单独的javascript程序。但是,要记住,没有严格关于javascript程序范围的定义。如果外边和里边的文档来自于同一个服务器,那么两个文档中的代码就可以进行交互,并且如果你愿意,就可以把他们当做同一个程序的两个相互作用的部分。12.8.iii会详细介绍window对象以及不同窗体之间的交互。
javascript程序的执行有两个阶段。在第一个阶段,载入文档内容,并执行<script>元素的代码(包括内陆脚本和外部脚本)。脚本通常按照它们在文档中出现的顺序执行。所有脚本里的代码都是从上往下,按照它在条件、循环以及其他控制语句中出现的顺序执行。
第二个阶段,当文档载入,所有脚本执行完成后,javascript就进入第二个阶段。这个阶段是异步的,而且是由事件驱动的。在时间驱动阶段,web浏览器调用处理程序函数(由第一阶段里执行的脚本指定的html事件处理程序,或之前调用的时间处理程序来定义),来响应时间异步事件的发生。调用事件处理程序通常是响应用户输入(如鼠标单击,键盘按下)。但是还可以由网络活动、运行时间、或者javascript代码中的错误来触发。15章会详细介绍事件和事件处理程序。本章2.ii节也会进行更多的讨论。注意,嵌入在web页面里的javascript:URL也可以当做一种事件处理的程序,直到用户单击或者提交表单之后才会有效果。
事件在驱动阶段第一个发生的事件是load事件,表示文档已经完全载入,并可以操作。javascript经常通过这个事件来触发或发送消息。
我们会经常看到一些定义函数的脚本程序,除了定义一个onload事件处理函数外不做其它操作,这个函数会在脚本事件驱动阶段开始时被load触发。正是这个onload事件会对文档进行操作,并做程序想做的任何事。javascript程序的载入是短暂的,通常持续1到2秒,在文档载入完成之后,事件驱动阶段就会一直持续下去。因为这个阶段是异步和事件驱动的,所以可能有长时间处于不活动状态。没有javascript被执行,被用户或网络事件触发的活动打断。本章3.iiii javascript执行的两个阶段。
核心javascript和客户端javascript都有一个单线程执行模型。脚本和事件处理程序(无论如何)在同一个时间里只能执行一个,并没有并发性。这保持了javascript编程的简单性。本章3.iii会做介绍。
i.同步、异步 或延迟的脚本
javascript第一次添加到web浏览器时,还没有api可以用来遍历和操作文档的结构内容,当文档还在载入时,javascript唯一方法就是快速生成内容。它使用document.write()完成上述内容,下面就是1996最先进的javascript的代码的样子:
function factorials(n){ //用来计算阶乘的函数 if(n<=1) return n; else return n*factorials(n-1); } document.write("<table>"); document.write("<tr><th>n</th><th>n!</th></tr>"); //输出表头 for(var i = 1; i<10;i++){ //输出10行 document.write("<tr><td>"+ i +"</td><td>" + factorials(i) +"</td></tr>"); } document.write("</table>");
当脚本把文本传递给document.write()时,这个文本被添加到文档输入流中,html解析器会在当前位置创建一个文本节点,将文本插入到这个文本节点后面。并不推荐使用document.write(),但在某些场景下有很重要的用途(13.10.ii节)。当HTML解析器遇到<script>元素时,它默认必须先执行脚本,然后再恢复文档的解析和渲染。这对于内联脚本没有什么问题,但如果在javascript具有src属性指定外部属性指定外部条件,这意味着脚本后面的文档部分在下载和执行脚本之前,都不会出现在浏览器中(所谓的“不出现在浏览器中”是指文档的文本内容已经载入,但是并未被浏览器引擎解析为DOM树,而DOM树的生成是受javascript代码“阻塞”页面UI的渲染)。
脚本的的执行只在默认的情况下是同步和阻塞的。<script>标签可以有defer和async属性,这可以改变脚本的执行方式。这些都是布尔属性,没有值;只需要出现在<script>标签里即可。HTML5说这些属性只在Src属性联合使用时才能有作用,但有些浏览器还支持内联的脚本。
<script defer src="1.js"></script> <script async src="1.js"></script>
defer和async属性都像在告诉浏览器链接进来的脚本不会使用document.write(),也不会生成文档内容,因此浏览器可以在下载脚本时继续解析和渲染文档。defer属性是使得的浏览器延迟脚本的执行,直到文本的载入和解析完成,并可以操作。async属性使得浏览器可以尽快的执行脚本,而不用在下载脚本时阻塞文档解析。如果<script>标签同时有两个属性,同时支持两者的浏览器会遵循async属性并忽略defer属性。
注意,延迟的脚本会按照它们在文档里的出现顺序执行。而异步脚本在它们载入后执行,这意味着它们可能会无序执行。
在不支持async的属性的浏览器里,通过动态的创建<script>元素并把它插入到文档中,来实现脚本的异步载入和执行。下面的例子中loadasync()函数完成了这个工作。13会介绍它使用的技术。
/*异步载入并执行脚本*/ //异步载入并执行一个指定url中的脚本 function loadasync(url) { var head = document.getElementsByTagName("head")[0]; //找到<head>元素 var s = document.createElement("script"); //创建一个<script>元素 s.src = url; //设置其src属性 head.appendChild(s); //将预算插入head标签中 } loadasync(11.js); loadasync(12.js); loadasync(13.js);
注意这个loadasync()函数会动态的载入脚本-脚本载入到文档中,成为正在执行的javascript程序的一部分,既不是通过web页面内联包含,也不算来自web页面的静态引用。
ii.事件驱动的javascript
在上面的factorials()函数展示了javascript程序是同步载入的程序:在页面载入时开始执行,生成一些输出,然后结束。这种类型的程序在今天已经不常见了。反之,我们通过注册时间处理程序来写程序。之后在注册的事件发生时异步调用这些函数。例如,想要为常用操作启用键盘快捷键的web应用会为键盘事件处理程序。甚至非交互的程序也使用事件。假如想要写一个分析文档结构并自动生成文档内容的表格程序。程序不需要用户输入事件的事件处理程序,但它还是会注册onload事件处理程序。这样就知道文档在什么时候载入完成并可以生成内容表格了。
事件和事件处理是15章的主题,但是这一节会提供一个快速概述。事件都有名字,比如click、change、load、mouseover、keypress、readystatechange,指示发生的事件的通用类型。事件还有目标,它是一个对象,并且事件就是在它上面发生的。当我们谈论事件时,必须指定事件的类型(名字)和目标,比如一个单击事件发生在HTMLbutton对象上,或者一个readystatechange事件发生在XMLHttpRequest对象上。
如果想要呈现响应一个事件,写一个函数,叫做“事件处理程序”、“事件监听器”、“回调”。然后注册这个函数,这样它就会在事件发生时调用它。正如前面提到的,这可以通过HTML属性来完成,不鼓励把javascript程序和HTML内容混淆在一起。反之,注册事件处理程序最简单的方法就是把javascript函数赋值给目标对象属性,类似这样写代码:
window.onload = function(){...}; document.getElementById("xx").onclick = function(){...}; function handleResponse(){...} request.onreadystatechange = handleResponse;
注意,按照约定事件处理程序的属性的名字是以“on”开始,后面跟着事件的名字。还要注意在上面的人和代码里没有函数调用:只是把函数本身赋值给这些属性。
浏览器会在这些事件发生时调用,用事件进行异步编程经常会涉及到嵌套函数,也经常要在函数的函数里定义函数。
对于大部分浏览器事件来说,会把一个对象传递给事件处理程序作为参数,那个对象的属性提供了事件的详细信息。比如传递给单击事件的对象,会有一个属性说明那个按钮被单击。(在IE里,这些信息存储在全局event对象里,而不是传递给处理程序的函数。)事件处理程序的返回值有时用指定函数是否处理了事件。以及阻止浏览器执行它默认会进行的各种操作。
有些事件的目标是文档元素,它们经常往上传递给文档树,这个过程叫“冒泡”。例如,如果用户在<button>元素上单击鼠标,单击事件就会在按钮上触发。如果注册在按钮上的函数没有处理(并且冒泡停止)该事件。事件冒泡到按钮嵌套的容器元素。这样,任何注册在元素上的单击事件都会调用。
如果需要为一个事件注册多个事件处理程序函数,或者如果想要写一个可以安全注册事件处理程序的代码模块,就算另一个模块已经为相同的事件注册了一个处理程序,也需要用到另一种事件处理程序注册技术。大部分可以成为事件目标对象都有一个叫做addEventListaner()方法,允许注册多个监听器:
window.addEventListener("load",function(){...},false); request.addEventListener("readystatechange",function(){...},false);
注意,这个函数的第一个参数是事件的名称。虽然addEventListener()已经标准化超过了10年,而微软目前只在IE9里实现了它。在IE8之前的浏览器中,必须使用一个相似的方法,叫做attachEvent():
window.attachEvent("onload",function(){...});
在第15章会看到更多关于addEventListener()和attachEvent()内容。
客户端javascript还使用异步通知类型,这些类型往往不是事件。如果设置window对象的onerror属性为一个函数,会发生(参加12.6节)javascript错误(或者其它未捕获的异常)时调用函数。还有setTimeout()和setInterval()函数(这些是window对象方法,因此是客户端javascript的全局函数)会在指定的一段时间之后出发指定函数的调用。传递给setTimeout()的函数和真实时间处理程序的注册不同,它们通常叫做“回调逻辑”而不是“处理程序”,但它们和时间处理程序一样,也是异步的。参加12.1获得更多关于setTimeout()和setInterval()函数的信息。
下面的例子演示了setTimeout()、addEventlistenter()和attachEvent()、定义一个onload()函数注册在文档载入完成时执行的函数。
/*当文档载入时调用一个函数*/ //注册函数f,当文档载入时执行这个函数f //如果文档已经载入完成,尽快以异步的方式执行它 function onLoad(f) { if (onLoad.loaded) //如果文档已经载入完成 window.setTimeout(f, 0); //将f放入异步对了,并尽快执行它 else if (window.addEventListener) //注册事件的标准方法 window.addEventListener("load", f, false); else if (window.attachEvent) window.attachEvent("onload", f); } //给onLoad设置一个标志,用来指定文档是否已经载入完成 onLoad.loaded = false; //注册一个函数,当文档载入完成时使用这个标志 onLoad(function() {onLoad.loaded = true;});
iii.客户端javascript线程模型
javascript语言核心并不包含任何线程机制,并且客户端javascript传统上也没有定义任何线程机制。html5定义了一种作为后台线程的"webworker",但是客户端javascript还是像严格的单线程一样工作。
单线程执行是为了让编程更加简单。编写代码时可以确保两个事件处理程序不会同一时刻运行。操作文档内容时不必担心有其它线程试图修改文档。并且永远不需要担心javascript编写时的锁死,死锁和竟态条件。
单线程执行意味这浏览器必须在脚本和事件语句程序执行时候停止响应用户输入。这为javascript程序员带来了负担。这意味这javascript脚本和事件处理程序不能运行太长事件。如果一个脚本执行计算密集的任务,它将会给文档载入带来延迟。如果事件程序执行计算密集任务,浏览器可能变得无法响应,可能导致用户认为浏览器奔溃了。
如果程序执行的不太多计算导致明显的延迟,在文档没有完全载入前,可以告知用户正在运行计算并且浏览器没有挂起。如果有可能可以将其分解为离散子任务。可以使用setTimeout()和setInterval()在后台运行子任务。
HTML5定义了一种并发控制方式,“web worker”,它是一个用来执行计算密集任务而不冻结用户界面的后台线程。运行在web worker线程里的代码不能访问文档里的内容,不能和主线程或其它worker共享状态,只可以和主线程和其它worker通过异步事件进行通信,所以主线程不能检测并非行,而且web worker不能修改javascript程序基础单线程执行模型。20章4节会有更多相关内容。
iiii.客户端javascript时间线。
我们已经看到javascript程序从脚本执行阶段开始,然后切换到事件处理阶段。本节会详细地解释javascript程序执行的时间线:
- web浏览器创建Document对象,并且开始解析web页面,解析HTML元素和它们的文本内容后添加Element对象和Text节点到文档中,在这个阶段document.readystate的属性值是"loading".
- 当HTML解析器遇到async和defer属性的<script>元素时,它把这些元素添加到文档中,然后执行行内或者外部脚本。这些脚本会同步执行,并且在脚本下载(如果需要)和执行时解析器会暂停。这样脚本就可以用document.write()来把文本插入到数据流中。解析器恢复时这些文本就会成为文档的一部分。同步脚本继承简单定义函数和注册后面使用的注册事件处理程序,但它们可以遍历和操作文档树,因为他们执行时已经存在了。这样,同步脚本可以看到它自己的<script>元素和它们之前的文档内容。
- 当解析器遇到async属性的<script>元素时,它开始下载脚本文本,并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器没有停下来等它下载。异步脚本禁止document.write()方法,它们可以看到自己的<script>元素和它之前的所有文档元素,并且可能或直接不放我其它文档内容。
- 当文档解析完成,document.readyState属性变成“interactive”。
- 所有defer属性脚本,会按照文档里的出现顺序执行。异步脚本可能也是会在这个时间执行,延迟脚本能访问完整的文档树,禁止使用document.write()方法。
- 浏览器在Document对象上触发DOMContentLoaded事件。这标志着程序执行从同步脚本阶段转到了异步事件驱动阶段。但要注意,这时可能还有异步脚本没有执行完成。
- 这时,文档已经完全吉祥完成,但是浏览器可能还等待其它内容的载入,如图片。当所有的内容完成载入时,document.readyState属性变成为"Complete"。浏览器从window对象开始触发load事件
- 从此刻起,会调用异步事件,以异步响应用户输入时间、网络事件、计时器过期等
这是一条理想的时间线(网友自己理解版本)
1、创建document对象,开始解析web页面。创建HTMLHtmlElement对象,添加到document中。 创建HTMLHeadElement添加到HTMLHtmlElement中等等,总之遇到不同的标签创建不同的element、node等等,这个阶段document.readyState = ‘loading‘。 2、遇到link外部css,创建线程加载,并继续解析文档。 3、遇到script外部js,并且没有设置async、defer,浏览器创建线程加载,并阻塞,等待js加载完成并执行该脚本,然后继续解析文档。 4、遇到script外部js,并且设置有async、defter,浏览器创建线程加载,并继续解析文档。 对于async属性的脚本,脚本加载完成后立即执行。 可以采用document.createElement(‘script‘)的方式动态插入script元素来模拟async属性,实现脚本异步加载和执行。 5、遇到img等,浏览器创建线程加载,并继续解析文档。 6、当文档解析完成,document.readyState = ‘interactive‘。 7、文档解析完成后,所有设置有defer的脚本会按照顺序执行。(注意与async的不同) 8、document对象触发DOMContentLoaded事件,这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段。 9、当所有async的脚本加载完成并执行后、img等加载完成后,document.readyState = ‘complete‘,window对象触发load事件。 10、从此,以异步响应方式处理用户输入、网络事件等。 注:document的每一次readyState属性变化,都会触发readystatechange事件。
但是所有的浏览器都没有支持它的全部细节。所有的浏览器普遍支持load事件,都会触发它。它是决定文档完全载入并可操作的最通用技术。
DOMcontentLoaded事件在load事件之前触发,当前所有的浏览器都支持这个事件,除了IE之外,document.readyState属性已经被大部分浏览器实现。但是这个属性在浏览器之间还存在差别。async属性还不通用,使用上文中的loadasync()函数动态载入脚本的能力能让程序的执行脚本载入阶段和事件驱动之间界限更模糊。
这条时间线并没有指定什么时候文档开始对用户可见或什么时候web浏览器必须开始响应用户输入事件。这些都是实现细节。对于很长的文档或非常慢的网络连接。web浏览器理论上会先渲染一部分文档。并且在脚本执行之前,就能允许用户和页面产生一些交互。这种情况下,用户输入事件可能在程序执行的事件驱动开始之前触发。
(本文更新未完,欢迎大家关注上章节内容:第十章:Javascript子集和扩展)
以下将更新:
4.兼容性和互用性
5.可访问性
6.安全性
7.客户端框架