浏览器模型

31次阅读

共计 5006 个字符,预计需要花费 13 分钟才能阅读完成。

1. 代码嵌入网页的方法
1.1script 元素嵌入代码
1.2script 元素加载外部脚本
1.3 事件属性
1.4URL 协议
2.script 元素
2.1 工作原理
2.2defer 属性 同步下载生成 dom 后执行按顺序
2.3async 属性 同步下载直接中断开始执行不按顺序
2.4 脚本的动态加载 生成 dom 后不按顺序执行 可以设 async false 后按顺序执行
2.5 加载使用的协议
3. 浏览器的组成
3.1 渲染引擎
3.2 重流和重绘
3.3JavaScript 引擎

1. 代码嵌入网页的方法

网页中嵌入 JavaScript 代码,主要有三种方法。

  • <script> 元素直接嵌入代码。
  • <script> 标签加载外部脚本
  • 事件属性
  • URL 协议

1.1script 元素嵌入代码

1.1.1 <script> 标签有一个 type 属性,用来指定脚本类型。对 JavaScript 脚本来说,type 属性可以设为两种值。

text/javascript:这是默认值,也是历史上一贯设定的值。如果你省略 type 属性,默认就是这个值。对于老式浏览器,设为这个值比较好。
application/javascript:对于较新的浏览器,建议设为这个值。

<script type=”application/javascript”>
console.log(‘Hello World’);
</script>

如果 type 属性的值,浏览器不认识,那么它不会执行其中的代码。但是,这个 <script> 节点依然存在于 DOM 之中,可以使用 <script> 节点的 text 属性读出它的内容。

<script id=”mydata” type=”x-custom-data”>
console.log(‘Hello World’);
</script>

1.2script 元素加载外部脚本

如果脚本文件使用了非英语字符,还应该注明字符的编码。
为了防止攻击者篡改外部脚本,script 标签允许设置一个 integrity 属性,写入该外部脚本的 Hash 签名,用来验证脚本的一致性

<script src=”/assets/application.js”
integrity=”sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs=” charset=”utf-8″>
</script>

1.3 事件属性

网页元素的事件属性(比如 onclick 和 onmouseover),可以写入 JavaScript 代码

<button id=”myBtn” onclick=”console.log(this.id)”> 点击 </button

1.4URL 协议

URL 支持 javascript: 协议,即在 URL 的位置写入代码,使用这个 URL 的时候就会执行 JavaScript 代码。没有返回值,不进行跳转

点击
// 浏览器的地址栏也可以执行 javascript: 协议。将 javascript:console.log(‘Hello’) 放入地址栏,按回车键也会执行这段代码。不进行跳转

如果 JavaScript 代码返回一个字符串,浏览器就会新建一个文档,展示这个字符串的内容,原有文档的内容都会消失。

点击
上面代码中,用户点击链接以后,会打开一个新文档,里面有当前时间。

javascript: 协议的常见用途是书签脚本 Bookmarklet。由于浏览器的书签保存的是一个网址,所以 javascript: 网址也可以保存在里面,用户选择这个书签的时候,就会在当前页面执行这个脚本。为了防止书签替换掉当前文档,可以在脚本前加上 void,或者在脚本最后加上 void 0。

点击
点击

Void 执行 但不返回值
上面这两种写法,点击链接后,执行代码都不会网页跳转

2.script 元素

2.1 工作原理

1.html 一边下载一边解析
2. 遇到 script 标签,停止解析,把网页渲染的控制权转交给 JavaScript 引擎
3. 内部的 js 直接执行,外部的 js 下载和执行 js 代码. 多个 js 文件同时下载,按顺序执行。如果有 css,css 在这之前下载解析,或者在 js 遇到 css,停下后去解析 css
3. 执行完成后,继续 html 下载解析。

加载外部脚本时,浏览器会暂停页面渲染,等待脚本下载并执行完成后,再继续渲染。原因是 JavaScript 代码可以修改 DOM,所以必须把控制权让给它,否则会导致复杂的线程竞赛的问题。

解析和执行 CSS,也会产生阻塞。Firefox 浏览器会等到脚本前面的所有样式表,都下载并解析完,再执行脚本;Webkit 则是一旦发现脚本引用了样式,就会暂停执行脚本,等到样式表下载并解析完,再恢复执行。

此外,对于来自同一个域名的资源,比如脚本文件、样式表文件、图片文件等,浏览器一般有限制,同时最多下载 6~20 个资源,即最多同时打开的 TCP 连接有限制,这是为了防止对服务器造成太大压力。如果是来自不同域名的资源,就没有这个限制。所以,通常把静态文件放在不同的域名之下,以加快下载速度。

解决 js 在 dom 结构生成之前调用报错,可以把 script 标签放在页面最后。

另一种解决方法是设定 DOMContentLoaded 事件的回调函数。

<head>
<script>

document.addEventListener(
  'DOMContentLoaded',
  function (event) {console.log(document.body.innerHTML);
  }
);

</script>
</head>

另一种解决方法是,使用 <script> 标签的 onload 属性。当 <script> 标签指定的外部脚本文件下载和解析完成,会触发一个 load 事件,可以把所需执行的代码,放在这个事件的回调函数里面。

<script src=”jquery.min.js” onload=”console.log(document.body.innerHTML)”>
</script>
上面代码中,指定 DOMContentLoaded 事件发生后,才开始执行相关代码。DOMContentLoaded 事件只有在 DOM 结构生成之后才会触发

