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

The operating principle of JavaScript engine

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

Share

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

This article mainly explains "the operation principle of JavaScript engine". The content of the explanation is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn "the operation principle of JavaScript engine".

Some nouns

JS engine-an engine that reads instead of code and runs, without a single "JS engine"; each browser has its own engine, such as Google has V.

Scope-the "area" from which variables can be accessed.

Lexical scope-the scope at the lexical stage, in other words, the lexical scope is determined by where you write the variables and blocks when you write the code, so the scope remains the same when the lexical analyzer processes the code.

Block scope-range created by curly braces {}

Scope chain-the function can rise to its external environment (lexically) to search for a variable, and it can keep looking up until it reaches the global scope.

Synchronization-performs one thing at a time, the synchronization engine executes only one line at a time, and the JavaScript is synchronized.

Asynchronous-doing multiple things at once, JS simulates asynchronous behavior through the browser API

Event loop (Event Loop)-the browser API completes the process of calling the function, pushing the callback function to the callback queue (callback queue), and then pushing the callback function to the call stack when the stack is empty.

Stack-A data structure that can only push elements in and out of the top elements. Think of stacking a zigzag tower; you can't delete the middle block, last in, first out.

Heap-variables are stored in memory.

Call stack-the queue for function calls, which implements the stack data type, which means that you can run one function at a time. The calling function pushes it onto the stack and returns from the function to pop it off the stack.

Execution context-the environment created by JS when the function is placed on the call stack.

Closure-when a function is created within another function, it "remembers" the environment it creates when it is called later.

Garbage collection-when a variable in memory is automatically deleted, the engine disposes of it because it is no longer in use.

Variable promotion-when the variable memory is not assigned, it is promoted to the top of the global and set to undefined.

This-variables / keywords automatically created by JavaScript for each new execution context.

Call stack (Call Stack)

Look at the following code:

Var myOtherVar = 10function a () {console.log ('myVar', myVar) b ()} function b () {console.log (' myOtherVar', myOtherVar) c ()} function c () {console.log ('Hello worldview')} a () var myVar = 5

There are a few points to note:

The location of the variable declaration (one at the top and the other at the bottom)

Function a calls function b defined below, and function b calls function c

What do you expect to happen when it is executed? Did an error occur because b declares after an or everything is fine? What about the variables printed by console.log?

The following is the print result:

"myVar" undefined "myOtherVar" 10 "Hello world!"

To break down the above implementation steps.

1. Variable and function declarations (creation phase)

The first step is to allocate space for all variables and functions in memory. Note, however, that the variable has not been assigned a value with the exception of undefined. Therefore, the value of myVar when printed is undefined, because the JS engine executes code line by line from the top.

Unlike variables, functions can be declared and initialized at once, which means they can be called anywhere.

So the above code looks like this:

Var myOtherVar = undefinedvar myVar = undefinedfunction a () {...} function b () {...} function c () {...}

These all exist in the global context created by JS because it is in the global space.

In the global context, JS also added:

Global objects (window objects in browsers and global objects in NodeJs)

This points to the global object

two。 Execution

Next, the JS engine executes the code line by line.

MyOtherVar = 10 in the global context, myOtherVar is assigned to 10

Now that all the functions have been created, the next step is to execute the function a ()

Each time a function is called, a new context is created for the function (repeat step 1) and placed on the call stack.

Function a () {console.log ('myVar', myVar) b ()}

The steps are as follows:

Create a new function context

No variables and functions are declared in the a function

This is created inside the function and points to the global object (window)

Then it refers to the external variable myVar,myVar that belongs to the global scope.

Then call function b, the process of function b is the same as a, there is no analysis here.

The following is an execution diagram of the call stack:

Create global contexts, global variables, and functions.

Each function call creates a context, a reference to the external environment, and a this.

When the function is finished, it pops up from the stack, and its execution context is reclaimed by garbage collection (except closures).

When the call stack is empty, it gets the event from the event queue.

