AI智能
改变未来

JavaScript Ninja–第二章笔记


2、 运行时的页面构建过程

2.1 生命周期概览

典型客户端Web应用的生命周期从用户在浏览器地址栏输入一串URL,或单机一个链接开始

从用户的角度来说,浏览器构建了发送至服务器的请求(2),该服务器处理了请求(3)并形成了一个通常由HTML、CSS和JavaScript代码所组成的响应。当浏览器接受了响应(4)时,我们的客户端应用开始了它的生命周期。由于客户端Web应用是图形用户界面(GUI)应用,其生命周期与其他的GUI应用相似(例如标准的桌面应用或移动应用),其执行步骤如下所示:

  1. 页面构建–创建用户界面
  2. 事件处理–进入循环(5)从而等待事件(6)的发生,发生后调用事件处理器

应用的生命周期随着用户关掉页面(7)而结束。现在让我们一起看一个简单的示例程序:

示例代码:

<!DOCTYPE html><html><head><title>Web app lifecycle</title><style>/*对应的是ul*/#first {color: green;}#second {color: red;}</style></head><body><ul id="first"></ul><script><!---->function addMessage(element, message) {//添加一个li标签var messageElement = document.createElement(\'li\')messageElement.textContent = messageelement.appendChild(messageElement)}var first = document.getElementById(\'first\')/*调用函数,传入first*/addMessage(first, \'Page loading\')</script><ul id="second"></ul><script>document.body.addEventListener(\'mousemove\', function() {var second = document.getElementById(\'second\')addMessage(second, \'Event: mousemove\')})document.body.addEventListener(\'click\', function() {var second = document.getElementById(\'second\')addMessage(second, \'Event: click\')})</script></body></html>

示例代码在浏览器的执行结果

2.2 页面构建阶段

​ 当前Web应用能被展示或交互之前,其页面必须根据服务器获取的响应(通常是HTML、CSS和JavaScript代码)来构建。页面构建阶段的目标是建立Web应用的UI,其应用主要包括两个步骤:

1. 解析HTML代码并构建文档对象模型(DOM)2. 执行JavaScript代码

步骤一会在浏览器处理HTML节点的过程中执行,步骤二会在HTML解析到一种特殊节点–脚本节点(包含或引用JavaScript代码的节点)时执行。页面构建阶段中,这两个步骤会交替执行多次,如下图所示

​ 页面构建阶段始于浏览器接受HTML代码时,该阶段为浏览器构建页面UI的基础,通过解析收到的HTML代码,构建一个HTML元素,构建DOM。在这种对HTML结构化表示的形式中,每个HTML元素都被当作一个节点,直到遇到第一个脚本元素,示例页面都在构建DOM。

​ 所有节点都只有一个父节点,例如,head节点父节点为html节点,同时,一个节点可以有任意数量的子节点,例如html系欸点有两个孩子节点:head节点和body节点。同时一个元素的孩子节点被称作兄弟接二点。(head节点body节点是兄弟节点)尽管DOM是根据HTML来创建的,两者紧密联系,但需要强调的是,他们两者并不相同。你可以把HTML代码看作浏览器页面UI构建初始DOM的蓝图。为了正确构建每个DOM,浏览器还会修复它在蓝图中发现的问题。如下图所示

图2.5展示了一个简单的错误HTML代码示例,页面中的head元素中错误地包含了一个paragraph元素。head元素的一般用途是展示页面的总体信息,例如页面标题、字符编码和外部样式脚本,而不是用于类似本例中的定义页面内容

当前HTML的版本是HTML5,可以通过https://html.spec.whatwg.org/查看当前版本中有哪些可用特性。你若需要更易读的文档MDN

​ 在页面的构建阶段,浏览器会遇到特殊类型的HTML元素–脚本元素,该元素用于包括JavaScript代码。每当解析到脚本元素时,浏览器就会停止从HTML构建DOM,并开始执行JavaScript代码

