第十一章:WEB浏览器中的javascript

优质
小牛编辑
185浏览
2023-12-01

客户端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程序执行的时间线

  1. web浏览器创建Document对象,并且开始解析web页面,解析HTML元素和它们的文本内容后添加Element对象和Text节点到文档中,在这个阶段document.readystate的属性值是"loading".
  2. 当HTML解析器遇到async和defer属性的<script>元素时,它把这些元素添加到文档中,然后执行行内或者外部脚本。这些脚本会同步执行,并且在脚本下载(如果需要)和执行时解析器会暂停。这样脚本就可以用document.write()来把文本插入到数据流中。解析器恢复时这些文本就会成为文档的一部分。同步脚本继承简单定义函数和注册后面使用的注册事件处理程序,但它们可以遍历和操作文档树,因为他们执行时已经存在了。这样,同步脚本可以看到它自己的<script>元素和它们之前的文档内容。
  3. 当解析器遇到async属性的<script>元素时,它开始下载脚本文本,并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器没有停下来等它下载。异步脚本禁止document.write()方法,它们可以看到自己的<script>元素和它之前的所有文档元素,并且可能或直接不放我其它文档内容。
  4. 当文档解析完成,document.readyState属性变成“interactive”。
  5. 所有defer属性脚本,会按照文档里的出现顺序执行。异步脚本可能也是会在这个时间执行,延迟脚本能访问完整的文档树,禁止使用document.write()方法。
  6. 浏览器在Document对象上触发DOMContentLoaded事件。这标志着程序执行从同步脚本阶段转到了异步事件驱动阶段。但要注意,这时可能还有异步脚本没有执行完成。
  7. 这时,文档已经完全吉祥完成,但是浏览器可能还等待其它内容的载入,如图片。当所有的内容完成载入时,document.readyState属性变成为"Complete"。浏览器从window对象开始触发load事件
  8. 从此刻起,会调用异步事件,以异步响应用户输入时间、网络事件、计时器过期等