Scope and scope chain

In the previous example, everything is globally scoped, which means that we can access it from anywhere in the code. Now, let's introduce the private scope and how to define the scope.

Function / lexical scope

Consider the following code:

Function a () {var myOtherVar = 'inside Arubb ()} function b () {var myVar =' inside B'console.log ('myOtherVar:', myOtherVar) function c () {console.log (' myVar:', myVar)} c ()} var myOtherVar = 'global otherVar'var myVar =' global myVar'a ()

You should pay attention to the following points:

Variables are declared both within the global scope and within the function

Function c is now declared in function b

The print result is as follows:

MyOtherVar: "global otherVar" myVar: "inside B"

Perform the steps:

Global creation and declaration-creates all functions and variables in memory as well as global objects and this

Execute-it reads the code line by line, assigns values to variables, and executes function a

Function a creates a new context and is put on the stack, creates the variable myOtherVar in the context, and then calls function b

The function b also creates a new context, which is also put on the stack

The myVar variable is created in the context of function b, and function c is declared

The external references that are created for each new context are mentioned above, depending on where the function is declared in the code.

Function b attempts to print myOtherVar, but this variable does not exist in function b, so function b looks up the scope chain using its external reference. Because function b is declared globally, not inside function a, it uses the global variable myOtherVar.

Function c performs the same steps. Since function c itself does not have a variable myVar, it looks up through the scope chain, that is, function b, because myVar is declared inside function b.

The following is a schematic diagram of the execution:

Remember that the external reference is one-way, it is not a two-way relationship. For example, function b cannot jump directly into the context of function c and get variables from there.

It is best to think of it as a chain (scope chain) that can only run in one direction.

A-> global

C-> b-> global

In the figure above, you may notice that a function is a way to create a new scope. (in addition to global scope) however, there is another way to create a new scope, which is block scope.

Block scope

In the following code, we have two variables and two loops. If we redeclare the same variable in the loop, what will be printed (I did something wrong anyway)?

Function loopScope () {var I = 50var j = 99for (var I = 0; I

< 10; i++) {}console.log('i =', i)for (let j = 0; j < 10; j++) {}console.log('j =', j)}loopScope() 打印结果: i = 10j = 99 第一个循环覆盖了var i,对于不知情的开发人员来说,这可能会导致bug。 第二个循环,每次迭代创建了自己作用域和变量。 这是因为它使用let关键字,它与var相同,只是let有自己的块作用域。 另一个关键字是const,它与let相同,但const常量且无法更改(指内存地址)。 块作用域由大括号 {} 创建的作用域 再看一个例子: function blockScope () {let a = 5{const blockedVar = 'blocked'var b = 11a = 9000}console.log('a =', a)console.log('b =', b)console.log('blockedVar =', blockedVar)}blockScope() 打印结果: a = 9000b = 11ReferenceError: blockedVar is not defined a是块作用域,但它在函数中,而不是嵌套的,本例中使用var是一样的。 对于块作用域的变量,它的行为类似于函数,注意var b可以在外部访问,但是const blockedVar不能。 在块内部,从作用域链向上找到 a 并将let a更改为9000。 使用块作用域可以使代码更清晰,更安全,应该尽可能地使用它。 事件循环(Event Loop) 接下来看看事件循环。 这是回调,事件和浏览器API工作的地方 我们没有过多讨论的事情是堆,也叫全局内存。它是变量存储的地方。由于了解JS引擎是如何实现其数据存储的实际用途并不多,所以我们不在这里讨论它。 来个异步代码: function logMessage2 () {console.log('Message 2')}console.log('Message 1')setTimeout(logMessage2, 1000)console.log('Message 3') 上述代码主要是将一些 message 打印到控制台。 利用setTimeout函数来延迟一条消息。 我们知道js是同步,来看看输出结果 Message 1Message 3Message 2 打印 Message 1 调用 setTimeout 打印 Message 3 打印 Message 2 它记录消息3 稍后,它会记录消息2 setTimeout是一个 API,和大多数浏览器 API一样,当它被调用时,它会向浏览器发送一些数据和回调。我们这边是延迟一秒打印 Message 2。 调用完setTimeout 后,我们的代码继续运行,没有暂停,打印 Message 3 并执行一些必须先执行的操作。 浏览器等待一秒钟,它就会将数据传递给我们的回调函数并将其添加到事件/回调队列中( event/callback queue)。 然后停留在 队列中,只有当**调用堆栈(call stack)**为空时才会被压入堆栈。 代码示例 要熟悉JS引擎,最好的方法就是使用它,再来些有意义的例子。 简单的闭包 这个例子中 有一个返回函数的函数,并在返回的函数中使用外部的变量, 这称为闭包。 function exponent (x) {return function (y) {//和math.pow() 或者x的y次方是一样的return y ** x}}const square = exponent(2)console.log(square(2), square(3)) // 4, 9console.log(exponent(3)(2)) // 8 块代码 我们使用无限循环将将调用堆栈塞满,会发生什么,回调队列被会阻塞,因为只能在调用堆栈为空时添加回调队列。 function blockingCode() {const startTime = new Date().getSeconds()// 延迟函数250毫秒setTimeout(function() {const calledAt = new Date().getSeconds()const diff = calledAt - startTime// 打印调用此函数所需的时间console.log(`Callback called after: ${diff} seconds`)}, 250)// 用循环阻塞堆栈2秒钟while(true) {const currentTime = new Date().getSeconds()// 2 秒后退出if(currentTime - startTime >

= 2) break}} blockingCode () / / 'Callback called after: 2 seconds'

