Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Introduction and usage of open source JavaScript engine V8 developed by Google

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

Google developed by the open source JavaScript engine V8 introduction and use, I believe that many inexperienced people do not know what to do, so this paper summarizes the causes of the problem and solutions, through this article I hope you can solve this problem.

V8 is an open source JavaScript engine developed by Google, also known as a virtual machine, which simulates various functions of a real computer to compile and execute code.

Why do you need a JavaScript engine

When the JavaScript code we write is sent directly to the browser or Node for execution, the underlying CPU is not recognized and cannot be executed. CPU only knows its own instruction set, which corresponds to assembly code. Writing assembly code is a very painful thing. And different types of CPU have different instruction sets, which means that assembly code needs to be rewritten for each CPU.

The JavaScirpt engine can compile JS code into assembly code corresponding to different CPU (Intel, ARM, MIPS, etc.), so we don't have to go through each CPU instruction set manual to write assembly code. Of course, the job of the JavaScript engine is not just to compile code, but also to execute code, allocate memory, and garbage collection.

1000100111011000 # Machine instruction mov ax,bx # Assembly instruction

Data expansion: an introduction to assembly language course [Ruan Yifeng] | understand the "translation" of V8 bytecode

Https://zhuanlan.zhihu.com/p/28590489

Hot JavaScript engine

V8 (Google), written in C++, open source, developed by Google Denmark, is part of Google Chrome and is also used for Node.js.

JavaScriptCore (Apple), open source, for webkit browsers such as Safari, implemented a compiler and bytecode interpreter in 2008 and upgraded to SquirrelFish. Apple's JavaScript engine, code-named "Nitro", is also based on the JavaScriptCore engine.

Rhino, managed by the Mozilla Foundation, open source, written entirely in Java, for HTMLUnit

SpiderMonkey (Mozilla), the first JavaScript engine, was originally used in Netscape Navigator and is now used in Mozilla Firefox.

Chakra (JScript engine) for Internet Explorer.

Chakra (JavaScript engine) for Microsoft Edge.

KJS,KDE 's ECMAScript/JavaScript engine, originally developed by Harry Bolton, is used in the Konqueror web browser of the KDE project.

JerryScript-Samsung's small JavaScript engine for embedded devices.

Other: Nashorn, QuickJS, Hermes

V8

Google V8 engine is an open source high-performance JavaScript and WebAssembly engine written in C + +. It has been used in Chrome, Node.js and so on. It can be run on Windows 7 classic MacOS 10.12 + and Linux systems using x64 maxim 32 Magi arm or MIPS processors. V8 was first developed to be embedded in Google's open source browser Chrome, and the first version was released with the first version of Chrome on September 2, 2008. But V8 is a module that can be run independently and can be embedded in any C + application. The famous Node.js (an asynchronous server framework that can write efficient web servers using JavaScript on the server side) is based on the V8 engine, and Couchbase and MongoDB also use the V8 engine.   

Like other JavaScript engines, V8 compiles / executes JavaScript code, manages memory, is responsible for garbage collection, interacts with the host language, and so on. By exposing host objects (variables, functions, etc.) to JavaScript,JavaScript, you can access objects in the host environment and complete operations on host objects in scripts.

What is D8?

D8 is a very useful debugging tool, and you can think of it as an abbreviation for debug for V8. We can use d8 to view various intermediate data of V8 during JavaScript execution, such as scope, AST, bytecode, optimized binary code, garbage collection status, and use the private API provided by D8 to view some internal information.

Install D8

Method 1: download and compile by yourself

Download and compile V8 google

Official document: Using D8

Method 2: use the compiled d8 tool

Mac platform:

Https://storage.googleapis.com/chromium-v8/official/canary/v8-mac64-dbg-8.4.109.zip

Linux32 platform:

Https://storage.googleapis.com/chromium-v8/official/canary/v8-linux32-dbg-8.4.109.zip

Linux64 platform:

Https://storage.googleapis.com/chromium-v8/official/canary/v8-linux64-dbg-8.4.109.zip

Win32 platform:

Https://storage.googleapis.com/chromium-v8/official/canary/v8-win32-dbg-8.4.109.zip

Win64 platform:

Https://storage.googleapis.com/chromium-v8/official/canary/v8-win64-dbg-8.4.109.zip

/ / extract the file and click d8 to open (if mac security policy is restricted, press and hold control, then click, select Open from the pop-up menu) V8 version 8.4.109 D8 > 1 + 23 D8 > 2 +'4' "24" D8 > console.log (23) 23 undefined D8 > var a = 1 undefined D8 > a + 23 D8 > this [object global] D8 >

The file directory structure of this article for subsequent demo presentations:

V8: # D8 executable D8 icudtl.dat libc++.dylib libchrome_zlib.dylib libicui18n.dylib libicuuc.dylib libv8.dylib libv8_debug_helper.dylib libv8_for_testing.dylib libv8_libbase.dylib libv8_libplatform.dylib obj snapshot_blob.bin v8_build_config.json # newly created js sample file test.js

Method 3: mac

# if you already have HomeBrew, ignore the first command ruby-e "$(curl-fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" brew install v8)

Method 4: use node instead, for example, you can use node-- print-bytecode. / test.js to print out Bytecode (bytecode) generated by Ignition (interpreter).

What d8 commands are available?

View the D8 command

# if you don't want to debug using. / D8, you can add D8 to the environment variable, and then you can directly use `d8-- help`. / D8-- help

Filter specific commands, such as:

# if it is a Windows system and the grep program may be missing, download and install it yourself and add the environment variable. / D8-- help | grep print

Print-bytecode view the generated bytecode

Print-opt-code to view the optimized code

Print-ast to view the AST generated in the middle

Print-scopes to view the scope generated in the middle

Trace-gc to check the memory recovery status of this code.

Trace-opt to see which code has been optimized

Trace-deopt to see which code has been de-optimized

Turbofan-stats prints some statistics of the optimized compiler

Use d8 for debugging

/ / test.js function sum (a) {var b = 6; return a + 6;} console.log (sum (3)); # d8 is followed by the file name and the command to be executed. If you execute the following command, the bytecode generated by the test.js file will be printed. . / D8. / test.js-- print-bytecode # executes the following command to output 9. / D8. / test.js

Internal method