2.2.1 执行JavaScript代码

​ 所有包含在脚本元素中的JavaScript代码由浏览器的JavaScript引擎执行,例如Firefox的Spidermonkey引擎,chrome和opera和V8引擎和Edge的Chakra引擎,由于代码的主要目的是提供动态页面,故而浏览器通过全局对象提供一个API是JavaScript引擎可以与之交互并改变页面内容

JavaScript全局对象

​ 浏览器暴漏给JavaScript引擎的主要全局对象是window对象,它代表了包含一个页面的窗口,window对象是获取所有其他全局对象全局变量(甚至包含用户自定义对象)和浏览器API的访问途径。全局window对象最重要的属性是document,它代表了当前页面的DOM。通过使用这个对象,JavaScript代码就能在任何程度上改变DOM,包括修改或移除现存的节点,以及创建和插入新的节点

var first = document.getElementById("first")

这个示例中使用全局document对象来通过ID选择一个元素,然后将该元素赋值给变量first,随后我们就能在该元素上使用JavaScript代码来对其作各种操作,例如改变文字内容,修改属性,动态创建和增加新孩子节点,深圳可以从DOM上将该元素移除

​ 本书自始至终都会描述一系列浏览器内置对象和函数。不过很遗憾,浏览器所支持的全部特性已经超出本书探讨的JavaScript的范围,通过MDN你可以查看WebAPI接口的状态

document全局对象的键值对

2.2.2 JavaScript代码的不同类型

我们大致分出不同类型的JavaScript代码:

  • 1.全局代码

  • 2.函数代码

清单会帮你理解这两种类型的代码的不同

清单

