浏览器工作原理和 V8 引擎
一、浏览器的工作原理
比如在浏览器中输入网址,然后 dns 进行解析,解析出的就是服务器的一个 ip 地址。服务器返回一个 html 文件,浏览器内核在解析 html 文件的过程中,遇到 link 标签和 script 标签引用的 css 文件和 JavaScript 文件就会去下载下来。
二、浏览器内核
1. 我们经常会说:不同的浏览器有不同的内核组成:
Gecko:早期被 Netscape 和 Mozilla Firefox 浏览器浏览器使用;
Trident:微软开发,被 IE4~IE11 浏览器使用,但是 Edge 浏览器已经转向 Blink;
Webkit:苹果基于 KHTML 开发、开源的,用于 Safari,Google Chrome 之前也在使用;
Blink:是 Webkit 的一个分支,Google 开发,目前应用于 Google Chrome、Edge、Opera 等;
等等...
2. 事实上,我们经常说的浏览器内核指的是浏览器的排版引擎:
排版引擎(layout engine),也称为浏览器引擎(browser engine)、页面渲染引擎(rendering engine) 或样版引擎。
三、浏览器渲染过程
浏览器内核的 HTML Parse 将 HTML 转化为 DOM 树(DOM Tree),DOM 的 JavaScript 代码可以对 DOM 树(DOM Tree)进行操作(JavaScript 代码是由 JavaScript 引擎执行的)。CSS Parse 将 css 转化为 CSS 规则(Style Rules)。然后 DOM 树(DOM Tree)和 CSS 规则(Style Rules)通过附加(Attachment)生成渲染树(Render Tree),在 布局引擎(Layout)具体操作下,进行绘制(Painting),浏览器就可以进行展示(Dispaly)。之所以需要布局引擎(Layout),是因为浏览器在不同状态下布局有所不同。
四、认识 JavaScript 引擎
1. 为什么需要 JavaScript 引擎呢?
我们前面说过,高级的编程语言都是需要转成最终的机器指令来执行的;
事实上我们编写的 JavaScript 无论你交给浏览器或者 Node 执行,最后都是需要被 CPU 执行的;
但是 CPU 只认识自己的指令集,实际上是机器语言,才能被 CPU 所执行;
所以我们需要 JavaScript 引擎帮助我们将 JavaScript 代码翻译成 CPU 指令来执行;
2. 比较常见的 JavaScript 引擎有哪些呢?
SpiderMonkey:第一款 JavaScript 引擎,由 Brendan Eich 开发(也就是 JavaScript 作者);
Chakra:微软开发,用于 IT 浏览器;
JavaScriptCore:WebKit 中的 JavaScript 引擎,Apple 公司开发;
V8:Google 开发的强大 JavaScript 引擎,也帮助 Chrome 从众多浏览器中脱颖而出;
等等…
3. JavaScript 是一门高级编程语言:
机械语言————>汇编语言————>高级语言
五、浏览器内核和 JS 引擎的关系
这里我们先以 WebKit 为例,WebKit 事实上由两部分组成的:
WebCore:负责 HTML 解析、布局、渲染等等相关的工作;
JavaScriptCore:解析、执行 JavaScript 代码;
另外一个强大的 JavaScript 引擎就是 V8 引擎。
六、V8 引擎原理
1. 我们来看一下官方对 V8 引擎的定义:
V8 是用 C ++编写的 Google 开源高性能 JavaScript 和 WebAssembly 引擎,它用于 Chrome 和 Node.js 等。
它实现 ECMAScript 和 WebAssembly,并在 Windows 7 或更高版本,macOS 10.12+和使用 x64,IA-32, ARM 或 MIPS 处理器的 Linux 系统上运行。
V8 可以独立运行,也可以嵌入到任何 C ++应用程序中。
2. V8 引擎架构
Parse 模块会将 JavaScript 代码转换成 AST(抽象语法树),这是因为解释器并不直接认识 JavaScript 代码
如果函数没有被调用,那么是不会被转换成 AST 的。PreParse(预解析),并不是一开始所有代码都需要执行,所以 V8 引擎就实现了 Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂 时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
Ignition 是一个解释器,会将 AST 转换成 ByteCode(字节码)
同时会收集 TurboFan 优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算); 如果函数只调用一次,Ignition 会执行解释执行 ByteCode;
TurboFan 是一个编译器,可以将字节码编译为 CPU 可以直接执行的机器码
如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过 TurboFan 转换成优化的机器码,提高代码的执行性能; 但是,机器码实际上也会被还原为 ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如 sum 函数原来执行的是 number 类型,后来执行变成了 string 类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码。
七、执行上下文
1. 全局代码执行前的解析(红色框内)
2. 全局代码执行和 foo 函数执行体执行前的解析(红色框内)
3. foo 函数执行体执行(红色框内)
4. bar 函数执行体执行前的解析
5. bar 函数执行体执行
因为 bar 函数内无 name 属性,所以向上到父级作用域中找 (看函数定义时的位置,其所在的上一层作用域为父级作用域,不时看调用位置)。如果在 GO 或之前找到,则输出 name 值,否则报出 undefined。
bar 函数执行体执行完后,则函数执行上下文(FEC)退出 ECS 执行上下文栈;foo 函数执行体执行完后;函数执行上下文(FEC)一样退出 ECS 执行上下文栈。
基于早期 ECMA 的版本规范:每一个执行上下文会被关联到一个变量对象(variable object,VO),在源代码中的变量和函数声明会被作为属性添加到 VO 中。对与函数来说,参数也会被添加到 VO 中。
在最新的 ECMA 的版本规范中,对于一些词汇进行了修改:每一个执行上下文会被关联到一个变量环境(VariableEnvironment),在执行代码中的变量和函数声明会被作为环境记录(Environment Record)添加到变量环境中。对与函数来说,参数也会被环境记录添加到变量环境中。
八、作用域提升面试题
建议:要是像我一样这种基础薄弱的人来说,做题时还是建议画一下执行上下文来理解,这样更加加深印象和理解!!!
最后
如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163 相互学习,我们会有专业的技术答疑解惑
如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点 star:http://github.crmeb.net/u/defu不胜感激 !
PHP 学习手册:https://doc.crmeb.com
技术交流论坛:https://q.crmeb.com
评论