为了解决脚本文件下载阻塞网页渲染的问题,有了 defer 和 async 属性,区别在 defer 在 dom 加载完成以后按顺序执行,async 直接不按顺序执行。

2.2defer 属性

defer 属性的运行流程如下。

浏览器开始解析 HTML 网页。
1. 解析过程中,发现带有 defer 属性的 <script> 元素。
2. 浏览器继续往下解析 HTML 网页,同时并行下载 <script> 元素加载的外部脚本。
3. 浏览器完成解析 HTML 网页,此时再回过头执行已经下载完成的脚本。按顺序执行

对于内置而不是加载外部脚本的 script 标签,以及动态生成的 script 标签,defer 属性不起作用。另外,使用 defer 加载的外部脚本不应该使用 document.write 方法

2.3async 属性

1. 浏览器开始解析 HTML 网页。
2. 解析过程中,发现带有 async 属性的 script 标签。
3. 浏览器继续往下解析 HTML 网页,同时并行下载 <script> 标签中的外部脚本。
4. 脚本下载完成,浏览器暂停解析 HTML 网页,开始执行下载的脚本。哪个先下载完先执行
5. 脚本执行完毕,浏览器恢复解析 HTML 网页。

不应该使用 document.write 方法

defer 属性和 async 属性到底应该使用哪一个?

一般来说,如果脚本之间没有依赖关系,就使用 async 属性,如果脚本之间有依赖关系,就使用 defer 属性。如果同时使用 async 和 defer 属性,后者不起作用,浏览器行为由 async 属性决定

2.4 脚本的动态加载(不按顺序)

动态生成的 script 标签不会阻塞页面渲染,也就不会造成浏览器假死。但是问题在于,这种方法无法保证脚本的执行顺序,哪个脚本文件先下载完成,就先执行哪个。

如果想避免这个问题,可以设置 async 属性为 false。

[‘a.js’, ‘b.js’].forEach(function(src) {
var script = document.createElement(‘script’);
script.src = src;
script.async = false;
document.head.appendChild(script);
});

需要注意的是,在这段代码后面加载的脚本文件,会因此都等待 b.js 执行完成后再执行

2.5 加载使用的协议

如果不指定协议,浏览器默认采用 HTTP 协议下载

如果要采用 HTTPS 协议下载,必需写明。

<script src=”https://example.js&quot;></script>
根据页面本身的协议来决定加载协议,这时可以采用下面的写法。

<script src=”//example.js”></script>

3. 浏览器的组成

浏览器的核心是两部分:渲染引擎和 JavaScript 解释器(又称 JavaScript 引擎)。

3.1 渲染引擎

将网页代码渲染为用户视觉可以感知的平面文档

Firefox:gecko 引擎
safari:WebKit 引擎
Chrome:Blink 引擎
IE: Trident 引擎
Edge: EdgeHTML 引擎

渲染引擎处理网页,通常分成四个阶段
1. 解析代码:html 解析 dom css 解析为 cssom
2. 对象合成:合成 dom 和 cssom 为渲染 renderr tree
3. 布局:计算渲染树布局 layout
4. 绘制:将渲染树绘制到屏幕

往往第一步还没完成,第二步和第三步就已经开始

3.2 重流和重绘

渲染树转换为网页布局,称为“布局流”(flow);布局显示到页面的这个过程,称为“绘制”(paint)

作为开发者,应该尽量设法降低重绘的次数和成本。比如,尽量不要变动高层的 DOM 元素,而以底层 DOM 元素的变动代替;再比如,重绘 table 布局和 flex 布局,开销都会比较大。

优化技巧。

读取 DOM 或者写入 DOM,尽量写在一起,不要混杂。不要读取一个 DOM 节点,然后立刻写入,接着再读取一个 DOM 节点。
缓存 DOM 信息。
不要一项一项地改变样式,而是使用 CSS class 一次性改变样式。
使用 documentFragment 操作 DOM
动画使用 absolute 定位或 fixed 定位,这样可以减少对其他元素的影响。
只在必要时才显示隐藏元素。
使用 window.requestAnimationFrame(),因为它可以把代码推迟到下一次重流时执行,而不是立即要求页面重流。
使用虚拟 DOM(virtual DOM)库
下面是一个 window.requestAnimationFrame()对比效果的例子。

// 重绘代价高
function doubleHeight(element) {
var currentHeight = element.clientHeight;
element.style.height = (currentHeight * 2) + ‘px’;
}

all_my_elements.forEach(doubleHeight);

// 重绘代价低
function doubleHeight(element) {
var currentHeight = element.clientHeight;

window.requestAnimationFrame(function () {

element.style.height = (currentHeight * 2) + 'px';

});
}

all_my_elements.forEach(doubleHeight);


上面的第一段代码,每读一次 DOM,就写入新的值,会造成不停的重排和重流。第二段代码把所有的写操作,都累积在一起,从而 DOM 代码变动的代价就最小化了

3.3JavaScript 引擎

JavaScript 引擎的主要作用是,读取网页中的 JavaScript 代码,对其处理后运行

不需要编译,由解释器实时运行。这样的好处是运行和修改都比较方便,刷新页面就可以重新解释;缺点是每次运行都要调用解释器,系统开销较大,运行速度慢于编译型语言
下面是目前最常见的一些 JavaScript 虚拟机:

Chakra (Microsoft Internet Explorer)
Nitro/JavaScript Core (Safari)
Carakan (Opera)
SpiderMonkey (Firefox)
V8 (Chrome, Chromium)

正文完
 0