当前位置: 首页 > article >正文

2024-09-04 深入JavaScript高级语法十五——浏览器原理-V8引擎-js执行原理

目录

  • 1、浏览器的工作原理
    • 1.1、认识浏览器内核
    • 1.2、浏览器渲染过程
  • 2、JS引擎
    • 2.1、认识 JavaScript 引擎
    • 2.2、浏览器内核和JS引擎的关系
    • 2.3、V8引擎的原理
    • 2.4、V8引擎的架构
    • 2.5、V8执行的细节
  • 3、全局代码的执行过程
    • 3.1、初始化全局对象
    • 3.2、执行上下文栈(调用栈)
    • 3.3、全局代码里包含函数时的执行过程
    • 3.4、函数调用函数的执行过程-引出作用域提升的概念
    • 3.5、概念:变量环境和记录
    • 3.6、作用域提升面试题
    • 3.7、vscode中的画图插件

1、浏览器的工作原理

在这里插入图片描述

1.1、认识浏览器内核

  • 我们经常会说:不同的浏览器由不同的内核组成
  • Gecko :早期被 Netscape 和 Mozilla Firefox 浏览器浏览器使用;
  • Trident :微软开发,被IE4~IE11浏览器使用,但是 Edge 浏览器已经转向 Blink ;
  • Webkit :苹果基于 KHTML 开发、开源的,用于 Safari , Google Chrome 之前也在使用;
  • Blink :是 Webkit 的一个分支, Google 开发,目前应用于 Google Chrome 、 Edge 、 Opera 等;
  • 等等…
  • 事实上,我们经常说的浏览器内核指的是浏览器的排版引擎:
  • 排版引擎( layout engine ),也称为浏览器引擎( browser engine )、页面渲染引擎( rendering engine )或样版引擎。

1.2、浏览器渲染过程

  • 在下图的执行过程中, HTML 解析的时候遇到了 JavaScript 标签,应该怎么办呢?
  • 会停止解析 HTML ,而去加载和执行 JavaScript 代码;

在这里插入图片描述

  • 那么, JavaScript 代码由谁来执行呢?
  • JavaScript 引擎

2、JS引擎

2.1、认识 JavaScript 引擎

  • 为什么需要 JavaScript 引擎呢?
  • 我们前面说过,高级的编程语言都是需要转成最终的机器指令来执行的;
  • 事实上我们编写的 JavaScript 无论你交给浏览器或者 Node 执行,最后都是需要被 CPU 执行的;
  • 但是 CPU 只认识自己的指令集,实际上是机器语言,才能被 CPU 所执行;
  • 所以我们需要 JavaScript 引擎帮助我们将 JavaScript 代码翻译成 CPU 指令来执行;
  • 比较常见的 JavaScript 引擎有哪些呢?
  • SpiderMonkey :第一款 JavaScript 引擎,由 Brendan Eich 开发(也就是 JavaScript 作者);
  • Chakra :微软开发,用于 IE 浏览器;
  • JavaScriptCore : WebKit 中的 JavaScript 引擎, Apple 公司开发;
  • V8:Google开发的强大 JavaScript 引擎,也帮助 Chrome 从众多浏览器中脱颖而出;
  • 等等…

2.2、浏览器内核和JS引擎的关系

  • 这里我们先以 WebKit 为例, WebKit 事实上由两部分组成的:
  • WebCore :负责 HTML 解析、布局、渲染等等相关的工作;
  • JavaScriptCore :解析、执行 JavaScript 代码;

在这里插入图片描述

  • 看到这里,学过小程序的同学有没有感觉非常的熟悉呢?
  • 在小程序中编写的 JavaScript 代码就是被 JSCore 执行的;

在这里插入图片描述

  • 另外一个强大的 JavaScript 引擎就是V8引擎。

2.3、V8引擎的原理

  • 我们来看一下官方对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.4、V8引擎的架构

