浏览器模型

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)

相关推荐