当前位置: 首页 > 网络应用技术

如何执行V8引擎的JavaScript(2)

时间:2023-03-09 13:10:55 网络应用技术

  执行之前,JavaScript引擎需要编译JavaScript代码。编译完成后,它将进入执行阶段。执行阶段是指字节代码的解释器解释阶段,或CPU直接执行二进制计算机代码。

  在此阶段,将宣传通过VAR声明的变量和函数声明。并生成两个部分:执行上下文和可执行代码。

  执行上下文是当JavaScript执行代码时的操作环境。变量促销的内容保留在执行上下文的变量环境的对象中。

  它是为了分析可执行代码并分配在变量环境中促进的变量。执行阶段是从上到下执行的。当遇到相同的变量名称和功能时,它将互相覆盖。

  请分析此代码的结果?

  执行JS代码时,将创建大量执行上下文。每次他创建上下文时,都会被压入呼叫堆栈。

  当我们处于编译阶段时,我们创建了一个全局执行上下文,并将其按下堆栈的底部。但是只有在调用函数时,才会创建该函数以执行上下文并将其按堆栈。

  您可以通过浏览器开发人员工具中的中断点检查呼叫堆栈。您还可以打印当前的呼叫堆栈信息。

  通话堆很大且小。当堆栈的上下文大于一定数字时,JavaScript引擎将报告错误。我们称此错误溢出。

  如何更改以下代码以使其没有堆栈溢出?

  通过计时器。

  蹦床功能

  循环一段时间

  我们知道,要通过二进制代码执行高级别的语言需要编译为字节码或二进制代码。

  该程序的执行本质上是按顺序执行这堆指令的过程。

  首先,让我们了解计算机的硬件组成。首先,在执行程序之前,我们的程序需要安装在内存中。Memory是具有临时存储数据的设备。临时内存之所以是因为内存中的数据将在电源关闭后消失。

  CPU可以从内存地址从内存地址读取数据,或从内存中写入数据。使用内存地址,CPU和存储器可以有序交互。

  当将二进制代码加载到内存中时,内存中的每个二进制代码都有其自己的信件。CPU可以从内存中获取指令(相应的二进制代码),然后分析说明,最后执行指令。

  我们调用三个过程:CPU时钟周期:指令,分析指令和执行指令的三个过程。CPU将始终在这些阶段之间切换。直到执行所有指令。

  PC寄存器,保存要执行的指令地址。将二进制代码加载到内存中时,系统将在PC寄存器中写入二进制代码中的第一个指令的地址。到下一个时钟周期,CPU将基于PC寄存器中的地址从内存中的内存到内存。输出指令。这可以加速CPU的执行速度。由于直接读取和写作记忆,它将认真影响程序的执行性能。

  function foo(){foo()//是否有堆栈溢出错误?} foo()

  函数foo(){return Promise.resolve()。然后(foo)} foo()

  变量A在堆栈上并同时堆积。解析FOO函数时,前分辨率发现内部函数引用外部变量A。目前,将A复制到堆上。在同一时间,修改堆栈上变量A的值。当父函数被破坏时,它将仅在堆栈上销毁变量A,并且将保留堆上的变量A。在执行内部功能后,在执行内部功能后,不会再次引用堆上的变量A,并且它将被垃圾回收。

  为了提高代码的执行速度,早期的V8通过基线编译器将JavaScript源代码直接编译为非优化的二进制机器代码。HOT代码将通过优化的编译器优化,并且优化的机器代码执行效率为更高。

  但是,随着移动设备的流行,V8团队逐渐发现JavaScript源代码直接编译为二进制代码。

  如何提高字节代码的启动速度?解释器可以快速生成字节代码,但是字节码通常不是有效的。相反,尽管优化编译器需要更长的时间,但最终将产生更有效的机器代码,这是确切使用的V8模型。ITS解释器称为点火器(就原始字节代码执行的速度而言)是所有引擎中最快的解释器。V8的优化编译器是Turbofan,最终生成了高度优化的机器代码。

  如何降低代码的复杂性?

  早期的V8代码,无论是基线编译器还是优化的编译器,它们都是基于AST抽象语法树以将代码转换为机器代码的。这意味着基线编译器和优化编译器必须为CPU的不同系统编写不同的代码,这将大大增加代码。

  引入了字节代码,并且可以将字节代码转换为不同平台的二进制代码。

  因为在静态语言中,您可以通过偏移查询直接查询对象的属性值,这是静态语言高执行效率的原因之一。

  因此,在运行JavaScript的过程中,JavaScript中的对象是静态的,并为每个对象创建一个隐藏的类。对象的隐藏类记录对象的布局信息,包括以下两个点:

  在V8中,隐藏类也称为地图。每个对象都有一个MAP属性,其值指向内存中的隐藏类。隐藏的类描述了对象的属性布局,该布局主要包括与属性名称和每个属性相对应的偏移。

  现在我们知道,在V8中,每个对象都有一个MAP属性,该属性指向对象的隐藏类。但是,如果两个对象的形状相同,则V8将重复使用相同的隐藏类。这有两个好处:

  有两种不同形式的回调函数,同步回调和异步回调。同步回调函数在执行函数中执行,并且在执行函数之外执行异步回调函数。

  对于计时器,为了确保可以在指定的时间内执行回调函数,浏览器将将计时器的回调函数添加到另一个消息队列(延迟队列)。

  异步函数将添加到浏览器的消息队列中。当当前主线程未执行代码时,队列恢复函数将在消息队列中取出,然后移交给相应的进程执行。(例如,如果是网络请求,将移交给它到网络流程处理)

  主线程继续执行以下任务。在网络线程的下载过程中,一些中间信息和回调函数将打包到新消息中,将其添加到消息队列中,然后主线程将从中删除回调事件消息队列并执行回调函数。

  宏任务非常简单,它指的是消息队列中的事件,该事件正在等待由主线程执行。执行每个宏任务时,V8将重新创建堆栈,然后随着函数调用宏任务,堆栈还会更改(每个宏任务都在维护其自己范围的任务)

  最后,当执行宏任务时,整个堆栈将再次清除,然后主线程将继续执行下一个宏任务。

  微型任务稍微复杂。实际上,您可以将微任务视为需要异步执行的函数。执行时间是在主函数执行结束之前,当前的宏任务结束了。

  JavaScript中引入微任务的原因

  V8将为每个宏任务维护一个微任务队列(即,如果每个宏任务中都有一个微任务,则将添加到宏任务自己的Micro -Task队列中,这对于判断代码输出很重要,这一点很重要问题)。当V8执行JavaScript的一部分时,它将为此代码创建一个环境对象,并且微型锻炼队列存储在环境中。

  让我们分析此代码的执行过程

  第一步是GC根标记中的活动对象和非活动对象。

  第三步是完成内存。从总体上讲,在频繁回收对象之后,内存中将有大量的不连续空间。我们将这些不连续的内存空间称为内存片段。在内存中出现大量内存片段时,如果需要大的连续内存,则可能发生内存,因此需要从这些内存片段中清除最后一步。此步骤实际上是可选的,因为某些垃圾回收商不会产生内存片段,例如我们要接下来要引入的sub -garbage恢复设备。

  在V8中,桩将分为新一代和旧一代的两个区域。新一代的新一代是短期生存时间的目标,以及长期的生存时间。

  因此,对于上述对象,V8使用两种类型的垃圾回收器。

  侧垃圾回收使用了清除算法,该算法将新一代空间分为两个区域。对象区域的一半,一半是空闲区域。新数据分配在对象区域中。当该区域到处都是物体区域时,垃圾回收将执行垃圾回收操作。之后,将幸存的物体从对象区域复制到空闲区域,两个区域是可以互换的。

  但是,复制操作需要时间成本。如果大一区域的空间设置得太多,那么每个清洁的时间都会太长。因此,为了提高效率,将设置一般新地区的空间。

  正是因为新生区域的空间不大,因此很容易充满容易幸存的整个区域。要解决这个问题,JavaScript引擎采用了对象促进策略,即,仍将通过两次垃圾回收存活,将移至旧区域。

  主垃圾回收器主要负责旧一代垃圾数据的回收运行,并使用Mark -Sweep的算法。在去除过程中可能会生成大量片段,这不利于存储的存储将来大型对象,因此您需要使用Mark -Compact算法来集成内存片段。

  我们知道,垃圾回收正在在JS的主线程上运行,因此,当恢复较大时,JS会堵塞很长时间。

  为了减少旧一代垃圾回收的口吃,V8将标记过程划分为子标记过程,同时允许垃圾回收标记和JavaScript的应用逻辑。信息标记算法。

  旧尚恩区有两个特征,一个是该物体占据了一个巨大的空间,另一个是长期生存的空间。