JavaScript 浏览器原理(二)

208 阅读5分钟

JavaScript 浏览器原理(二)

推荐阅读

How browsers work: behind the scenes of modern web browsers

As a web developer, learning the internals of browser operations helps you make better decisions and know the justifications behind development best practices. While this is a rather lengthy document, we recommend you spend some time digging in; we guarantee you’ll be glad you did. Paul Irish, Chrome Developer Relations

The browser main functionality is to present the web resource you choose, by requesting it from the server and displaying it on the browser window. The resource is usually an HTML document, but may also be a PDF, image, or other type. The location of the resource is specified by the user using a URI (Uniform resource Identifier).

image-20241107073738278.png

JavaScript script元素和页面的解析关系

上一篇中我们谈到,浏览器遇到了从外部实现 link 的CSS之后,回去实现单独的下载,但是是不会影响接下来的继续解析的

但是我们的 JS 代码就有所不同了,当遇到了我们的 JS 代码的时候,整个网页就不会继续解析,而是停止下来,等待JS代码

被解析完后,才实现进一步的解析,会造成整个网页的解析过程的阻塞

也就是说明浏览器实现解析HTML 的过程中,遇到了我们的 script 元素是不会参与构成构建 DOM Tree 的

他会停止 DOM Tree 继续构建,首先下载 JavaScript 代码,并且实现执行 javaScript 脚本

只有等到我们的 JavaScript 脚本被执行完后,才会继续解析 HTML ,继续构建 DOM Tree

我们这个时候是可以给我们的 JavaScript 代码添加一个 debugger 来实现认为的添加一个断点,让后续的代码不在执行

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .box {
            width: 100px;
            height: 100px;
            background-color: orange;
        }
    </style>
</head>
<body>
<script>
    debugger;
    var boxEl = document.querySelector(".box");
    console.log(boxEl)
</script><div class="box"></div>
</body>
</html>

image-20241107071451772.png

这样做的原因是什么耶???

  • javaScript 元素的最大的作用之一就是实现操作我们的 DOM,并且实现对DOM 的修改或者其他的操作
  • 等到DOM Tree 实现了构建完成并且渲染后再执行javaScript,会导致严重的回流和重绘,影响页面的性能
  • 遇到了 script 元素后,优先下载和执行 JavaScript 代码,再实现构建 DOM Tree

这种操作模式还会出现一个新的问题:

  • 在我们的当前的开发模式中(Vue React Angular),脚本往往比我们的 HTML 页面更重要,处理时间更长
  • 如果一遇到了我们的 script 元素就是造成页面的阻塞,那么在脚本实现下载并且解析执行完之前,用户是看不见任何界面的
  • 或者说用户观看到的页面只是一部分页面而已
  • 所以说现在的开发模式就是实现操作的是我们的 虚拟 DOM,而不是真真的 DOM,减少对构建的 DOM Tree 的后续修改

前端现代的开发模式

  • 在我们的 index.html 中只是书写一个容器就行了: <div id="app"></div>
  • 然后通过对这个容器进行操作添加内容,然后就完成了前端的具体开发

为了解决这个问题,我们的 script 就添加了两种解析网页的模式 defer 和 async

JavaScript script的 defer 解析模式

At this stage the browser will mark the document as interactive and start parsing scripts that are in “deferred” mode - those who should be executed after the document is parsed. The document state will be then set to “complete” and a “load” event will be fired.

You can see the full algorithms for tokenization and tree construction in HTML5 specification

The model of the web is synchronous. Authors expect scripts to be parsed and executed immediately when the parser reaches a

defer 通过上面的问藏阅读,我们就可以初步的了解到一点: 这种解析模式实现的是让我们的脚本独立进行下载,页面继续

构建我们的 DOM Tree

  • 脚本独立的被浏览器下载,同时不阻塞 DOM Tree 的继续构建
  • 如果说脚本是提前下载好的,那么他就会等待DOM Tree的构建完成,在 DOMContentLoaded 事件之前先执行defer中的JS代码
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .box {
            width: 100px;
            height: 100px;
            background-color: orange;
        }
    </style>
</head>
<body>
<div class="box"></div><script src="./cutdown.js" defer>
</script><div class="box"></div>
</body>
</html>

这个时候,我们的脚本实现的是就是不会阻塞DOM Tree 的构建,实现继续的渲染页面

但是在 defer 里面代码的执行会在网页中的内容加载完毕之前实现运行完的

还是那一条原则:在 DOM Tree 构建完成之前,任何可以引起 DOM Tree发生改变的操作都先执行完

防止造成后续对 DOM Tree 的再次操作,引起回流和重绘,影响代码的性能

使用 defer 的一些注意事项

理解的时候,我们还需要注意的一点就是: JS代码的解析肯定是比我们的 解析HTML要慢很多的

  • defre 会告诉浏览器脚本独立进行下载,但是 DOM Tree 还是继续创建

  • 在 onload 和 DOMContentLoaded 实现调用前,defer 中的 JS 代码实现运行

  • defer 从某种角度而言的话,是可以提高页面的性能的,推荐将使用 defer 的 JS 脚本代码的引入放置于 head 中

    • 注意: defer 只是适用于引入外部 JS 脚本, 对于默认的 script 其会被忽略

JavaScript script中的async解析模式

浏览器不会因 async 脚本的而阻塞(用法和 defer 类似)

async 是不保证运行顺序的,他是独立实现下载,独立运行,不会等待其他脚本的运行

async 不会保障在 DOMContentLoaded 之前或者之后实现运行

就是实现的是让我们的脚本和脚本之间实现完全独立,不相互干扰,执行顺序不受控制

在我们实现选择的时候,我们的选择的话,选择 defer 是更加保险很多的

然后的话,使用我们的 async ,就在实现一些完全独立的功能,JS代码和DOM 完全不依赖的时候的一些脚本的书写

反正就是一定要保证,实现运行 JS 代码后,DOM Tree 才全部构建完成