共计 7176 个字符,预计需要花费 18 分钟才能阅读完成。
JavaScript 页面运行时形成过程
- Web 利用的生命周期步骤
- 从 HTML 代码到 Web 页面的处理过程
- JavaScript 代码的执行程序
- 与事件交互
- 事件循环
咱们对 JavaScript 的摸索从客户端 Web 利用开始,其代码也在浏览器提供的引擎上执行。为了打好后续对 JavaScript 语言和浏览器平台的学习根底,首先咱们要了解 Web 利用的生命周期,尤其要了解 JavaScript 代码执行生命周期的所有环节。
你晓得吗?
- 浏览器为什么总是会依据给定的 HTML 来渲染页面呢?
- Web 利用一次能解决多少事件呢?
- 为什么浏览器应用事件队列来处理事件?
生命周期概览
典型的客户端 Web 利用的生命周期从用户在浏览器中输出一串 URL 开始。
- 用户:输出 URL
- 浏览器:生成申请并发送至服务器;
- 服务器:执行某些动作或获取某些资源,将响应发送回客户端;
- 浏览器:解决 HTML、CSS 和 JavaScript,并构建后果页面;
- 浏览器:监控事件队列,一次解决其中的一个事件;
- 用户:与页面元素交互;
- 用户:敞开 Web 页面;
- 浏览器:利用生命周期完结;
从用户的角度来说,浏览器构建了发送至服务器的申请,该服务器解决了申请,并造成了一个通常由 HTML、CSS 和 JavaScript 代码所组成的响应。当浏览器接管了响应时,咱们的客户端利用开始了它的生命周期。其执行步骤如下:
- 页面构建——创立用户界面;
- 事件处理——进入循环从而期待事件的产生,产生后调用事件处理器;
- 利用的生命周期随着用户关掉页面而完结。
利用的生命周期随着用户关掉或来到页面而完结。当初来写一个简略的示例程序,每当用户挪动鼠标或单击页面就会显示一条音讯。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul id="first"></ul>
<script>
function addMessage(element, message) {var messageElement = document.createElement('li')
messageElement.textContent = message
element.appendChild(messageElement)
}
var firstElement = document.getElementById('first')
addMessage(firstElement, 'foo bar')
</script>
<ul id="second"></ul>
<script>
document.body.addEventListener('mousemove', function(){var secondElement = document.getElementById('second')
addMessage(secondElement, 'second mousemove')
})
document.body.addEventListener('click', function(){var secondElement = document.getElementById('second')
addMessage(secondElement, 'second click')
})
</script>
</body>
</html>
页面构建阶段
在 Web 页面能被展现交互之前,其页面必须依据服务器获取的响应来构建。页面构建阶段是建设 Web 利用的 UI,其次要包含两个步骤:
- 解析 HTML 代码并构建文档对象模型(DOM)
- 执行 JavaScript 代码。
步骤 1 对在浏览器解决 HTML 节点的过程中执行,步骤二会在 HTML 解析一种非凡节点——脚本节点(蕴含或援用 JavaScript 代码的节点)时执行。页面构建过程中,这两个步骤会交替执行屡次。
HTML 解析和 DOM 构建
页面构建始于浏览器承受 HTML 代码时,该阶段为浏览器构建页面 UI 的根底。通过解析收到的 HTML 代码,构建一个个 HTML 元素,构建 DOM。在这种时候对 HTML 结构化的示意模式中,每个 HTML 都被当作是一个节点。
只管 DOM 是依据 HTML 来创立的,两者紧密联系,但须要强调的是,它们两者并不相同。你能够把 HTML 代码看作是浏览器页面 UI 构建初始 DOM 的蓝图。为了正确的构建 DOM,浏览器还会修复它在蓝图中发现的问题。
<html>
<head>
<p>
test
</p>
</head>
<body>
</body>
</html>
这是一个有效的 HTML,页面中的 head 元素中谬误的蕴含了一个 paragraph 的元素。head 元素的个别用处是展现页面的总体信息:页面题目、字符编码和内部款式脚本,而不是用于相似这里的定义页面内容。浏览器对于这个谬误会进行修复,将段落元素的内容搁置到页面内容中的 body 元素中,结构出正确的 DOM
执行 JavaScript
所有蕴含在脚本元素中的 JavaScript 代码由浏览器的 JavaScript 引擎执行。因为 代码的次要目标是提供动静页面,所以浏览器通过全局对象提供了一个 API 使 JavaScript 引擎能够与之交互,并扭转页面的内容。
JavaScript 中的全局对象
浏览器裸露给 JavaScript 引擎的次要全局对象是 window 对象,它代表了一个页面的窗口。window 对象是获取所有其余全局对象、全局变量和浏览器 API 的拜访路径。
全局 window 对象最重要的属性是 document,它代表了以后页面的 DOM,通过应用这个对象,JavaScript 代码就能在任何水平上扭转 DOM,包含批改或移除现有的节点,以及创立和插入新的节点。
JavaScript 代码的不同类型
从页面上来划分的话,JavaScript 代码能够分为两种不同类型的代码:全局代码和函数代码。
全局代码是指位于函数之外的代码,函数代码是指蕴含在函数中的代码。
<script>
function addMessage(element, message) {var messageElement = document.createElement('li')
messageElement.textContent = message
element.appendChild(messageElement)
}
var firstElement = document.getElementById('first')
addMessage(firstElement, 'foo bar')
</script>
这两类代码的次要不同是它们的地位:蕴含在函数内的代码叫做函数代码,而在所有函数以外的代码叫做全局代码。
这两种代码在执行中也有不同。全局代码由 JavaScript 引擎以一种间接的形式主动执行,每当遇到这样的代码就一行接一行地执行。
反过来,若想执行函数代码,则必须被全局代码调用。
注:从控制台也能够执行函数代码,这能够产生平安问题,尝试攻打他人网站的时候,能够试着从这里调用近程申请。
在页面构建阶段执行 JavaScript 代码
当浏览器在页面构建阶段遇到了脚本节点,它会进行 HTML 和 DOM 元素的构建,转而开始执行 JavaScript 代码,也就是执行蕴含脚本元素的全局 JavaScript:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul id="first"></ul>
<script>
function addMessage(element, message) {var messageElement = document.createElement('li')
messageElement.textContent = message
element.appendChild(messageElement)
}
var firstElement = document.getElementById('first')
addMessage(firstElement, 'foo bar')
</script>
</body>
</html>
咱们来认真的看看这个执行过程:
首先定义了一个 addMessage 函数
function addMessage(element, message) {var messageElement = document.createElement('li')
messageElement.textContent = message
element.appendChild(messageElement)
}
而后通过全局 document 对象上的 getElementById 办法从 DOM 上获取了一个元素:
var firstElement = document.getElementById('first')
这段代码后紧跟着的是对 addMessage 的调用:
addMessage(firstElement, 'foo bar')
这条代码创立了一个新的 li 元素,而后批改了其中的文字内容,最初将其插入 DOM 中。
在这个例子中,JavaScript 通过创立一个新元素并将其插入 DOM 节点批改了以后的 DOM 构造。一般而言,JavaScript 代码可能在任何水平上批改 DOM 构造:它能创立新的接单或移除现有 DOM 节点。但它仍然不能做某些事件,例如抉择和批改还没创立的节点。这就是为什么要把 script 元素放在页面底部的起因。咱们就不用放心是否某个 HTML 元素曾经加载为 DOM。
<u> 一旦 JavaScript 引擎执行到了脚本元素中 JavaScript 代码的最初一行,浏览器就退出了 JavaScript 执行模式,并持续余下的 HTML 构建 DOM 节点。在这期间,如果浏览器再次遇到脚本元素,那么从 HTML 到 DOM 的构建再次暂停,JavaScript 运行环境开始执行余下 JavaScript 代码。须要留神的是:JavaScript 利用在此时仍然会放弃着全局状态。所以在某个 JavaScript 代码执行期间用户创立的全局变量能失常地被其余脚本元素中的 JavaScript 代码所拜访。其起因在于全局 window 对象存在于整个页面的生存期之间,在它下面存储着所有 JavaScript 变量。只有还有没解决完的 HTML 元素和没执行完的 JavaScript 代码,上面两个步骤都会始终交替执行。</u>
- 将 HTML 构建为 DOM。
- 执行 JavaScript 代码。
最初,当浏览器解决完所有 HTML 元素后,页面构建阶段就完结了。随后浏览器就会进入利用生命周期的第二局部:事件处理。
事件处理
客户端 Web 利用是一种 GUI 利用,也就是这种利用对不同类型的事件作响应,如鼠标挪动、单击和键盘按压等。因而,<u> 在页面构建阶段执行的 JavaScript 代码,除了会影响全局利用状态和批改 DOM 外,还会注册事件监听器 </u>(或处理器)。这类监听器会在事件产生时,由浏览器调用执行。有了这些事件处理器,咱们的利用也就有了交互能力。在具体探讨注册事件处理之前,让咱们先从头到尾看一遍事件处理器的总体思维。
事件处理器概览
浏览器执行环境的核心思想基于:同一时刻只能执行一个代码片段,即所谓的单线程执行模型。设想一下在银行柜台前排队,每个人进入一支期待叫号并「解决」。但 JavaScript 则只开启了一个营业柜台!每当轮到某个顾客(某个事件),只能解决该位顾客。
你所须要的仅仅一个营业柜台的职员为你解决工作,帮你订制全年的财务计划。当一个事件到达后,浏览器须要执行相应的事件处理函数。这里不保障用户总会极富急躁地期待很长时间,直到下一个事件触发。所以,浏览器须要一种形式来跟踪曾经产生但尚未解决的事件。为了实现这个指标,浏览器应用了 <u> 事件队列 </u>。
所有已生成的事件——无论是用户生成的,例如鼠标挪动、点击,还是服务器生成的,例如 Ajax 事件,都会放在同一个事件队列里,以它们被浏览器检测到的顺序排列。
- 浏览器查看事件列头;
- 如果浏览器没有在队列中检测到事件,则持续查看;
- <u> 如果浏览器在队列头中检测到了事件,则取出该事件并执行相应的事件处理器。在这个过程中,余下的事件在事件队列中急躁期待,直到轮到它们被解决。</u>
因为一次只能解决一个事件,所以咱们必须分外留神解决所有事件的总工夫。执行须要破费大量工夫的事件函数会导致 Web 利用的无响应。
重点留神浏览器在这个过程中的机制,其搁置事件的队列是在页面构建阶段和事件处理阶段以外的。这个过程对于决定事件何时产生,并将其推入事件队列很重要,这个过程不会参加事件处理线程。
事件是异步的
事件可能会以难以预料的工夫和程序产生——强调用户以某个程序按键或点击是十分奇怪的。咱们对事件的解决,以及处理函数的调用是异步的。如下类型的事件会在其余类型的事件中产生。
- 浏览器事件,当页面加载完或者无奈加载时;
- 网络事件,例如 Ajax 事件和服务端事件;
- 用户事件,例如鼠标点击、鼠标挪动等;
- 计时器事件,当 setTimeout 工夫到期等。
Web 利用的 JavaScript 代码中,大部分都是对上述事件的解决!
事件处理的概念是 Web 利用的外围,代码的提前建设是为了在之后的某个工夫执行。除了全局代码,页面中的大部分代码都将作为某个事件执行的后果。
在事件能被解决之前,代码必须要告知浏览器咱们要解决特定事件。
注册事件处理器
事件处理器,是当某个特定事件产生后,咱们心愿执行的函数。为了达到这个指标,咱们必须通知浏览器咱们要解决哪个事件。这个过程叫作注册事件处理器。在 Web 利用中,有两种形式注册事件:
- 通过把函数赋值给某个非凡属性
- 通过内置的 addEventListener 办法
例如,编写如下代码,将一个函数赋值给 window 对象上的某个特定属性 onload:
window.onload = function(){}
通过这种形式,事件处理器就会注册到 load 事件上——当 DOM 曾经就绪并全副构建实现,就会触发这个事件。相似的,如果咱们想要为文档中的 body 元素注册点击事件,咱们能够这么做:
document.body.onClick = function(){}
把函数赋值给非凡属性是一种简略而间接的注册事件处理器的形式。然而,我并不举荐这种形式,因为这会带来一些毛病:onClick 对于某个事件只能注册一个事件处理器,也就是说,会将上一个事件处理器笼罩掉。
侥幸的是,还有一种代替计划:addEventListener 办法让咱们可能注册尽可能多的事件,只有咱们需要的话。
document.body.addEventListener('mousemove', function(){var secondElement = document.getElementById('second')
addMessage(secondElement, 'second mousemove')
})
document.body.addEventListener('click', function(){var secondElement = document.getElementById('second')
addMessage(secondElement, 'second click')
})
处理事件
事件处理的次要思维是:当事件产生时,浏览器调用相应的事件处理器。
因为单线程执行模型,所以同一时刻只能解决同一个事件,任何前面的事件都只能在以后事件处理器齐全完结执行后能力被解决。
让咱们再来看看下面的利用,为了响应用户的动作,浏览器把鼠标挪动和单击事件以它们产生的程序放入事件队列:
- 第一个是鼠标挪动事件
- 第二个是鼠标点击事件
在事件处理阶段中,事件循环会查看队列,其发现队列的后面有一个鼠标挪动事件,而后执行了相应的事件处理器序。当鼠标挪动事件被事件处理器处理完毕后,轮到期待在事件队列中的点击事件。
当鼠标挪动事件处理函数的最初一行代码执行结束后,JavaScript 引擎退出事件处理器函数。事件循环再次查看队列。这一次,在队列的最后面,事件循环发现了鼠标单击事件并解决了该事件,一次单击处理器执行实现。
队列中不再有新的事件,事件循环就会持续期待,期待新到来的事件。这个循环会始终执行到用户敞开了 Web 页面。
小结
- 浏览器接管的 HTML 代码用作创立 DOM 的蓝图,它是客户端 Web 利用构造的外部展现
- 咱们应用 JavaScript 代码来动静批改 DOM 以便给 Web 利用带来动静行为。
-
客户端 Web 利用的执行分为两个阶段:
- 页面构建代码是用于创立 DOM 的,而全局 JavaScript 代码是遇到 script 节点时执行的。在这个过程中,JavaScript 代码可能以任意水平扭转以后的 DOM,还能注册事件处理器。事件处理器是一种函数,当某个特定事件产生后会被执行。
- 事件处理——在同一时刻,只能解决不同事件中的一个 ,解决程序是事件生成的程序。事件处理阶段大量依赖事件队列, 所有的事件都以其呈现的顺序存储在事件队列中。<u> 事件循环会查看事件队列的队头,如果检测到了一个事件,那么相应的事件处理器就会被调用。</u>