Author: Au1ge
Blog : http://www.au1ge.xyz/
文章由作者Au1ge整理博客后首发先知社区
下篇传送门: 从Chrome源码看JavaScript的执行(下)

缘由

来源于ph师傅小密圈里的一个问题

例子1:

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
<div id="id"></div>
<script>
var a="<script>alert(1)</s"+"cript>";
//var a="<img src=1 onerror=alert(1)>";
document.getElementById("id").innerHTML=a;
</script>
</body>
</html>

这段代码,虽然成功插入了script标签,但是没有执行,如果改为插入img标签则可以执行

首先的猜想是:

onerror 事件在 DOM 树构建完成之后触发,而 script 标签内的内容在 DOM 解析中触发,但是可以看一下这个例子

例子2:

<script type="text/javascript" id="id2"></script>
<script>
  var a = "alert(/script/)";
  document.getElementById("id2").innerText = a;
</script>

这里正确执行了JS,说明浏览器在解析并执行第二个 script 标签时,回头执行了第一个 script 标签内的内容,而第一个 script 标签此时已经成功加入了 DOM 树里,这可能是因为浏览器在遇到 script 标签,或者对 script 标签进行 DOM 操作的时候,会刷新一次页面渲染,因为浏览器在碰到 script 标签的时候,需要渲染页面保证 script 能获取到最新的 DOM 元素信息,可以看下这个:链接:原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的 - 掘金

所以,最开始的例子是对 div 元素进行的 DOM 操作,浏览器不会再触发页面渲染并执行动态创建的 script,创建的 script 无法在解析中执行,关于浏览器的执行流程可以看下这个:链接:浏览器的工作原理:现代网络浏览器幕后揭秘 - HTML5 Rocks

再看一个例子:

<div id="id"></div>
<script>
var a = document.createElement('script');
a.innerText = 'alert(/xx/)';
document.getElementById("id").appendChild(a);
</script>

这个例子里JS成功触发,于是可能最开始的猜想就是正确的,对script标签的DOM操作,无论他是被操作还是操作,都会进行一次渲染,那么这个怎么用正确的术语描述呢?

掘金那篇文章的一个评论

可以翻墙的看看下面这篇文章: https://developers.google.com/web/fundamentals/performance/critical-rendering-path/?hl=zh-cn CSSOM树和DOM树是分开构建,之所以把link标签放抬头而script放body尾部,是因为浏览器遇到script标签时,会去下载并执行js脚本,从而导致浏览器暂停构建DOM。然而JS脚本需要查询CSS信息,所以JS脚本还必须等待CSSOM树构建完才可以执行。 这将相当于CSS阻塞了JS脚本,JS脚本阻塞了DOM树构建。是这样子的关联才对。 只要设置CSS脚本提前加载避免阻塞JS脚本执行时CSSOM树还没构建好,同时给script标签设置async就可以解决这个问题

https://developers.google.com/web/fundamentals/?hl=zh-cn 这个貌似比较详细的解释了浏览器

简言之,JavaScript 在 DOM、CSSOM 和 JavaScript 执行之间引入了大量新的依赖关系,从而可能导致浏览器在处理以及在屏幕上渲染网页时出现大幅延迟:

- 脚本在文档中的位置很重要。
- 当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行。
- JavaScript 可以查询和修改 DOM 与 CSSOM。
- JavaScript 执行将暂停,直至 CSSOM 就绪。

“优化关键渲染路径”在很大程度上是指了解和优化 HTML、CSS 和 JavaScript 之间的依赖关系谱。

看了一些文章,了解了reflow和repaint的概念,可能需要重新审视最开始的代码为什么没有进入JS环境

毫无疑问,innerHTML会执行回流操作,并且 innerHTML 中的东西会被加入DOM树,

innerHTML会解析其中的 html 代码并将其加入 DOM 树:https://developer.mozilla.org/en-US/docs/Web/API/DOMParser

那么就有点奇怪,如果 innerHTML 使 script 加入了 DOM 树,并且触发了回流操作,那么为什么不会执行呢?

同样的,innerText给 script 标签加入代码,也会回流(reflow) DOM 树,并且执行了 JS 代码

需要调试一下chrome,确定他什么时候会进入JS环境

Chrome是如何执行JavaScript的

对于例子1和例子2,猜测他们存在一个分流点,例子1进入了不能调用JS引擎的分支,例子2则进入了能够调用JS引擎的分支,要找到这个地方,就需要找到调用JS引擎的入口函数,从而回溯调用链,为了找到这个入口函数,我们先调试一个简单的例子

<script>alert(1)</script>

第一个断点断在 HTMLTreeBuilder::ProcessToken(AtomicHTMLToken* token) 这个函数上,看一下从浏览器打开页面到开始处理token之间经过了什么,这里不是我们这篇文章的重点,所以准备一笔带过,关于token是什么以及浏览器如何构建DOM树,可以看这篇文章:https://zhuanlan.zhihu.com/p/24911872

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