AST解析查看
在这里插入图片描述

  • V8引擎本身的源码非常复杂,大概有超过100w行 C ++代码,通过了解它的架构,我们可以知道它是如何对 JavaScript 执行的:
  • Parse模块会将 JavaScript 代码转换成 AST (抽象语法树),这是因为解释器并不直接认识 JavaScript 代码;
  • 如果函数没有被调用,那么是不会被转换成 AST 的;
  • Parse的V8官方文档:Parse的V8官方文档
  • Ignition 是一个解释器,会将 AST 转换成 ByteCode (字节码)
  • 同时会收集 TurboFan 优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算);
  • 如果函数只调用一次, Ignition 会解析执行 ByteCode ;
  • Ignition的V8官方文档:Ignition的V8官方文档
  • TurboFan 是一个编译器,可以将字节码编译为 CPU 可以直接执行的机器码;
  • 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过 TurboFan 转换成优化的机器码,提高代码的执行性能;
  • 但是,机器码实际上也会被还原为 ByteCode ,这是因为如果后续执行函数的过程中,类型发生了变化(比如 sum 函数原来执行的是 number 类型,后来执行变成了 string 类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码;
  • TurboFan的V8官方文档: TurboFan的V8官方文档

2.5、V8执行的细节

  • 那么我们的 JavaScript 源码是如何被解析( Parse 过程)的呢?
  • Blink 将源码交给V8引擎, Stream 获取到源码并且进行编码转换;
  • Scanner 会进行词法分析( lexical analysis ),词法分析会将代码转换成 tokens ;
  • 接下来 tokens 会被转换成 AST 树,经过 Parser 和 PreParser :
  • Parser 就是直接将 tokens 转成 AST 树架构;
  • PreParser 称之为预解析,为什么需要预解析呢?
    √ 这是因为并不是所有的 JavaScript 代码,在一开始时就会被执行。那么对所有的 JavaScript 代码进行解析,必然会影响网页的运行效率;
    √ 所以V8引擎就实现了 Lazy Parsing (延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
    √ 比如我们在一个函数 outer 内部定义了另外一个函数 inner ,那么 inner 函数就会进行预解析;
  • 生成 AST 树后,会被 lgnition 转成字节码( bytecode ),之后的过程就是代码的执行过程(后续会详细分析)。

在这里插入图片描述

3、全局代码的执行过程

假如我们有下面一段代码,它在JavaScript中是如何被执行的呢?

var name = 'why'
function foo() {
    var name = 'foo'
    console.log(name);
}

var num1 = 20
var num2 = 30
var result = num1 + num2

console.log(result);

foo()

3.1、初始化全局对象

  • js 引擎会在执行代码之前,会在堆内存中创建一个全局对象: Global Object ( GO )口该对象所有的作用域( scope )都可以访问;
  • 该对象所有的作用域( scope )都可以访问;
  • 里面会包含 Date 、 Array 、 String 、 Number 、 setTimeout 、 setInterval 等等;
  • 其中还有一个 window 属性指向自己;

在这里插入图片描述

3.2、执行上下文栈(调用栈)

  • js 引擎内部有一个执行上下文栈( Execution Context Stack ,简称 ECS ),它是用于执行代码的调用栈。
  • 那么现在它要执行谁呢?执行的是全局的代码块:
  • 全局的代码块为了执行会构建一个 Global Execution Context ( GEC );
  • GEC 会被放入到 ECS 中执行;
  • GEC 被放入到 ECS 中里面包含两部分内容:
  • 第一部分:在代码执行前,在 parser 转成 AST 的过程中,会将全局定义的变量、函数等加入到 GlobalObject 中,但是并不会赋值;
    这个过程也称之为变量的作用域提升( hoisting )
    在这里插入图片描述
  • 第二部分:在代码执行中,对变量赋值,或者执行其他的函数;
    在这里插入图片描述

3.3、全局代码里包含函数时的执行过程

  • 在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文( Functional Execution Context ,简称 FEC ),并且压入到 EC Stack 中。
  • FEC 中包含三部分内容:
  • 第一部分:在解析函数成为 AST 树结构时,会创建一个 Activation Object ( AO ):
    √ AO 中包含形参、 arguments 、函数定义和指向函数对象、定义的变量;
  • 第二部分:作用域链:由 VO (在函数中就是 AO 对象)和父级 VO 组成,查找时会一层层查找;
  • 第三部分: this 绑定的值;
    在这里插入图片描述
  • 只有一层函数
var name = 'why'

foo(123)
function foo(num) {
    console.log(m);
    var m = 10
    var n = 20
    console.log('foo');
}

在这里插入图片描述

  • 函数嵌套时
var name = 'why'

foo(123)
function foo(num) {
    console.log(m);
    var m = 10
    var n = 20
    function bar() {
        console.log(name);
    }

    bar()
}

在这里插入图片描述

3.4、函数调用函数的执行过程-引出作用域提升的概念

foo的父级作用域是GO。函数的父级作用域跟它的调用位置无关,只跟它的定义位置有关。

var message = 'hello global'

function foo() {
    console.log(message); // 'hello global'
}

function bar() {
    var message = 'hello bar'
    foo()
}

bar()

在这里插入图片描述

3.5、概念:变量环境和记录

  • 其实我们上面的讲解都是基于早期 ECMA 的版本规范:

Every execution context has associated with it a variable object . Variables and functions declared in the source text are added as properties of the variable object . For function code , parameters are added as properties of the variable object .
每一个执行上下文会被关联到一个变量环境( variable object , VO ),在源代码中的变量和函数声明会被作为属性添加到 VO 中。
对于函数来说,参数也会被添加到 VO 中。

  • 在最新的 ECMA 的版本规范中,对于一些词汇进行了修改:

Every execution context has an associated VariableEnvironment . Variables and functions declared in ECMAScript code evaluated in an execution context are added as bindings in that VariableEnvironment ’ s Environment Record . For function code , parameters are also added as bindings to that Environment Record .
每一个执行上下文会关联到一个变量环境( VariableEnvironment )中,在执行代码中变量和函数的声明会作为环境记录( Environment Record )添加到变量环境中。
对于函数来说,参数也会被作为环境记录添加到变量环境中。

  • 通过上面的变化我们可以知道,在最新的 ECMA 标准中,我们前面的变量对象 VO 已经有另外一个称呼了变量环境 VE 。

3.6、作用域提升面试题

  • 面试题一
var n = 100
function foo() {
    n = 200
}

foo()

console.log(n);
  • 面试题二
function foo() {
    console.log(n);
    var n = 200
    console.log(n);
}

var n = 100
foo()
  • 面试题三
var n = 100
function foo1() {
    console.log(n);
}
function foo2() {
    var n = 200
    console.log(n);
    foo1()
}

foo2()
console.log(n);
  • 面试题四
var a = 100
function foo() {
    console.log(a);
    return
    var a = 100
}
foo()
  • 面试题五
    代码会报错,m is not defined
function foo() {
    var m = 100
}

foo()
console.log(m);
  • 面试题六
    注意:这种写法在严格意义上算是一种语法错误,只是在JS引擎中对这种语法做了特殊处理,可能会出现在面试题中,但实际开发千万不要写这种代码。
function foo() {
    m = 100
}

foo()
console.log(m);
  • 面试题七
function foo() {
    var a = b = 10
    // => 转成下面的两行代码
    // var a = 10
    // b = 10
}

foo()

console.log(a);
console.log(b);

3.7、vscode中的画图插件

在这里插入图片描述


http://www.kler.cn/a/331205.html

相关文章:

  • More Effective C++之技术Techniques,Idioms,Patterns_条款26-27
  • golang 并发--goroutine(四)
  • Vivado常用IP例化2
  • Linux挖矿程序排查
  • Spring-Boot 插件
  • hCaptcha 图像识别 API 对接说明
  • Springboot项目jar包中resource目录下静态资源excel、pdf等二进制文件损坏
  • 跨平台音乐播放器Feishin
  • 位运算(6)_只出现一次的数字 II
  • Build a Large Language Model (From Scratch)学习汇总
  • uni-app运行到 Android 真机和Android studio模拟器
  • three.js 通过着色器实现热力图效果
  • 【项目开发】跨专业合作平台实战(附源码)
  • esp32开发环境搭建和烧录测试
  • 10.2学习
  • Sqoop面试整理
  • LeetCode[中等] 763. 划分字母区间
  • Leecode热题100-75.颜色分类
  • 【AndroidStudio】关于AndroidStudio的常见控件TextView和Button
  • Vue2(十三):路由
  • Java文件上传同时传入JSON参数
  • 软件工程的详细学习要点和学习方向
  • git commit -am 仅提交已修改文件
  • 怎么绕开华为纯净模式安装软件
  • 机器学习篇-day02-KNN算法实现鸢尾花模型和手写数字识别模型
  • Pikachu- SQL Inject - http header 头注入