浏览器环境概述

浏览器环境概述

1. 代码嵌入网页的方法

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

  1. <script>元素直接嵌入代码。
<script id="mydata" type="x-custom-data">
  console.log('Hello World');
</script>
<!-- 可以使用<script>节点的text属性读出它的内容,<script>存在于DOM中 -->
document.getElementById('mydata').text
  1. <script>标签加载外部脚本
<!-- script标签允许设置一个integrity属性,写入该外部脚本的 Hash 签名,用来验证脚本的一致性。-->
<script src="/assets/application.js"
  integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs=">
</script>
  1. 事件属性,(比如onclick和onmouseover)
<button id="myBtn" onclick="console.log(this.id)">点击</button>
  1. URL 协议
<a href="javascript: void new Date().toLocaleTimeString();">点击</a>
<a href="javascript: new Date().toLocaleTimeString();void 0;">点击</a>
<!-- 在脚本前加上void,或者在脚本最后加上void 0,可以防止书签替换掉当前文档。 -->

2. script 元素

2.1 工作原理

正常的网页加载流程

  1. 浏览器一边下载 HTML 网页,一边开始解析。也就是说,不等到下载完,就开始解析。
  2. 解析过程中,浏览器发现<script>元素,就暂停解析,把网页渲染的控制权转交给 JavaScript 引擎。
  3. 如果<script>元素引用了外部脚本,就下载该脚本再执行,否则就直接执行代码 。
  4. JavaScript 引擎执行完毕,控制权交还渲染引擎,恢复往下解析 HTML 网页。

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

    如果外部脚本加载时间很长(一直无法完成下载),那么浏览器就会一直等待脚本下载完成,造成网页长时间失去响应,浏览器就会呈现“假死”状态,这被称为阻塞效应。同样解析和执行 CSS,也会产生阻塞。
    为了避免这种情况,较好的做法是将<script>标签都放在页面底部,而不是头部,如下:

<body>
  <!-- 其他代码  -->
  <script>
    console.log(document.body.innerHTML);
  </script>
</body>

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

2.2 defer 属性

    为了解决脚本文件下载阻塞网页渲染的问题,一个方法是对<script>元素加入defer属性。它的作用是延迟脚本的执行,等到 DOM 加载生成后,再执行脚本。且执行顺序就是它们在页面上出现的顺序。

<script src="a.js" defer></script>
<script src="b.js" defer></script>

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

2.3 async 属性

    解决“阻塞效应”的另一个方法是对<script>元素加入async属性。会在解析 HTML 网页同时并行下载<script>标签中的外部脚本,脚本下载完成,浏览器暂停解析 HTML 网页,开始执行下载的脚本。脚本执行完毕,浏览器恢复解析 HTML 网页。

<script src="a.js" async></script>
<script src="b.js" async></script>

    async属性可以保证脚本下载的同时,浏览器继续渲染。需要注意的是,一旦采用这个属性,就无法保证脚本的执行顺序。哪个脚本先下载结束,就先执行那个脚本。另外,使用async属性的脚本文件里面的代码,不应该使用document.write方法。

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

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

3. 浏览器的组成

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

3.1 渲染引擎

渲染引擎的主要作用是,将网页代码渲染为用户视觉可以感知的平面文档

不同的浏览器有不同的渲染引擎。

  • Firefox:Gecko 引擎
  • Safari:WebKit 引擎
  • Chrome:Blink 引擎
  • IE: Trident 引擎
  • Edge: EdgeHTML 引擎

渲染引擎处理网页,通常分成四个阶段。

  1. 解析代码:HTML 代码解析为 DOM,CSS 代码解析为 CSSOM(CSS Object Model)。
  2. 对象合成:将 DOM 和 CSSOM 合成一棵渲染树(render tree)。
  3. 布局:计算出渲染树的布局(layout)。
  4. 绘制:将渲染树绘制到屏幕。

    以上四步并非严格按顺序执行,往往第一步还没完成,第二步和第三步就已经开始了。所以,会看到这种情况:网页的 HTML 代码还没下载完,但浏览器已经显示出内容了。

3.2 重流和重绘

    渲染树转换为网页布局,称为“布局流”(flow);布局显示到页面的这个过程,称为“绘制”(paint)。它们都具有阻塞效应,并且会耗费很多时间和计算资源。
    页面生成以后,脚本操作和样式表操作,都会触发“重流”(reflow)和“重绘”(repaint)。用户的互动也会触发重流和重绘,比如设置了鼠标悬停(a:hover)效果、页面滚动、在输入框中输入文本、改变窗口大小等等。
    重流和重绘并不一定一起发生,重流必然导致重绘,重绘不一定需要重流。比如改变元素颜色,只会导致重绘,而不会导致重流;改变元素的布局,则会导致重绘和重流。
大多数情况下,浏览器会智能判断,将重流和重绘只限制到相关的子树上面,最小化所耗费的代价,而不会全局重新生成网页。
    作为开发者,应该尽量设法降低重绘的次数和成本。比如,尽量不要变动高层的 DOM 元素,而以底层 DOM 元素的变动代替;再比如,重绘table布局和flex布局,开销都会比较大。

下面是一些优化技巧。

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

学习地址:https://wangdoc.com/javascript/bom/engine.html

发布了10 篇原创文章 · 获赞 2 · 访问量 1278
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 游动-白 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览