<script>function addMessage(element.message){var messageeElement = document.createElement(\'li\')messageElement.textContent = message//函数代码指的是包含在函数中的代码element.appendChild(messageElement)}var first = document.getElementById("first")addMessage(first,"page loading")//全局代码指的是位于函数代码之外的代码</script>

​ 这两类代码的主要不同是他们的位置:包含在函数内的代码叫做函数代码,而在所有函数意外的代码叫做全局代码

​ 这两种代码在执行中也有不同(随后你将看到一些其他的不同,尤其是在第5章中)。全局代码由JavaScript引擎以一种直接的方式自动执行,每当遇到这样的代码就一行接一行的执行。例如,定义在addMessage函数中的全局代码片段使用内置方法getElementById来获取ID为first的元素,然后再调用addMessage函数,每当遇到这些代码就会一个个执行。

​ 反过来,若想执行函数代码,则必须被其他代码调用:既可以是全是那句代码(例如由于全局代码的执行过程中执行addMessage函数代码,所以addMessage函数得被调用,也可以是其他函数,还可以由浏览器调用)

2.2.3在页面构建阶段执行JavaScript代码

​ 当浏览器在页面构建阶段遇到了脚本节点,他会停止HTML和DOM的构建,转而开始执行JavaScript代码,也就是执行包含在脚本元素的全局JavaScript代码(以及由全局代码执行中调用的函数代码)。让我们看看清单中的示例代码

​ 一下代码显示了全局JavaScript代码被执行后DOM的状态,让我们仔细看看这个执行过程,首先定义了一个addMessage函数

function addMessage(element,message){var messageElement = document.createElement(\'li\')messageElement.textContent = messageelement.appendChild(messageElement)}

然后通过全局document对象上的getElementById方法从DOM上获取了一个元素

var first = document.getElementById("first")

这段代码后紧跟着对函数addMessage的调用

addMessage(first,"page loading")

这条代码创建了一个新的li元素,然后修改了其中的文字内容,最后将其插入DOM中

​ 这个例子中,JavaScript通过创建一个新元素并将其插入DOM节点修改了当前的DOM结构。一般来说,JavaScript代码能够在任何程度上修改DOM结构,他能创建新的接单或移除现有DOM节点,但它依然不能做某些事情,例如选择和修改没有被创建的节点,这就是为什么把script元素放在页面底部的原因,如此一来,我们就不必担心是否某个HTML元素已经加载DOM

​ 1.通过对变量messageElement的打印发现,

li

标签本身也是实例对象,他的原型对象指向HTMLElement,也就是说DOM元素也是实例对象,因为他又

__proto__

指向了自己的原型对象HTMLelement

​ 2.对HTMLElement进行打印发现其也是一个函数对象(变量的原型对象)

一旦JavaScript引擎执行了脚本元素中JavaScript代码的最后一行,浏览器就退出了JavaScript执行模式,并继续将剩余的HTML构建为DOM节点。在这期间,如果浏览器再次遇到脚本元素,那么从HTML到DOM的构建再次暂停,JavaScript运行环境开始执行余下的JavaScript代码。需要重点注意:JavaScript应用在此时依然会保持着全局的状态

<!DOCTYPE html><html><head><title>Different types of JavaScript code – global and function code</title><style>#first {color: green;}#second {color: red;}</style></head><body><ul id = "first"></ul><script>function addMessage(element, message) {var messageElement = document.createElement(\'li\');	//#AmessageElement.textContent = message;			          //#Aelement.appendChild(messageElement);			          //#A}var first = document.getElementById(\'first\');		      //#BaddMessage(first, \'Page loading\');				            //#Bconsole.log(window);</script><!--#A – function code is the code contained within a function#B – global code is the code outside functions --><ul id = "second"></ul></body></html>

2.3 事件处理

​ 客户端时一种GUI应用,也就是说这种应用会对不同类型的时间作响应,如鼠标移动、单机和键盘按压等。因为,在页面构建阶段执行的JavaScript代码,除了会影响全局应用状态和修改DOM外,还会注册事件监听器。这类监听器会在事件发生时,由浏览器调用执行,有了这些事件处理器,我们的应用就有了交互能力,再详细探讨注册事件处理器之前,让我们从头到尾看一遍事件处理器的总体思想

2.3.1 事件处理器概览

​ 浏览器执行环境的核心思想基于:同一时刻只能执行一个代码片段,即所谓的单线程执行模型。想象一下在银行柜台前排队,每个人进入一直队伍等待叫号并处理,但JavaScript之开启了一个营业柜台每当轮到某个顾客时,只能处理这一个顾客

  • 浏览器检查事件队列头
  • 如果浏览器没有在队列中检测到事件,则继续检查
  • 如果浏览器在队列头中检测到了事件,则取出该事件并执行相应的事件处理器。在这个过程中,余下的事件在事件队列中耐心等待

由于一次只能处理一个事件,我们必须格外注意处理所有事件的总时间。执行需要花费大量时间执行的处理函数会导致Web应用无响应

​ 重点注意浏览器在这个过程中的机制,其放置事件的队列是在页面构建阶段和事件处理阶段以外的。这个过程对于决定事件何时发生将其

​ 赵总,这个架构是目前Vue生态中最完善可靠的企业级中后台解决方案了,使用了稳定的技术栈,提炼了典型的业务模型,内置了excel的

2.3.2 注册事件处理器

​ 事件处理器是当某个特定事件发生后我们希望执行的函数,为了达到这个目标,我们比哦徐告知浏览器我们要处理那个事件,这个过程叫做注册事件处理器,在客户端Web应用中,

  • 通过把函数赋值给某个特殊属性
  • 通过使用内置addEventListener

将一个函数赋值给window对象上的某个特定属性onload,通过这种方式,事件处理器就会注册到load事件上(当DOM已经就绪并全部构建完成,就会触发这个事件)。(如果你对赋值操作符右边的记法有些困惑,不要担心,随后的章节中我们会细致)

2.3.3 处理事件

​ 事件处理背后的主要思想是:当事件发生时,浏览器调用响应的事件处理其,如前面提到的,由于单线程执行模型,所以同一时刻只能处理一个事件。任何后面的事件都只能在当前事件处理器完全结束执行后才能被处理

2.4 小结

  • 浏览器接受的HTML代码用来创建DOM的蓝图,它是客户端Web应用结构的内部展示阶段
  • 我们使用JavaScript代码来动态的修改DOM以便给Web应用带来动态行为
  • 客户端Web应用的执行分为两个阶段页面构建阶段适用于创建DOM的,而全局JavaScript代码时遇到script节点时执行的。在这个执行过程中,JavaScript代码能够以任意程度改变当前的DOM,还能够注册事件处理器–事件处理器是一种函数,当某个特定事件(例如,一次鼠标单机或键盘按压)发生后会被执行。注册事件处理器很容易:使用内置的addEventListener方法
  • 事件处理–在同一时刻,只能处理多个不同事件中的一个,处理顺序时事件生成的顺序。事件处理阶段大量依赖事件队列,所有的事件都以其出现的顺序存储在事件队列中。事件循环会检查事件队列的对头,如果检测到了一个事件,那么相应的事件处理器就会被调用

2.5 练习

1.客户端Web应用的两个生命周期阶段是什么?

  • 页面构建阶段

    两个步骤:1.解析html代码并构建文档对象模型(DOM) 2.执行JavaScript 代码所有包含在脚本元素中的JavaScript代码都由浏览器的JavaScript引擎执行,由于代码的主要目的是提供动态页面,故而浏览器通过全局对象window提供了API和window最重要的属性document,使JavaScript引擎可以与页面进行交互并改变页面内容。当页面构建遇到JavaScript代码会停止html到dom的构建,然后执行完JavaScript代码的最后一行,浏览器会退出JavaScript执行模式,并继续余下的html构建成dom节点。当再次遇到脚本元素,因为全局window对象会存在于整个页面的生命周期之间,在他上面存储着所有的JavaScript变量。所以只要还有没处理完的html元素和没执行完的JavaScript代码,那么两个步骤都会一直交替执行。

  • 事件处理阶段

      浏览器执行的环境的核心思想基于:同一个时刻只能执行一个代码片段,即所谓的单线程执行模型。因此浏览器用队列来跟踪已经发生但尚未处理的事件。

      我们对事件的处理是异步的,如

      浏览器事件,例如页面加载完后无法再加载网络事件,例如来自服务器的响应(ajax事件和服务端事件)用户事件,例如鼠标单击,鼠标移动等计时器事件,当timeout时间到期或又触发来一次事件间隔

    2.相比将事件处理器赋值给某个特定元素的属性上,使用addEventListener方法来注册事件处理器的优势是什么?

    • 通过把函数赋值给某个特殊属性
    • 通过使用内置addEventListener
    window.onload = function (){}

    把函数赋值给特殊属性是一种简单而直接的注册事件处理器方式。但是,我们并不推荐你使用这种方式来注册事件处理器,这是因为这种做法会带来缺点,对于某个事件只能注册一个事件处理器,也就是,一不小心就会将上一个事件处理器改写掉,还有一种替代方案就是使用addEvenetListener方法让我们能够注册尽可能多的事件,只要我们需要。如下清单使用了清单2.3中的示例

    <script>//为mousemove事件注册处理器document.body.addEventListener("mousemove",function(){var second = document.getElementById("second")addMessage(second,"Event:mousemove")})//为click事件注册处理器document.body.addEventListener("click",function(){var second = document.getElementById("second")addMessage(second,"Event:click")})</script>

    3.JavaScript引擎在同一时刻能处理多少个事件?

    javascript引擎同一时刻只能处理一个事件

    4.事件队列中的事件是以什么顺序处理的?

    处理顺序是按照事件生成的顺序,事件处理阶段大量依赖事件队列,所有的事件都以其出现的顺序存储在事件队列中。事件循环会检查事件队列的队头,如果检测到一个事件,那么相应的事件处理器就会被调用

  • 赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » JavaScript Ninja–第二章笔记