You can also use some of the internal methods provided by V8, just pass in the allow-natives-syntax command when starting V8, and you can use internal methods such as HasFastProperties (to check whether an object has fast properties) in test.js (indexed properties, general properties, fast properties, etc.).

Function Foo (property_num, element_num) {/ / add indexable attribute for (let I = 0; I

< element_num; i++) { this[i] = `element${i}`; } //添加常规属性 for (let i = 0; i < property_num; i++) { let ppt = `property${i}`; this[ppt] = ppt; } } var bar = new Foo(10, 10); // 检查一个对象是否拥有快属性 console.log(%HasFastProperties(bar)); delete bar.property2; console.log(%HasFastProperties(bar));./d8 --allow-natives-syntax ./test.js # 依次打印:true false V8 引擎的内部结构 V8 是一个非常复杂的项目,有超过 100 万行 C++代码。它由许多子模块构成,其中这 4 个模块是最重要的: Parser:负责将 JavaScript 源码转换为 Abstract Syntax Tree (AST) Ignition:interpreter,即解释器,负责将 AST 转换为 Bytecode,解释执行 Bytecode;同时收集 TurboFan 优化编译所需的信息,比如函数参数的类型;解释器执行时主要有四个模块,内存中的字节码、寄存器、栈、堆。 通常有两种类型的解释器,基于栈 (Stack-based)和基于寄存器 (Register-based),基于栈的解释器使用栈来保存函数参数、中间运算结果、变量等;基于寄存器的虚拟机则支持寄存器的指令操作,使用寄存器来保存参数、中间计算结果。通常,基于栈的虚拟机也定义了少量的寄存器,基于寄存器的虚拟机也有堆栈,其区别体现在它们提供的指令集体系。大多数解释器都是基于栈的,比如 Java 虚拟机,.Net 虚拟机,还有早期的 V8 虚拟机。基于堆栈的虚拟机在处理函数调用、解决递归问题和切换上下文时简单明快。而现在的 V8 虚拟机则采用了基于寄存器的设计,它将一些中间数据保存到寄存器中。 基于寄存器的解释器架构: TurboFan:compiler,即编译器,利用 Ignitio 所收集的类型信息,将 Bytecode 转换为优化的汇编代码; Orinoco:garbage collector,垃圾回收模块,负责将程序不再需要的内存空间回收。 其中,Parser,Ignition 以及 TurboFan 可以将 JS 源码编译为汇编代码,其流程图如下:

  

In a nutshell, Parser converts the JS source code to AST, then Ignition converts AST to Bytecode, and finally TurboFan converts Bytecode to optimized Machine Code (actually assembly code).

If the function is not called, V8 will not compile it.

If the function is called only once, Ignition compiles it and Bytecode interprets and executes it directly. TurboFan does not optimize compilation because it requires Ignition to collect type information when the function is executed. This requires that the function needs to be executed at least once before TurboFan can be optimized for compilation.

If the function is called multiple times, it may be recognized as a hot function, and the type information collected by Ignition proves that it can be optimized for compilation, then TurboFan compiles Bytecode to Optimized Machine Code (optimized machine code) to improve the performance of the code.   

The red dotted line in the picture is reversed, which means that Optimized Machine Code will be restored to Bytecode, a process called Deoptimization. This is because the information collected by Ignition may be incorrect, such as the arguments to the add function were integers and then strings. The generated Optimized Machine Code already assumes that the arguments to the add function are integers, which is, of course, wrong, so you need to Deoptimization.

Function add (x, y) {return x + y;} add (3,5); add ('3percent,' 5')

Before running C, C++, Java and other programs, we need to compile, not directly execute the source code; but for JavaScript, we can directly execute the source code (for example: node test.js), which compiles and then executes at run time, which is called just-in-time compilation (Just-in-time compilation), or JIT for short. Therefore, V8 also belongs to the JIT compiler.

How does V8 execute a piece of JavaScript code

Before the advent of V8, all JavaScript virtual machines used interpretive execution, which is one of the main reasons why JavaScript execution is too slow. V8 is the first to introduce the two-wheel drive design of just-in-time compilation (JIT) (mixed use of compiler and interpreter technology), which is a tradeoff strategy, mixing compilation execution and interpretation execution, which greatly improves the execution speed of JavaScript. After the emergence of V8, major manufacturers have introduced JIT mechanism into their JavaScript virtual machines, so JavaScript virtual machines have a similar architecture on the market at present. In addition, V8 also introduced lazy compilation, inline caching, hidden classes and other mechanisms earlier than other virtual machines to further optimize the compilation and execution efficiency of JavaScript code.

Flow chart for V8 to execute a JavaScript:

V8 is essentially a virtual machine, and because the computer can only recognize binary instructions, there are usually two ways for the computer to execute a high-level language:

The first is to convert the high-level code to binary code and let the computer execute it.

Another way is to install an interpreter on the computer and let the interpreter interpret and execute it.

Interpretive execution and compilation execution have their own advantages and disadvantages. Interpretive execution starts fast, but the execution speed is slow, while compilation execution starts slowly, but the execution speed is fast. In order to make full use of the advantages of interpretive execution and compilation execution and avoid its disadvantages, V8 adopts a tradeoff strategy, which adopts the strategy of interpreting execution during startup, but if the frequency of execution of a piece of code exceeds a value, then V8 will use an optimization compiler to compile it into more efficient machine code.

Summary:

The main processes that V8 goes through to execute a piece of JavaScript code include:

Initialize the underlying environment

Parsing the source code to generate AST and scope

Generate bytecode based on AST and scope

Interpreting execution bytecode

Listen for hotspot codes

Optimize the machine code in which the hot code is binary

Anti-optimize the generated binary machine code.

First-class citizens and closures

The definition of first-class citizen

In programming languages, first-class citizens can be used as function parameters, return values as functions, or assign values to variables.

If a function in a programming language can do the same thing as the data type in that language, we call the function in that language a first-class citizen. For example, a string is a first-class citizen in almost all programming languages, a string can be used as a function parameter, a string can be returned as a function, and a string can be assigned to a variable. Functions are not necessarily first-class citizens for various programming languages, such as versions prior to Java 8.

For JavaScript, a function can be assigned to a variable, can be used as a function parameter, and can also be returned as a function, so a function in JavaScript is a first-class citizen.

Dynamic scope and static scope

If the scope of a language is static, then the reference relationship between symbols can be determined according to the program code at compile time and will not change at run time. Where a function is declared, it has the scope of its location. Which variables it can access, then it is bound to those variables, and they can always be accessed at run time. That is, the static scope can be determined by the program code and can be completely determined at compile time. Most languages are static in scope.

Dynamic scope (Dynamic Scope). That is, variable references and variable declarations are not bound at compile time. At run time, it dynamically looks for a variable with the same name in the runtime environment. The bash scripting language used in macOS or Linux is dynamically scoped.

Three basic properties of closures

The JavaScript language allows new functions to be defined within functions.

You can access variables defined in the parent function in the internal function

Because the function in JavaScript is a first-class citizen, the function can be used as the return value of another function

/ closure (static scope, first-class citizen, paradox of the call stack) function foo () {var d = 20; return function inner (a, b) {const c = a + b + d; return c;};} const f = foo ()

With regard to closures, please refer to my previous article. I will not repeat them here. I will mainly talk about the problems that closures bring to Chrome V8 and their solutions.

Lazy parsing   

The so-called lazy parsing means that if the parser encounters a function declaration during parsing, it will skip the code inside the function and will not generate AST and bytecode for it, but only generate the AST and bytecode of the top-level code.

In the process of compiling JavaScript code, V8 does not parse all JavaScript into intermediate code at once, mainly based on the following two points:

First of all, if you parse and compile all the JavaScript code at once, too much code will increase the compilation time, which will seriously affect the speed of executing JavaScript code for the first time and make users feel stuttered. Because sometimes the JavaScript code of a page is very large, if you want to parse and compile all the code at once, it will greatly increase the waiting time for users.

Second, the parsed bytecode and compiled machine code are stored in memory, and if all JavaScript code is parsed and compiled at once, the intermediate code and machine code will always take up memory.

For the above reasons, all mainstream JavaScript virtual machines implement lazy parsing.

The problem with lazy parsing caused by closures: the d above cannot be destroyed with the execution context of the foo function.

Pre-parser

V8 introduces a pre-parser, such as when parsing top-level code and encounters a function, then the pre-parser does not skip the function directly, but does a quick pre-parsing of the function.

Judge whether there are some grammatical errors in the current function, and if a grammatical error is found, a grammatical error will be thrown to V8.

Check whether external variables are referenced inside the function. If external variables are referenced, the pre-parser copies the variables in the stack to the heap, and the next time the function is executed, it directly uses the references in the heap. This solves the problem caused by closures.

How objects are stored internally in V8: fast and slow attributes

What will the following code output:

/ / test.js function Foo () {this [200] = 'test-200'; this [1] =' test-1'; this [100] = 'test-100'; this [' B'] = 'bar-B'; this [50] =' test-50'; this [9] = 'test-9'; this [8] =' test-8'; this [3] = 'test-3'; this [5] =' test-5' This ['D'] = 'bar-D'; this [' C'] = 'bar-C';} var bar = new Foo (); for (key in bar) {console.log (`index:$ {key} value:$ {barkey]} `) } / / output: / / index:1 value:test-1 / / index:3 value:test-3 / / index:5 value:test-5 / / index:8 value:test-8 / / index:9 value:test-9 / / index:50 value:test-50 / / index:100 value:test-100 / / index:200 value:test-200 / / index:B value:bar-B / / index:D value:bar-D / / index:C value:bar-C

The ECMAScript specification defines that numeric attributes should be arranged in ascending order of index values and string attributes in ascending order when they are created. Here we call the numeric attribute in the object a sort property, which is called an elements in V8, and a string property is called a general property, and a properties in V8. In V8, in order to effectively improve the performance of storing and accessing these two attributes, two linear data structures are used to store sorting attributes and general attributes respectively. At the same time, v8 stores some general attributes directly to the object itself, which we call in-object properties, but the number of attributes in the object is fixed, and the default is 10.

Function Foo (property_num, element_num) {/ / add indexable attribute for (let I = 0; I

< element_num; i++) { this[i] = `element${i}`; } //添加常规属性 for (let i = 0; i < property_num; i++) { let ppt = `property${i}`; this[ppt] = ppt; } } var bar = new Foo(10, 10); 可以通过 Chrome 开发者工具的 Memory 标签,捕获查看当前的内存快照。通过增大第一个参数来查看存储变化。 我们将保存在线性数据结构中的属性称之为"快属性",因为线性数据结构中只需要通过索引即可以访问到属性,虽然访问线性结构的速度快,但是如果从线性结构中添加或者删除大量的属性时,则执行效率会非常低,这主要因为会产生大量时间和内存开销。因此,如果一个对象的属性过多时,V8 就会采取另外一种存储策略,那就是"慢属性"策略,但慢属性的对象内部会有独立的非线性数据结构 (字典) 作为属性存储容器。所有的属性元信息不再是线性存储的,而是直接保存在属性字典中。 v8 属性存储: 总结:   因为 JavaScript 中的对象是由一组组属性和值组成的,所以最简单的方式是使用一个字典来保存属性和值,但是由于字典是非线性结构,所以如果使用字典,读取效率会大大降低。为了提升查找效率,V8 在对象中添加了两个隐藏属性,排序属性和常规属性,element 属性指向了 elements 对象,在 elements 对象中,会按照顺序存放排序属性。properties 属性则指向了 properties 对象,在 properties 对象中,会按照创建时的顺序保存常规属性。   通过引入这两个属性,加速了 V8 查找属性的速度,为了更加进一步提升查找效率,V8 还实现了内置内属性的策略,当常规属性少于一定数量时,V8 就会将这些常规属性直接写进对象中,这样又节省了一个中间步骤。   但是如果对象中的属性过多时,或者存在反复添加或者删除属性的操作,那么 V8 就会将线性的存储模式降级为非线性的字典存储模式,这样虽然降低了查找速度,但是却提升了修改对象的属性的速度。 堆空间和栈空间 栈空间 现代语言都是基于函数的,每个函数在执行过程中,都有自己的生命周期和作用域,当函数执行结束时,其作用域也会被销毁,因此,我们会使用栈这种数据结构来管理函数的调用过程,我们也把管理函数调用过程的栈结构称之为调用栈。 栈空间主要是用来管理 JavaScript 函数调用的,栈是内存中连续的一块空间,同时栈结构是"先进后出"的策略。在函数调用过程中,涉及到上下文相关的内容都会存放在栈上,比如原生类型、引用到的对象的地址、函数的执行状态、this 值等都会存在在栈上。当一个函数执行结束,那么该函数的执行上下文便会被销毁掉。 栈空间的最大的特点是空间连续,所以在栈中每个元素的地址都是固定的,因此栈空间的查找效率非常高,但是通常在内存中,很难分配到一块很大的连续空间,因此,V8 对栈空间的大小做了限制,如果函数调用层过深,那么 V8 就有可能抛出栈溢出的错误。 栈的优势和缺点: 栈的结构非常适合函数调用过程。 在栈上分配资源和销毁资源的速度非常快,这主要归结于栈空间是连续的,分配空间和销毁空间只需要移动下指针就可以了。 虽然操作速度非常快,但是栈也是有缺点的,其中最大的缺点也是它的优点所造成的,那就是栈是连续的,所以要想在内存中分配一块连续的大空间是非常难的,因此栈空间是有限的。 // 栈溢出 function factorial(n) { if (n === 1) { return 1; } return n * factorial(n - 1); } console.log(factorial(50000)); 堆空间 堆空间是一种树形的存储结构,用来存储对象类型的离散的数据,JavaScript 中除了原生类型的数据,其他的都是对象类型,诸如函数、数组,在浏览器中还有 window 对象、document 对象等,这些都是存在堆空间的。 宿主在启动 V8 的过程中,会同时创建堆空间和栈空间,再继续往下执行,产生的新数据都会存放在这两个空间中。 继承 继承就是一个对象可以访问另外一个对象中的属性和方法,在 JavaScript 中,我们通过原型和原型链的方式来实现了继承特性。 JavaScript 的每个对象都包含了一个隐藏属性 __proto__ ,我们就把该隐藏属性 __proto__ 称之为该对象的原型 (prototype),__proto__ 指向了内存中的另外一个对象,我们就把 __proto__ 指向的对象称为该对象的原型对象,那么该对象就可以直接访问其原型对象的方法或者属性。   JavaScript 中的继承非常简洁,就是每个对象都有一个原型属性,该属性指向了原型对象,查找属性的时候,JavaScript 虚拟机会沿着原型一层一层向上查找,直至找到正确的属性。 隐藏属性__proto__ var animal = { type: 'Default', color: 'Default', getInfo: function () { return `Type is: ${this.type},color is ${this.color}.`; }, }; var dog = { type: 'Dog', color: 'Black', }; 利用__proto__实现继承: dog.__proto__ = animal; dog.getInfo(); 通常隐藏属性是不能使用 JavaScript 来直接与之交互的。虽然现代浏览器都开了一个口子,让 JavaScript 可以访问隐藏属性 __proto__,但是在实际项目中,我们不应该直接通过 __proto__ 来访问或者修改该属性,其主要原因有两个: 首先,这是隐藏属性,并不是标准定义的; 其次,使用该属性会造成严重的性能问题。因为 JavaScript 通过隐藏类优化了很多原有的对象结构,所以通过直接修改__proto__会直接破坏现有已经优化的结构,触发 V8 重构该对象的隐藏类! 构造函数是怎么创建对象的?  在 JavaScript 中,使用 new 加上构造函数的这种组合来创建对象和实现对象的继承。不过使用这种方式隐含的语义过于隐晦。其实是 JavaScript 为了吸引 Java 程序员、在语法层面去蹭 Java 热点,所以就被硬生生地强制加入了非常不协调的关键字 new。 function DogFactory(type, color) { this.type = type; this.color = color; } var dog = new DogFactory('Dog', 'Black'); 其实当 V8 执行上面这段代码时,V8 在背后悄悄地做了以下几件事情: var dog = {}; dog.__proto__ = DogFactory.prototype; DogFactory.call(dog, 'Dog', 'Black'); 机器码、字节码 V8 为什么要引入字节码 早期的 V8 为了提升代码的执行速度,直接将 JavaScript 源代码编译成了没有优化的二进制机器代码,如果某一段二进制代码执行频率过高,那么 V8 会将其标记为热点代码,热点代码会被优化编译器优化,优化后的机器代码执行效率更高。 随着移动设备的普及,V8 团队逐渐发现将 JavaScript 源码直接编译成二进制代码存在两个致命的问题: 时间问题:编译时间过久,影响代码启动速度; 空间问题:缓存编译后的二进制代码占用更多的内存。 这两个问题无疑会阻碍 V8 在移动设备上的普及,于是 V8 团队大规模重构代码,引入了中间的字节码。字节码的优势有如下三点: 解决启动问题:生成字节码的时间很短; 解决空间问题:字节码虽然占用的空间比原始的 JavaScript 多,但是相较于机器代码,字节码还是小了太多,缓存字节码会大大降低内存的使用。 代码架构清晰:采用字节码,可以简化程序的复杂度,使得 V8 移植到不同的 CPU 架构平台更加容易。 Bytecode 某种程度上就是汇编语言,只是它没有对应特定的 CPU,或者说它对应的是虚拟的 CPU。这样的话,生成 Bytecode 时简单很多,无需为不同的 CPU 生产不同的代码。要知道,V8 支持 9 种不同的 CPU,引入一个中间层 Bytecode,可以简化 V8 的编译流程,提高可扩展性。 如果我们在不同硬件上去生成 Bytecode,会发现生成代码的指令是一样的。 如何查看字节码 // test.js function add(x, y) { var z = x + y; return z; } console.log(add(1, 2)); 运行./d8 ./test.js --print-bytecode: [generated bytecode for function: add (0x01000824fe59 )] Parameter count 3 #三个参数,包括了显式地传入的 x 和 y,还有一个隐式地传入的 this Register count 1 Frame size 8 0x10008250026 @ 0 : 25 02 Ldar a1 #将a1寄存器中的值加载到累加器中,LoaD Accumulator from Register 0x10008250028 @ 2 : 34 03 00 Add a0, [0] 0x1000825002b @ 5 : 26 fb Star r0 #Store Accumulator to Register,把累加器中的值保存到r0寄存器中 0x1000825002d @ 7 : aa Return #结束当前函数的执行,并将控制权传回给调用方 Constant pool (size = 0) Handler Table (size = 0) Source Position Table (size = 0) 3 常用字节码指令: Ldar:表示将寄存器中的值加载到累加器中,你可以把它理解为 LoaD Accumulator from Register,就是把某个寄存器中的值,加载到累加器中。 Star:表示 Store Accumulator Register, 你可以把它理解为 Store Accumulator to Register,就是把累加器中的值保存到某个寄存器中 Add:Add a0, [0]是从 a0 寄存器加载值并将其与累加器中的值相加,然后将结果再次放入累加器。 add a0 后面的[0]称之为 feedback vector slot,又叫反馈向量槽,它是一个数组,解释器将解释执行过程中的一些数据类型的分析信息都保存在这个反馈向量槽中了,目的是为了给 TurboFan 优化编译器提供优化信息,很多字节码都会为反馈向量槽提供运行时信息。 LdaSmi:将小整数(Smi)加载到累加器寄存器中 Return:结束当前函数的执行,并将控制权传回给调用方。返回的值是累加器中的值。 隐藏类和内联缓存 JavaScript 是一门动态语言,其执行效率要低于静态语言,V8 为了提升 JavaScript 的执行速度,借鉴了很多静态语言的特性,比如实现了 JIT 机制,为了提升对象的属性访问速度而引入了隐藏类,为了加速运算而引入了内联缓存。 为什么静态语言的效率更高?   静态语言中,如 C++ 在声明一个对象之前需要定义该对象的结构,代码在执行之前需要先被编译,编译的时候,每个对象的形状都是固定的,也就是说,在代码的执行过程中是无法被改变的。可以直接通过偏移量查询来查询对象的属性值,这也就是静态语言的执行效率高的一个原因。   JavaScript 在运行时,对象的属性是可以被修改的,所以当 V8 使用了一个对象时,比如使用了 obj.x 的时候,它并不知道该对象中是否有 x,也不知道 x 相对于对象的偏移量是多少,也就是说 V8 并不知道该对象的具体的形状。那么,当在 JavaScript 中要查询对象 obj 中的 x 属性时,V8 会按照具体的规则一步一步来查询,这个过程非常的慢且耗时。 将静态的特性引入到 V8 V8 采用的一个思路就是将 JavaScript 中的对象静态化,也就是 V8 在运行 JavaScript 的过程中,会假设 JavaScript 中的对象是静态的。 具体地讲,V8 对每个对象做如下两点假设: 对象创建好了之后就不会添加新的属性; 对象创建好了之后也不会删除属性。 符合这两个假设之后,V8 就可以对 JavaScript 中的对象做深度优化了。V8 会为每个对象创建一个隐藏类,对象的隐藏类中记录了该对象一些基础的布局信息,包括以下两点: 对象中所包含的所有的属性; 每个属性相对于对象的偏移量。 有了隐藏类之后,那么当 V8 访问某个对象中的某个属性时,就会先去隐藏类中查找该属性相对于它的对象的偏移量,有了偏移量和属性类型,V8 就可以直接去内存中取出对应的属性值,而不需要经历一系列的查找过程,那么这就大大提升了 V8 查找对象的效率。 在 V8 中,把隐藏类又称为 map,每个对象都有一个 map 属性,其值指向内存中的隐藏类; map 描述了对象的内存布局,比如对象都包括了哪些属性,这些数据对应于对象的偏移量是多少。 通过 d8 查看隐藏类 // test.js let point1 = { x: 100, y: 200 }; let point2 = { x: 200, y: 300 }; let point3 = { x: 100 }; %DebugPrint(point1); %DebugPrint(point2); %DebugPrint(point3);./d8 --allow-natives-syntax ./test.js# =============== DebugPrint: 0x1ea3080c5bc5: [JS_OBJECT_TYPE] # V8 为 point1 对象创建的隐藏类 - map: 0x1ea308284ce9 [FastProperties] - prototype: 0x1ea308241395 - elements: 0x1ea3080406e9 [HOLEY_ELEMENTS] - properties: 0x1ea3080406e9 { #x: 100 (const data field 0) #y: 200 (const data field 1) } 0x1ea308284ce9: [Map] - type: JS_OBJECT_TYPE - instance size: 20 - inobject properties: 2 - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - stable_map - back pointer: 0x1ea308284cc1 - prototype_validity cell: 0x1ea3081c0451 - instance descriptors (own) #2: 0x1ea3080c5bf5 - prototype: 0x1ea308241395 - constructor: 0x1ea3082413b1 - dependent code: 0x1ea3080401ed - construction counter: 0 # =============== DebugPrint: 0x1ea3080c5c1d: [JS_OBJECT_TYPE] # V8 为 point2 对象创建的隐藏类 - map: 0x1ea308284ce9 [FastProperties] - prototype: 0x1ea308241395 - elements: 0x1ea3080406e9 [HOLEY_ELEMENTS] - properties: 0x1ea3080406e9 { #x: 200 (const data field 0) #y: 300 (const data field 1) } 0x1ea308284ce9: [Map] - type: JS_OBJECT_TYPE - instance size: 20 - inobject properties: 2 - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - stable_map - back pointer: 0x1ea308284cc1 - prototype_validity cell: 0x1ea3081c0451 - instance descriptors (own) #2: 0x1ea3080c5bf5 - prototype: 0x1ea308241395 - constructor: 0x1ea3082413b1 - dependent code: 0x1ea3080401ed - construction counter: 0 # =============== DebugPrint: 0x1ea3080c5c31: [JS_OBJECT_TYPE] # V8 为 point3 对象创建的隐藏类 - map: 0x1ea308284d39 [FastProperties] - prototype: 0x1ea308241395 - elements: 0x1ea3080406e9 [HOLEY_ELEMENTS] - properties: 0x1ea3080406e9 { #x: 100 (const data field 0) } 0x1ea308284d39: [Map] - type: JS_OBJECT_TYPE - instance size: 16 - inobject properties: 1 - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - stable_map - back pointer: 0x1ea308284d11 - prototype_validity cell: 0x1ea3081c0451 - instance descriptors (own) #1: 0x1ea3080c5c41 - prototype: 0x1ea308241395 - constructor: 0x1ea3082413b1 - dependent code: 0x1ea3080401ed - construction counter: 0 多个对象共用一个隐藏类 在 V8 中,每个对象都有一个 map 属性,该属性值指向该对象的隐藏类。不过如果两个对象的形状是相同的,V8 就会为其复用同一个隐藏类,这样有两个好处: 减少隐藏类的创建次数,也间接加速了代码的执行速度; 减少了隐藏类的存储空间。 那么,什么情况下两个对象的形状是相同的,要满足以下两点: 相同的属性名称; 相等的属性个数。 重新构建隐藏类 给一个对象添加新的属性,删除新的属性,或者改变某个属性的数据类型都会改变这个对象的形状,那么势必也就会触发 V8 为改变形状后的对象重建新的隐藏类。 // test.js let point = {}; %DebugPrint(point); point.x = 100; %DebugPrint(point); point.y = 200; %DebugPrint(point);# ./d8 --allow-natives-syntax ./test.js DebugPrint: 0x32c7080c5b2d: [JS_OBJECT_TYPE] - map: 0x32c7082802d9 [FastProperties] ... DebugPrint: 0x32c7080c5b2d: [JS_OBJECT_TYPE] - map: 0x32c708284cc1 [FastProperties] ... DebugPrint: 0x32c7080c5b2d: [JS_OBJECT_TYPE] - map: 0x32c708284ce9 [FastProperties] ... 每次给对象添加了一个新属性之后,该对象的隐藏类的地址都会改变,这也就意味着隐藏类也随着改变了;如果删除对象的某个属性,那么对象的形状也就随着发生了改变,这时 V8 也会重建该对象的隐藏类; 最佳实践 使用字面量初始化对象时,要保证属性的顺序是一致的; 尽量使用字面量一次性初始化完整对象属性; 尽量避免使用 delete 方法。 通过内联缓存来提升函数执行效率 虽然隐藏类能够加速查找对象的速度,但是在 V8 查找对象属性值的过程中,依然有查找对象的隐藏类和根据隐藏类来查找对象属性值的过程。如果一个函数中利用了对象的属性,并且这个函数会被多次执行: function loadX(obj) { return obj.x; } var obj = { x: 1, y: 3 }; var obj1 = { x: 3, y: 6 }; var obj2 = { x: 3, y: 6, z: 8 }; for (var i = 0; i < 90000; i++) { loadX(obj); loadX(obj1); // 产生多态 loadX(obj2); } 通常 V8 获取 obj.x 的流程: 找对象 obj 的隐藏类; 再通过隐藏类查找 x 属性偏移量; 然后根据偏移量获取属性值,在这段代码中 loadX 函数会被反复执行,那么获取 obj.x 的流程也需要反复被执行; 内联缓存及其原理: 函数 loadX 在一个 for 循环里面被重复执行了很多次,因此 V8 会想尽一切办法来压缩这个查找过程,以提升对象的查找效率。这个加速函数执行的策略就是内联缓存 (Inline Cache),简称为 IC; IC 的原理:在 V8 执行函数的过程中,会观察函数中一些调用点 (CallSite) 上的关键中间数据,然后将这些数据缓存起来,当下次再次执行该函数的时候,V8 就可以直接利用这些中间数据,节省了再次获取这些数据的过程,因此 V8 利用 IC,可以有效提升一些重复代码的执行效率。 IC 会为每个函数维护一个反馈向量 (FeedBack Vector),反馈向量记录了函数在执行过程中的一些关键的中间数据。 反馈向量其实就是一个表结构,它由很多项组成的,每一项称为一个插槽 (Slot),V8 会依次将执行 loadX 函数的中间数据写入到反馈向量的插槽中。 当 V8 再次调用 loadX 函数时,比如执行到 loadX 函数中的 return obj.x 语句时,它就会在对应的插槽中查找 x 属性的偏移量,之后 V8 就能直接去内存中获取 obj.x 的属性值了。这样就大大提升了 V8 的执行效率。 单态、多态和超态: 如果一个插槽中只包含 1 个隐藏类,那么我们称这种状态为单态 (monomorphic); 如果一个插槽中包含了 2 ~ 4 个隐藏类,那我们称这种状态为多态 (polymorphic); 如果一个插槽中超过 4 个隐藏类,那我们称这种状态为超态 (magamorphic)。 单态的性能优于多态和超态,所以我们需要稍微避免多态和超态的情况。要避免多态和超态,那么就尽量默认所有的对象属性是不变的,比如你写了一个 loadX(obj) 的函数,那么当传递参数时,尽量不要使用多个不同形状的 obj 对象。 总结: V8 引入了内联缓存(IC),IC 会监听每个函数的执行过程,并在一些关键的地方埋下监听点,这些包括了加载对象属性 (Load)、给对象属性赋值 (Store)、还有函数调用 (Call),V8 会将监听到的数据写入一个称为反馈向量 (FeedBack Vector) 的结构中,同时 V8 会为每个执行的函数维护一个反馈向量。有了反馈向量缓存的临时数据,V8 就可以缩短对象属性的查找路径,从而提升执行效率。但是针对函数中的同一段代码,如果对象的隐藏类是不同的,那么反馈向量也会记录这些不同的隐藏类,这就出现了多态和超态的情况。我们在实际项目中,要尽量避免出现多态或者超态的情况。 异步编程与消息队列 V8 是如何执行回调函数的 回调函数有两种类型:同步回调和异步回调,同步回调函数是在执行函数内部被执行的,而异步回调函数是在执行函数外部被执行的。   通用 UI 线程宏观架构:

  

The UI thread provides a message queue and adds the events to be executed to the message queue, and then the UI thread repeatedly fetches the event from the message queue and executes the event. With regard to asynchronous callbacks, there are also two different types, typical of which are setTimeout and XMLHttpRequest:

The execution process of setTimeout is actually relatively simple. The callback message is encapsulated inside the setTimeout function, and the callback message is added to the message queue. Then the main thread takes the callback event from the message queue and executes the callback function.

XMLHttpRequest is a little more complicated because the download process needs to be executed in a separate thread, so when XMLHttpRequest.send is executed, the host forwards the actual request to the network thread, then the send function exits, and the main thread continues to perform the following tasks. In the process of downloading, the network thread encapsulates some intermediate information and callback functions into new messages and adds them to the message queue, and then the main thread takes the callback event from the message queue and executes the callback function.

Macro task and micro task

Call stack: the call stack is a data structure used to manage the call relationship of functions executed on the main thread. The main thread in the process of executing the task, if the function call level is too deep, may cause stack overflow error, we can use setTimeout to solve the stack overflow problem. The essence of setTimeout is to change synchronous function calls into asynchronous function calls, where the asynchronous call encapsulates the callback function into a macro task and adds it to the message queue, and then the main thread reads the next macro task from the message queue according to certain rules.

Macro task: refers to the event in the message queue waiting to be executed by the main thread. As each macro task is executed, V8 recreates the stack, and then the stack changes as the function is called in the macro task, and eventually, when the macro task ends, the entire stack is emptied again, and then the main thread moves on to the next macro task.

Microtask: you can think of a microtask as a function that needs to be executed asynchronously after the end of the main function and before the end of the current macro task.

The reason why micro-tasks are introduced into JavaScript is that the time granularity of the main thread executing macro tasks in message queue is too coarse to be competent for some scenarios that require high precision and real-time performance. Micro-tasks can make an effective tradeoff between real-time and efficiency. In addition, using microtasks, we can change our current asynchronous programming model so that we can use synchronous code to write asynchronous calls.

Microtasks are based on message queues, event loops, UI main threads and stacks, and then based on microtasks, it can be extended to some technologies commonly used in modern front ends, such as collaboration, Promise, Generator, await/async and so on.

/ / will not cause the browser to jam function foo () {setTimeout (foo, 0);} foo ()

Micro tasks:

/ / the browser console console can jam the browser (unable to respond to mouse events, etc.) function foo () {return Promise.resolve () .then (foo);} foo ()

If a microtask is generated in the current task, either Promise.resolve () or Promise.reject () will trigger the microtask, and the triggered microtask will not be executed in the current function, so * when executing the microtask, it will not cause infinite stack expansion.

Unlike asynchronous calls, microtasks are still executed before the end of the current task execution, which means that other tasks in the message queue cannot be executed until the current microtask execution ends. Therefore, the micro task triggered inside the function must take precedence over the macro task triggered inside the function.

The microtask is still executed in the current task, so if a new microtask is triggered in a loop in the microtask, it will result in no opportunity for other tasks in the message queue to be executed.

History of front-end asynchronous programming solutions

The asynchronous programming model of Callback pattern needs to implement a large number of callback functions. A large number of callback functions will disrupt the normal logic of the code, making the code non-linear and difficult to read. This is what we call the callback hell problem.

Promise can solve the problem of callback hell very well, we can write code in a linear way of thinking, the process is linear, very intuitive.

But this approach is full of Promise's then () method, if the processing flow is more complex, then the whole code will be filled with a lot of then, the semantics is not obvious, the code can not well represent the execution process. We want to write asynchronous code in a linear way, and the most important thing to achieve this ideal is to be able to pause and resume execution of the function. The generator can implement function pause and resume, and we can use the logic of synchronous code in the generator to asynchronize the code (the core of implementing this logic is the co-program).

But in addition to the generator, we also need a trigger to drive the execution of the generator. The final solution of the front end is that async/await,async is a function that can pause and resume execution. Await is used inside the async function to suspend the execution of the async function. Await waits for a Promise object. If the state of the Promise changes to resolve or reject, then the async function will resume execution. Therefore, the goal of writing asynchronous code in a synchronous manner can be achieved using async/await. Like the generator function, a function declared by async is a separate co-program when executed, and we can use await to pause it, and since await is waiting for a Promise object, we can resolve to restore it.

A co-program is a more lightweight presence than a thread. You can think of a co-program as a task running on a thread. There can be more than one co-program on a thread, but only one co-program can be executed on a thread at the same time. For example, the current execution of the A co-program, to start the B co-program, then the A co-program needs to hand over the control of the main thread to the B co-program, which is reflected in that the A co-program suspends execution and the B co-program resumes execution; similarly, A co-program can also be started from the B co-program. In general, if the B co-program is started from the A co-program, we call the A co-program the parent of the B co-program.

Just as a process can have multiple threads, a thread can have multiple collaborators. At a time, the thread can only execute one of the collaborations. Most importantly, the co-program is not managed by the operating system kernel, but is completely controlled by the program (that is, executed in the user mode). The benefit of this is that performance is greatly improved and does not consume as much resources as thread switching.

Garbage collection

Junk data   

Starting from the "GC Roots" object, traverse all the objects in the GC Root, and if you don't traverse through the GC Roots, these objects are junk data. V8 will have a special garbage collector to collect the garbage data.

Garbage collection algorithm

Garbage collection can be divided into the following steps:

The first step is to mark active and inactive objects in the space through GC Root. At present, V8 uses the accessibility (reachability) algorithm to determine whether the object in the heap is active. Specifically, this algorithm takes some GC Root as a collection of initial living objects, starting from the GC Roots object, traversing all the objects in the GC Root:

Global window object (in each iframe)

Document DOM tree, consisting of all native DOM nodes that can be reached by traversing the document

Store variables on the stack.

Through the GC Root traversal of the object, we think that the object is accessible (reachable), then we must ensure that these objects should be kept in memory, we also call accessible objects as active objects.

Objects that are not traversed through GC Roots are inaccessible (unreachable), then these inaccessible objects may be recycled, and we call inaccessible objects inactive objects.

In a browser environment, there are many GC Root, which usually include the following (but not just a few):

The second step is to reclaim the memory occupied by inactive objects. In fact, after all the tags are completed, uniformly clean up all the objects in memory that are marked as recyclable.

The third step is to clean up the memory. Generally speaking, after frequently recycling objects, there will be a lot of discontiguous space in memory, which we call memory fragmentation. When there are a large number of memory fragments in memory, it is possible to run out of memory if you need to allocate large contiguous memory, so the last step is to defragment these memory fragments. But this step is actually optional because some garbage collectors do not produce memory fragments (such as secondary garbage collectors).

Garbage collection

According to the intergenerational hypothesis, V8 divides the heap memory into two regions: the Cenozoic generation and the old generation. The objects with short survival time are stored in the new generation, and the objects with long survival time are stored in the old generation. The intergenerational hypothesis has two characteristics:

The first is that most objects are "life-and-death", that is, most objects live in memory for a short time, such as variables declared within a function, or variables in a block-level scope. when the execution of the function or block of code ends, the variables defined in the scope are destroyed. As a result, once memory is allocated, such objects quickly become inaccessible.

The second is immortal objects that live longer, such as global window, DOM, Web API, and so on.

In order to improve the efficiency of garbage collection, V8 sets up two garbage collectors, the main garbage collector and the secondary garbage collector.

The main garbage collector is mainly responsible for garbage collection in the old generation. In addition to those who are promoted in the new generation, some large objects will be assigned directly to the older generation.

The object in the old age has two characteristics: one is that the object takes up a lot of space, and the other is that the object lives for a long time.

This role flipping operation also allows these two areas in the new generation to be reused indefinitely.

Every time the sub-garbage collector performs a cleaning operation, it needs to copy the surviving objects from the object area to the idle area, which takes time. If the space of the new area is set too large, then the cleaning time of each time will be too long. Therefore, for the sake of efficiency, the space of the new area is generally set to be relatively small.

The sub-garbage collector also uses an object promotion strategy, that is, moving objects that are still alive after two garbage collections to the older generation.

The main garbage collector is responsible for collecting garbage data from the old generation, and the secondary garbage collector is responsible for collecting garbage data from the new generation.

The sub-garbage collector uses the Scavenge algorithm, which divides the Cenozoic space into two regions (also known as From and To spaces in some places), half object areas and half idle areas. The new data is allocated to the object area, and when the object area is almost full, the garbage collector performs a garbage collection operation, then copies the surviving objects from the object area to the free area, and swaps the two areas.

The main garbage collector is mainly responsible for the collection of garbage data in the old age, which will go through the process of marking, cleaning and sorting.

Stop-The-World

Because JavaScript runs on the main thread, once the garbage collection algorithm is executed, you need to pause the executing JavaScript script and resume the script execution after the garbage collection is complete. We call this behavior Stop-The-World.

The original V8 garbage collector has two features:

The first is that garbage collection is performed on the main thread

The second feature is to execute a complete garbage collection process at a time.

Because of these two reasons, it is easy to cause stutters in the main thread, so V8 adopts a lot of schemes to optimize the execution efficiency.

The first scenario is parallel collection, in which the garbage collector uses multiple worker threads to perform garbage collection in parallel during a complete garbage collection.

The second scenario is incremental garbage collection, in which the garbage collector breaks down the marking work into smaller blocks and performs it between tasks with different main threads. With incremental garbage collection, it is not necessary for the garbage collector to perform a complete garbage collection process at a time, only a small part of the entire garbage collection process is performed at a time.

The third scheme is concurrent collection, the collection thread is performing the process of JavaScript, and the helper thread can perform garbage collection operations in the background.

The main garbage collector adopts all the schemes (concurrent marking, incremental marking, auxiliary cleaning), and the secondary garbage collector also adopts part of the scheme.

It seems that this star is not last night, for whom the wind stands in the middle of the night

Breaking the JavaScript Speed Limit with V8   

Daniel Clifford gave a wonderful speech "Breaking the JavaScript Speed Limit with V8" on Google Imax O 2012. In his presentation, he explained in depth 13 simple code optimization methods that can make your JavaScript code faster when the Chrome V8 engine compiles / runs. In his speech, he introduced how to optimize and explained why. Here are a concise list of 13 JavaScript performance improvement tips:

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

Initialize the members of all objects in the constructor (so these instances do not change their hidden classes after that)

Always initialize object members in the same order

Try to use numbers that can be represented by 31-bit signed integers

Use consecutive primary keys starting at 0 for an array

Don't pre-allocate a large array (for example, more than 64K elements) to its maximum size, just let its size develop naturally.

Don't delete elements from an array, especially an array of numbers.

Do not load uninitialized or deleted elements

For fixed-size arrays, initialize with "array literals" (initialize small fixed-length arrays with literals)

The decimal array (less than 64k) is pre-assigned the correct size before use.

Do not store non-numeric values (objects) in a numeric array

Try to use a single type (monomorphic) instead of multiple types (polymorphic) (if you initialize a decimal array with a non-literal quantity, do not trigger a type reconversion)

Do not use try {} catch {} (if there is fast try/catch code, put performance-sensitive code in a nested function)

Avoid modifying hidden classes in methods after optimization.

5 techniques for optimizing code in V8 engine

1. Order of object properties: be sure to use the same order when instantiating your object properties so that hidden classes and subsequent optimized code can be shared.

two。 Dynamic attributes: adding attributes after the object is instantiated forces changes to the hidden class and slows down the execution of code optimized for the old hidden class. Therefore, you need to assign all properties in the constructor of the object.

3. Method: repeating the same method will run faster than executing different methods only once (because of inline caching)

4. Array: avoid using sparse arrays where keys is not an incremental number. Such a sparse array whose key value is not an incremental number is actually an hash table. The acquisition of each element in this array is expensive. At the same time, avoid applying for large arrays in advance. The best thing to do is to slowly increase the array as you need it. Finally, do not delete elements from the array, as this will make keys sparse

5. Tag value (Tagged values): V8 uses 32 bits to represent objects and numbers. It uses one bit to distinguish whether it is an object (flag = 1) or an integer (flag = 0). It is also called a small integer (SMI) because it has only 31 bits. Then, if a value is greater than 31 bits, V8 will box it, convert it to double, and create a new object to hold the number. Therefore, in order to avoid costly box operations, try to use 31-bit signed numbers.

Bottleneck Analysis and solution of JavaScript Startup performance

Reference:

JavaScript Start-up Performance:

Https://medium.com/reloading/javascript-start-up-performance-69200f43b201

JavaScript startup performance bottleneck: analysis and solution:

Https://zhuanlan.zhihu.com/p/25221314

When there is a shortage of silk and cocoon peeling, V8 will last forever.

V8 official documentation (https://v8.dev/)

Diagram Google V8 (https://time.geekbang.org/column/intro/296)

Browser working principle and practice (https://time.geekbang.org/column/intro/216)

How JavaScript works: an overview of engine, runtime, call stack]: https://juejin.im/post/6844903510538993671)

How JavaScript works: the rise of event loops and asynchronous programming + 5 tips on how to write better in async/await. (https://juejin.im/post/6844903518319411207)

An extra piece

Console Importer:Easily import JS and CSS resources from Chrome console. (you can install loadsh, moment, jQuery and other libraries in the browser console, and verify and use these libraries directly in the console. )

Effect picture:

After reading the above, have you mastered the introduction of the open source JavaScript engine V8 developed by Google and how to use it? If you want to learn more skills or want to know more about it, you are welcome to follow the industry information channel, thank you for reading!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report