这是一条理想的时间线(网友自己理解版本

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浏览器理论上会先渲染一部分文档。并且在脚本执行之前,就能允许用户和页面产生一些交互。这种情况下,用户输入事件可能在程序执行的事件驱动开始之前触发。

4.兼容性和互用性

web浏览器是web应用的操作系统,但是web是一个存在各种差异性的环境,web文档和应用在不同的操作系统(windows、Mac OS、Linux、iPhone OS、Abdroid)不同开发商(microsoft、Mozilla、Apple、Google、Opera)的不同时代的浏览器(从预览版到类似IE6这种十多年之前的浏览器)上查看和运行。能够写出一个健壮的javascript程序并能正确地运行在这么多类型的平台上,的确是一种挑战。

客户端javascript兼容性和交互性的问题可以归纳为以下三个类:

演化:

web平台一直在演变和发展当中。一个标准规范会倡导一个新的特性或API。一个新的特性看起来有用,浏览器开发商实现它,开发者开始使用这个特性。有一种情况是新的特性以及被添加到web中,新浏览器支持它但是老浏览器不支持。web开发者必先在使用老旧浏览器的大量用户和使用新式浏览器的少量用户之间做出权衡。

未实现:

举例说明:IE8不支持<canvas>元素,虽然其他浏览器已经实现它了。一个更糟糕的例子是,Microsoft决定不实现DOM Level2 Event规范(它定义了addEventListener()和相关方法。。)这个规范在12年前就已经标准化了,其他浏览器厂商已经支持了很久了
bug:
每个浏览器都有bug,并且没有按照规范准确地实现所有客户端javascriptAPI,必须研究已有浏览器中的各种bug
幸运的是,javascript语言本身是被所有浏览器厂商实现的。它不是兼容性问题的源头。在老式的浏览器ECMAScript3和新式的ECMAScript5之间转换会导致兼容性问题,因为一些浏览器会支持严格模式而其他的不支持。浏览器厂商对ECMAScript5的实现是基本相互通用的。

首先,要解决javascript的兼容性的问题是要了解问题的根源是什么。web浏览器版本更迭更快。我们可以常去这些网站查询信息:

MOzilla开发者中心
https://developer.mozilla.org

microsoft 开发者中心
https://msdn.microsoft.com/zh-cn/

apple开发者中心 safari
https://developer.apple.com/safari/tools/

Google Doctype
致力于帮助Web开发人员,目前尚处于Beta阶段,其中已经包含多篇由Google顶级开发人员撰写的关于网络安全、网页性能、JavaScript DOM处理、CSS技巧等方面的内容,可以作为Web开发者的参考资料库

http://code.google.com/doctype/

http://a.deveria.com/caniuse/
这个“何时可用...”站点跟踪重要web特性实现的状态,允许根据各种标准进行过滤,并在某个特性只剩下少量已部署的浏览器不支持时推荐使用。

http://quirksmode.org/dom/

根据w3c标准列出各种浏览器的DOM兼容性表格

当然,意识到浏览器之间的兼容性问题只是第一步。接下来,你需要解决这些不兼容性。一种策略是限制之间使用你选择支持的所有浏览器普遍支持的特性(或者很容易模拟出的特性)。之前提出的“何时可用...”这个网站就是围绕这个策略的。它列出了ie6淘汰之后才能用的新特性。

下面介绍一个消极对付客户端不兼容性问题的策略。

i.处理兼容性问题的类库

处理不兼容问题其中一种最简单的方法就是使用类库。比如考虑图像的<canvas>元素(19章主题)、IE(8)是唯一不支持这个特性的当前浏览器。在开源的"explerer canvas"项目上有一个类库,引入一个javascript文件叫excanvas.js,然后IE就会看起来像支持<canvas>元素一样。这个兼容类库是一个很纯粹的例子。

在开发过程中,可能会对某个特性编写类似的库。ECMAScript5数组方法(7.9节),比如forEach(),map()和reduce(),可以在ECMAScript3中完美模拟,并且通过把合适的类库添加到页面中,可以把这些强大有用 的方法当做所有浏览器平台基线的部分。

但是有时候,不可能完全地(或有效地)在一个不支持某个特性的浏览器上实现一个特性,就像一件提到的,IE是唯一没有实现标准事件处理API的浏览器,包括注册事件处理程序addEventListener()方法。iE的attachEvent()不像addEventListener()一样强大,并且在IE提供的继承上透明地实现整个标准并非可行。反之,开发者要有一个折中的处理方法,通常叫addEvent,它可以用attachEvent()不像addEventListener()来方便实现绑定事件功能。然后,它们在所有的代码里用addEvent()来代替addEventListener()和attachEvent()。

在实际开发工作中,今天不少web开发者在它们的页面上使用看客户端javascript框架。比如jQuery(17章)。使这些框架必不可少的一个重要功能是:它们定义了新的客户端API并兼容所有浏览器。例如在jQuery中,事件处理程序的注册是通过叫bind()方法完成的,如果你基于jQuery做web开发,就永远不需要考虑addEventListener()和attachEvent()之间不兼容的问题。

ii.分级浏览器支持

分级浏览器支持(graded browser support)是由yahoo!率先提出的一种测试技术。分级浏览器中的A级要通过所需网页完全可用,C级浏览器只需在HTML完整的情况下可用即可,而不需要javascript和css都正常工作,C级浏览器都称作X级浏览器,这部分是全新或者罕见的浏览器。我们默认这些浏览器网页是完全可用的。但官方不会对X级浏览器的功能提供完整的支持和测试。(11年第四季度统计,yahoo!已经不再将浏览器划分为A级和C级。而是统一给出测试基准。根据这次更新 ,可以明显感觉到测试基准向移动端倾斜)

 

iii.功能测试

功能测试(capability testing)是解决不兼容性问题的一种强大的计算。如果你想试用某个功能,但又不清楚这个功能是否在所有的浏览器中都有比较好的兼容性,则需要在脚本中添加相应的代码来检测是否在浏览器中支持该功能。如果期望使用的功能还没有被当前的平台所支持,要么不该在平台中使用它,要么提供可在平台上运行的代码。
你将会在后面的各章节中一次又一次地看到功能体验测试。例如在第15章,有如下面所示的代码:

        if (element.addEventListener) { //在使用这个w3c之前首先检测它是否可用
            element.addEventListener("keydown",handler, false);
            element.addEventListener("keypress",handler, false);
        } else if (element.attachEvent) { //在使用该ie方法之前
            element.attachEvent("onkeydown", handler);
            element.attachEvent("onkeypress", handler);
        } else { //否则选择普遍支持的技术
            element.onkeydown = element.onkeypress = handler;
        }

关于功能测试最重要的是,它并不涉及浏览器开发商和浏览器版本号,代码在当前浏览器集合中有效,在浏览器后续的版本中也同样有效,而不管后续的浏览器是否实现了这些功能集合。但要注意的是:这种方法需要测试某个属性或方法是否在浏览器中已经定义了,出发该属性或方法完全可用,如果Microsoft要定义一个addEventListener()方法,但Microsoft只是实现了一部分W3c规范,在调用addEventListener()之前这将会给使用特性的代码带来很多麻烦。

iiii.怪异模式和标准模式

Microsoft在发布IE6的时候,增加了IE5里没有的很多css标准特性。但为了确保为了web内容的向后兼容性,它定义了两种不同的渲染摩丝。在“标准模式”或“css兼容模式”中,浏览器要遵循css标准,在“怪异模式”中,浏览器表现的和IE4和IE5中的怪异非标准模式一样,渲染模式的选择依赖于html文件顶部的DOCTYPE声明,在IE6中打开没有DOCtype的页面,会按照标准模式进行渲染。定义了html5 <!DOCTYPE HTML>的页面在所有现代浏览器都会按照标准模式渲染。

怪异模式和标准模式之间的差别经历了很长的发展历程,现在新版的ie都支持标准模式。其它主流的浏览器都支持标准模式。这两种模式都被html5规范所认可。怪异模式和标准模式之前的差异对于html和css开发者影响最大。但客户端javascript代码则需要知道文档是以哪种模式进行渲染的。要进行这种渲染模式的特性检测,通常检测document.compatMode属性。如果其值为"CSS1Compat",则说明浏览器告知在标准模式;如果其值为"BackCompat"(或undefined,说明属性不存在),说明浏览器工作在怪异模式。所有现代的浏览器都实现了compatMode属性,并且HTML5规范对它进行了标准化。

iiiii.浏览器测试

功能测试费用适用于检测大小功能领域的支持,比如可以使用这种方法来确定浏览器是否支持w3c事件处理模式还是IE事件处理模型。另外,有时候可能需要在某种浏览器中解决个别BUG或难题,但缺没有太好的方法来检测bug的存在性。

在客户端javascript中检测浏览器的类型和版本的方法就是使用Navigator对象,我们将在12章学习它。在早期,客户端嗅探就是一种常见的客户端编程技术,现在的兼容性基本已经稳定。需要注意的是,客户端嗅探可以在服务器端完成,web服务器根据User-Agent头部可以选择地返回特定的javascript代码给客户端。

5.可访问性

web是发布信息的理想工具,而javascript程序可以增强对信息的访问。然而,javascript程序员必须小心,因为程序员写代码太过随意,以至于那行有视觉障碍或肢体困难的用户没办法正确地获取信息。

盲人用户使用一种叫做屏幕阅读器的“辅助性技术”将书面的文字转换为语言词汇。有些屏幕阅读器是识别javascript的,而并一些只能在禁用javascript时才会工作得更好。javascript是的角色应当是增加信息的表现里,而不是负责信息的呈现。javascript可访问性的一条重要元素则是,在禁用javascript解释器的浏览器中也能正常使用(或至少某种形式能正常使用)。

可访问性的另一个重要原则是,对于只使用键盘但不能(或者选择不用)鼠标的用户来说,如果编写的javascript代码依赖特定的鼠标事件。这就会给那行不使用鼠标的用户排除在外。web浏览器允许使用键盘来遍历和激活一个web页面中的UI元素。并且javascript代码也应该允许这样做。正如15章所介绍,javascript支持独立设备的事件:onfocus和onchange.以及依赖于设备的事件(onmouseover和onmousedown).为了考虑到可访问性,应该尽早可能地支持独立设备的事件。

创建可访问的web页面并非鸡毛蒜皮的小事情。关心可访问性的web应用开发应该阅读这里的文档http://www.w3.org/WAI/intro/aria。

6.安全性

web浏览器包含javascript解释器,也就是说,一旦载入web页面,就可以让任意的javascript代码在计算机里执行。很明显,这里存在着安全隐患。浏览器厂商也在不断权衡下面这两个之前的博弈:

  • 定义强大的客户端API,启用强大的WEB应用。
  • 阻止恶意代码读取或修改数据、盗取隐私、诈骗或浪费时间。

就像在其它领域中一样,javascript也在盘根错节的安全漏洞和补丁之前不断的发展变化。在web早期,浏览器添加了类似能够打开、移动、调整窗口大小、已经编辑浏览器状态栏的功能。由于广告和诈骗的滥用,浏览器作者不得不限制和禁用这些API,今天在标准化的 html5中,浏览器厂商会小心(并且开放和合作性地)掂量某个长期存在的安全限制,并且在(希望)不引入新的安全漏洞的基础上给客户端javascript添加少量的功能。

下面几节会介绍javascript的安全限制和安全问题,这些问题是每个web开发者都需要意识到的。

i.javascript不能做什么
web浏览器征对恶意代码的第一条防线就是他们不支持某些功能。例如,客户端javascript没有权限来写入或删除客户计算机上的任意文件或列出任意目录。这意味着javascript不能删除数据或植入病毒。(20.6.iiiii介绍javascript如何实现安全隐私文件系统,以及如何读取和写入文件。)

类似的客户端javascript没有任何通用的网络能力。  客户端javascript程序可以对HTTP协议编程(参见16章);并且html5有一个附属标准胶webSockes,定义一个类套接字API,用于和指定的服务器通信。但是这些API都不允许对于范围更广的网络进行直接访问。通用的Iternet客户端和服务器不能同时使用客户端javascript来写(这里的提示很重要,我们不能基于浏览器写出一个“服务器”,网络中的浏览器和浏览器之间无法直接通信。)

浏览器征对恶意代码的第二条防线就是在自己支持某些功能上添加限制,以下是一些功能限制:

  • javascript程序可以打开一个新的浏览器窗口,但是为了防止广告商滥用弹出窗口,很多浏览器限制了这一功能,只有为了响应鼠标单击这样用户触发的时候才弹出,才能使用它。
  • javascript程序可以关闭自己打开的浏览器窗口,但是不允许不经过用户允许就关闭其他窗口。
  • HTML fileupload元素的value属性是只读的。如果可以设置这个属性,脚本就能设置它为任意期望的文件名,从而导致表单上传指定文件。(比如密码文件)内容到服务器。
  • 脚本不能从不同的服务器(严格来说,这些服务器来自于不同的域,端口或协议,更详细请参照本章6.ii)载入文档的内容,除非这个就是包含脚本的文档。类似地,一个脚本不能来自不同的服务器的文档上注册事件监听。这就防止了脚本窃取其它页面的用户输入(例如组成一个密码项的键盘单击过程),这一项限制叫同源策略,下一节将详细介绍它。

注意这里并未列出所有客户端javascript的限制项,不同浏览器有不同安全策略。并可能实现的API限制。部分浏览器还可能让用户偏好决定强弱的限制。

ii.同源策略

同源策略是对javascript代码能够操作那些WEB内容的一条完整的安全限制 。当web页面使用多个<iframe>元素或者打开其它浏览器窗口的时候,这一策略通常就会发挥作用。在这种情况下,同源策略赋值管理窗口或窗体中的javascript代码以及和其它窗口或帧的交互。具体来说,脚本只能读取和所属文档来源相同的窗口和文档的属性(参见12章8节了解如何使用javascript操控多个窗口和窗体)。

文档的来源包含协议、主机,以及载入文档的URL端口。从不同的web服务器载入的文档具有不同的来源。使用http:协议载入的文档和使用https:载入的文档具有不同的来源。即使他们来自同一个服务器。

脚本本身的来源和同源策略并不相关,相关的是脚本所嵌入的文档的来源,理解这一点很重要。例如,一个来自于主机A的脚本被包含到宿主B的一个web页面中,这个脚本的来源是主机B,并且可以完整的访问包含它的文档的内容。如果脚本打开一个新窗口并载入来自B主句的另一个文档,脚本对这个文档的内容也具有完全访问权限。但是,如果脚本打开第三个窗口并载入一个来自主机C的文档(或者来自主机A),这个同源策略就会发挥作用,阻止这个脚本访问这个文档。

实际上,同源策略并非应用不同源的窗口中所有对象的所有属性。不过它应用到了其中大多数属性,尤其是对Document对象的几乎所有属性而言。凡是包含另一个服务器中文档的窗口或窗体,都是同源策略的适用范围。如果脚本打开一个窗口,脚本也可以关闭它。但不能以任何方式查看窗口内部。同源策略还应用于XMLHttpRequests生成的HTTP请求(16章)。这个对象允许客户端javascript生成任意的HTTP请求到脚本所属文档的web服务器。但是不允许脚本和其他web服务器通信。

对于防止脚本窃取有效的信息来说,同源策略是必须的。如果没有这个限制。恶意脚本(通过防火墙载入安全的公司内外的浏览器)可能会打开一个空的窗口,欺骗用户进入并使用这个窗口在网上浏览文件 。恶意脚本能够读取窗口的内容并将其发送回自己的服务器。同源策略防止了这种行为。

不严格的同源策略

在 某些情况下,同源策略就显得稍微严格,本节会介绍三种不严格的同源策略

同源策略给那行使用多个子域的大站带来了一些问题,例如来自a.ahthw.com的文档里的脚本想要合法的从b.ahthw.com读取文档的属性。为了支持这种类型多域名占占,可以使用Document.domain属性。在默认的情况下,domain属性存放的是载入文档的服务器的主机名。可以设置这一属性为ahthw.com
如果两个窗口(或窗体)包含的脚本把domain设置成了相同的值,那么这两个窗口就不再受同源策略的约束。他们可相互读取对象的属性。例如,从c.ahthw.com和d.ahthw.com载入的文档的脚本可以把他们的document.domain属性都设置为ahthw.com,这一依赖,这些文档就有了同源性,可以相互读取属性。

不严格同源的第二项技术已经标准化为:跨域资源共享(Cross-Origin Resource Sharing,参见http://www.w3.org/TR/cors/)。使用“Origin”请求头和新的Access-Control-Allow-Origin响应头来扩展HTTP。它允许服务器用头信息显式地列出源,或使用通配符来匹配所有的源并允许任何地址请求文件。使用这种新的头信息来允许跨域HTTP请求,这样XMLHttpRequest就不会被同源策略所限制了。

另外一种新的技术,跨域文档消息(cross - document messagin),允许来自一个文档的脚本可以传递文本消息到另一个文档的脚本,而不管脚本的来源是否不同。调用window对象上的 postMessage()方法,可以异步传递消息事件(可用onmessage事件句处理海曙来处理它)到窗口文档里。一个文档里的脚本还是不能调用在其他文档里的方法和读取属性。但它们可以用这些消息传递技术来实现安全的通信(20章3节有跟多关于跨文档消息api的细节)。

iii.跨站脚本

跨站脚本(cross-site scrpting),或者胶XSS,这个术语表示一类安全问题,也就是攻击者想目标web站点诸如HTML标签或者脚本。防止XSS攻击是服务器端WEB开发者的一项基本规则。然而,客户端javascript程序员也必须意识到或者能够预防跨站脚本。

如果web页面动态产生文档内容,并且这些文档内容是基于用户提交数据的,而并没有通过从中移除任何嵌入的html标签来“消毒”的话,这个页面就很容易遭到跨站脚本的攻击。

来看一个小例子,考虑如下的web页面,它使用javascript通过用户名字像用户说问好。

    var name =decodeURIComponent(window.location.search.substring(1)) ||"";
    document.write("hello " + name)

专门通过以下地址来调用

www.a.com/good.html?Davide

这时候,它会显示文本"Hello David"。但是考虑一下,使用下面的脚本调用会发生什么样的情况。

www.a.com/good.html?%3Cscript%3Ealert("Davide")%3C/script%3E

使用这个URL,脚本会动态的生成另一个脚本,(%3C和%3E是一个尖括号的编码)在这个例子中,注入的脚本只显示一个对话框。但是考虑如下情况

www.b.com/good.html?name=%3Cscript src=siteB/xxx.js%3E%3Cscript%3E

之所以叫做跨站脚本估计,就是因为它涉及多个站点。站点B专门构造到站点A的链接,注入来自站点B的脚本。脚本xxx.js驻留在恶意站点B中,但现在,它嵌入到站点A中,并且可以对站点A的内容进行任何想要的操作。它可能损坏这个页面或者使其不能正常工作(例如下节介绍的拒绝式服务攻击)。者可能对站点A的用户带来不少坏处。

更危险的是,恶意脚本可以读取站点A存储的Cookie(可能统计数据或者其它个人验证信息)然后发送回站点B。注入的脚本甚至可以诱骗用户点击将数据发送回站点B。
通常,防止XSS估计的方式是,在使用任何不可信的数据来动态创建文档内容之前,从中移除HTML标签。可以通过下一行代码来移除<script>两边的尖括号。

name = name.replace(/</g,"&lt;").replace(/>/g,"&gt;");

上面简单代码把字符串中所有的尖括号替换成他们对应的HTML实体,也就是说将字符串中任意HTML标签进行转义过滤和删除处理。IE8定义了一个更加微妙的toStaticHTML()方法,可以移除<script>标签(和其它潜在的可执行内容)而不修改不可执行的HTML。toStaticHTML()是不标准的,但在javascript核心代码中自己实现一个HTML安全函数也非常简单。

引用博客园作者小坦克:的预防提示

        1.将重要的cookie标记为http only,   这样的话Javascript 中的document.cookie语句就不能获取到cookie了.
        2.只允许用户输入我们期望的数据。 例如: 年龄的textbox中,只允许用户4输入数字。 而数字之外的字符都过滤掉。
        3.对数据进行Html Encode 处理
        4.过滤或移除特殊的Html标签, 例如: <script>, <iframe> ,  &lt; for <, &gt; for >, &quot for
        5.过滤JavaScript 事件的标签。例如 "onclick=", "onfocus" 等等。

HTML5的内容安全策略则更进一步,它为<iframe>元素定义了一个sandbox。在实现之后,它允许显示不可信的内容,并自动禁用脚本。
跨站脚本使有害的漏洞能够立足web构架中,深入理解这些跨站脚本是值得的。很多在线资源可以参考
http://cert.org/historical/advisories/CA-2000-02.cfm

iiii.拒绝服务攻击
这里描述同源策略和其他的安全限制可以很好地预防恶意代码毁坏数据或者防止侵犯隐私这种问题。然而根据不止一种,拒绝服务攻击,这种手法非常暴力。比如alert()对话框无限占用浏览器,或者使用一个没有意义的循环来占用cpu等。
利用window.setInterval()方法占用cpu,并分配很多内存来根据你的系统。web浏览器没有通用的办法来放在这种笨重的手法。但是实际上没有人会访问一个滥用这种脚本的网站。因此在web上不是一个常见的问题。

7.客户端框架。

一些web开发者基于客户端框架或类库创建它们的web应用非常便捷。从某种意义上来说,类库也是框架。它们对web浏览器提供的标准和专用的API进行了封装,向上提供更高级的API。
使用框架的好处就是可以使用更简洁的代码完成更复杂的功能,此外,完善的框架也会帮我们处理很多兼容性、安全性和可访问性的问题。

17章会介绍jQuery,它是当前最流行的框架之一。理解底层的API会帮助你称为更优秀的web开发者。虽说使用他们后很少使用原生的API。

除了jQuery外,还有很多优秀的javascript框架,其中有些框架非常有名,并且广泛使用。

Prototype
Prototype类库和jQuery类似,是专门征对DOM和AJax实现的一套工具,此外还问语言核心扩展了很多实用的工具,scriptaculous就是类库基于Prototype实现的。

Dojo
DOjo是一个大型的框架,它包括一个种类繁多的UI组件集合、包括管理系统、数据抽象层等

YUI
YUI是yahoo使用的一个著名框架,YUI和Dojo一样庞大,是一个无所不包的客户端类库,包含一眼工具、DOM、UI组件等。目前有两个不兼容的版本YUI2和YUI3

Closure
Closure类库是Google应用Gmail、Docs和其它web应用客户端类库。这个类库是打算和Closure编译器http://code.google.com/closure/compiler/配合使用的,剃除没用的类库函数。因为没有用的代码会在部署之前都被移除。Closure类库设计者不需要保持特性集合的紧凑。所以Closure包含一个庞大的工具集。

GWT
GWT,即google web toolkit,是一个完全不同类型的客户端框架。它用JAVA定义了web应用接口,并提供编译器,将JAVA程序翻译成兼容的客户端Javascript。GWT在一些google产品中使用,但不如它们之间的Closure类库用的那么广泛。

(本文完,欢迎大家关注上章节内容:第十章:Javascript子集和扩展,下章内容:第十二章 window对象