浏览器工作原理和V8引擎

2,310 阅读6分钟

学习了coderwhy的JavaScript高级语法视频课的笔记

如有错误或者不合适的地方,敬请见谅,欢迎指出和拓展,谢谢各位了

一、浏览器的工作原理

image.png 比如在浏览器中输入网址,然后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) 或样版引擎

三、浏览器渲染过程

image.png 浏览器内核的 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:微软开发,用于IE浏览器;
  • 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 ++应用程序中。 image.png 2. V8引擎架构
  • Parse模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码 如果函数没有被调用,那么是不会被转换成AST的。PreParse(预解析),并不是一开始所有代码都需要执行,所以V8引擎就实现了Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂 时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
  • Ignition是一个解释器,会将AST转换成ByteCode(字节码) 同时会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算); 如果函数只调用一次,Ignition会执行解释执行ByteCode;
  • TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能; 但是,机器码实际上也会被还原为ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是 number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码。

七、执行上下文

<script>
        var name = 'why'
        
        foo(123)
        function foo (num) {
            console.log(m)
            var m = 10
            var n = 20
            
            function bar () {
                console.log(name)
            }
            bar()
            
        }
</script>

1. 全局代码执行前的解析(红色框内) image.png 2. 全局代码执行和foo函数执行体执行前的解析(红色框内) image.png 3. foo函数执行体执行(红色框内) image.png 4. bar函数执行体执行前的解析 image.png 5. bar函数执行体执行 image.png 因为bar函数内无name属性,所以向上到父级作用域中找 (看函数定义时的位置,其所在的上一层作用域为父级作用域,不时看调用位置)。如果在GO或之前找到,则输出name值,否则报出undefined。

  • bar函数执行体执行完后,则函数执行上下文(FEC)退出ECS执行上下文栈;foo函数执行体执行完后;函数执行上下文(FEC)一样退出ECS执行上下文栈。
  • 基于早期ECMA的版本规范:
    • 每一个执行上下文会被关联到一个变量对象(variable object,VO),在源代码中的变量和函数声明会被作为属性添加到VO中。对与函数来说,参数也会被添加到VO中。
  • 在最新的ECMA的版本规范中,对于一些词汇进行了修改:
    • 每一个执行上下文会被关联到一个变量环境(VariableEnvironment),在执行代码中的变量和函数声明会被作为环境记录(Environment Record)添加到变量环境中。对与函数来说,参数也会被环境记录添加到变量环境中。

八、变量提升面试题

建议:要是像我一样这种基础薄弱的人来说,做题时还是建议画一下执行上下文来理解,这样更加加深印象和理解!!!

<script>
        var n = 100

        function foo () {
            n = 100
        }
        foo()

        console.log(n)//100
</script>
<script>
        function foo () {
            console.log(n)//undefined
            var n = 200
            console.log(n)//200
        }

        var n = 100
        foo()
</script>
<script>
        var n = 100

        function foo1 () {
            console.log(n)//100
        }

        function foo2 () {
            var n = 200
            console.log(n)//200
            foo1()
        }

        foo2()
        console.log(n)//100
</script>
<script>
        var n = 100

        function foo () {
            console.log(n)
            return
            var n = 100//undefined
        }

        foo()
</script>
<script>
        function foo () {
            var a = b = 100
            // b=100(该赋值语句在该函数作用域到全局作用域之中找不到,则添加到全局作用域中)
            // var a=100
        }

        foo()

        console.log(a)//报错 a is not defined
        console.log(b)//100
</script>