We tried to call a function after 250ms, but because it took two seconds for our loop to block the stack, the callback function actually took two seconds to execute, which is a common error in JavaScript applications.

SetTimeout cannot guarantee that the function will be called after the set time. On the contrary, a better description is to call this function after at least that period of time.

Delay function

What happens when setTimeout is set to 0?

Function defer () {setTimeout (()) = > console.log ('timeout with 0 delayments'), 0) console.log ('after timeout') console.log (' last log')} defer ()

You might expect it to be called immediately, but that's not the case.

Execution result:

After timeoutlast logtimeout with 0 delay!

It is immediately pushed to the callback queue, but it still waits for the call stack to be empty before it executes.

Use closures to cache

Memoization is the process of caching the result of a function call.

For example, there is a function add that adds two numbers. Calling add (1Magazine 2) returns 3, and when it is called again with the same parameter add (1Magne2), this time it is not recalculated, but remember that 1 + 2 is the result of 3 and return the corresponding result directly. Memoization can improve the speed of code and is a good tool.

We can use closures to implement a simple memoize function.

/ / cache function, receive a function const memoize = (func) = > {/ / cache object / / keys is arguments, values are resultsconst cache = {} / / return a new function / / it remembers the cache object & func (closure) / /. Args is any number of argumentsreturn (... args) = > {/ / convert the parameter to a string so that we can store it const argStr = JSON.stringify (args) / / if it is already stored Print console.log ('cache', cache,!! cache [argStr]) cache [argStr] = cache [argStr] | | func (... args) return cache [argStr]} const add = memoize ((a, b) = > a + b) console.log (' first add call:', add (1,2) console.log ('second add call', add (1,2)

Execution result:

Cache {} falsefirst add call: 3cache {'[1je 2]': 3} truesecond add call 3

The first time the add method, the cache object is empty, it calls our incoming function to get the value 3. 0. It then stores the args/value key-value pair in the cache object.

In the second call, it is already in the cache and the value is found and returned.

Caching may seem insignificant or even less efficient for the add function, but it can save a lot of time for some complex calculations. This example is not a perfect caching example, but a practical application of closures.

Thank you for your reading, the above is the content of "the operating principle of the JavaScript engine", after the study of this article, I believe you have a deeper understanding of the operating principle of the JavaScript engine, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

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