This article creation background
(: This is after the completion of the creation wrote: brothers and sisters adorable!! Take ๐ต/๐ง/๐บ/๐/ ๐ง, “stop and go”, tired to do happy things, come back to continue to read. I’m sure this article will continue to refresh the perception of XDJJM! Temporary entire network only &&2W word long article. ๐ ๐
From the wizard title – the final title is the body of this article. Thinking about how to make people both happy and can generally understand, so the text a little, a lot? ๐ ๐ oh ๐๐
Additional words (: boil a whole night, finally catch up in now finished writing -2021.12.10 7:42, recently has been staying up liver, because tomorrow morning โ๏ธโ๏ธ, after please small long vacation. So disorderly/improper, I hope you can correct the comments!!
Since I have had practical experience in compiling principle of C language && have a deeper understanding of JS and browser surface layer (non-chromium source level I all refer to as surface layer), I have more and more doubts about JS and many related things. For example, “execution context”, “rendering pipeline”, “how does browser multi-process play (IPC, etc.)” and so on, the existing theory on the Internet (including ES official), has not been able to solve my various doubts. What I need is chromium source level explanation!!
First I would like to solve the “execution context”, etc, and helpless, as 2021.11.1 (I have to precise it, you never know, we never know what will happen the next moment, not to mention the past for a month?) I should search an article about “the execution context” interpretation of the source level (I don’t need eight-part essays are not ES official specification). The source code for Chromium itself is very, very, very small, ๐๐
Why read the source code? You have to understand that the official standard of the ES specification is only a theoretical standard. It is also a standard that comes out after a technology has been implemented. As a result, only the developers know how elegant the implementation will be. Others can only read the source code to get a glimpse. And the source code is riddled with best practices, as well as all sorts of “dirty operations” for extreme performance (which make it hard to read).
I had no choice, because of my own strong obsessive-compulsive disorder, I had to brave it and go on by myself (although I only had a freshman C foundation, and I had never learned C++, and I was just a little FEer still in practice. But! This doesn’t stop my determination to explore Chromium, Fighting! Fighting!! . ๐ ๐
After more than a month (2~ 3hours added by myself after completing the company’s tasks every day), I did a lot of work on Chromium (although I was ready locally, I failed to debug locally. So I’m in Chromium [need science online], reading. At the end of this article, I will explain why I failed to debug ๐ญ๐ญ๐ญ๐ญ in reading experience of Chromium). I started writing this article at 10:24 on 2021.12.5.
An unfortunate analogy: if other miners don’t have time to share the “resources” from Chromium mine, I’d like to learn how to be a miner. Put on a small steel cap, pack on the back, pick up a small shovel, dig, explore the “resources” to share with everyone.
Hi~ I am the guide for this article
(: later wanted to think, not quite recommended pure white watch oh, pure white shen into โ ๏ธ. Preferably familiar with js& some knowledge of compilation and c++. Compilation and C++ do not understand it does not matter, understand js on the line, the other I tell ๐๐
First of all, you can search for chromium and Chrome.
“Mowgli, what do you remember from what I taught you?” “Back to master Tai, I remember most of it.”
“And now?” “There’s a little bit left.”
“And now?” ‘I’ve forgotten all about it!
“Ok, you can go…”
Forget the js context and scope you learned before, including ES official said, forget too!
I can only say that the official description of the “appearance” source code has the corresponding embodiment, but the specific address, convention, implementation, the official can not be strong constraints. Again, it was chrome(or the major browser vendors) that implemented it first, and the ES organization came up with an official standard later. So I’d like you to come with me as we rediscover and relearn.
This article is mainly from the chromium source code v8 (JavaScript engine, the main content is implemented by C++, but also involves the Torque language developed by Chrome. Tq), to sort out the js context & scope, objects and arrays and other content. We’ll also cover a little bit of compilation. In order to try to synchronize the latest source code & style unity, all screenshots arechromiumIn the latest real-time screenshots, don’t look at my local code.
My mind goes completely blank.๐๐. Because this is written for everyone, “You can love me only if YOU want me to do and say what YOU want me to do.” ๐๐.
Take screenshots of everything covered in this article – impossible, the article would be too big. But I will try to screenshot the source code.
In order to help brothers and sisters meng transition, I will first introduce some macro knowledge, and then combined with micro exploration details, it is best not to jump to see oh
๐ ๐
โญ๏ธโญ๏ธ ๏ธ V8 source macro matting and analysis – Uncover the Sorcerer’s Stone
I’m going to start by giving you a little bit of background knowledge to slowly uncover part of v8’s magic Stone – JS Context&Scope& Others,
Note the graphs I drew, the translations, and the notes I typed below. I want XDJMM to be able to scan word by word. If you do not read it, I am afraid that you will be directly confused by ๐ณ๐ณ๐คฆ๐ป college
โญ๏ธโญ๏ธHandle– Mr. Bean
First: Handle manages the memory address of a V8 object, which is essentially a pointer to a pointer.
An important starter — pointer introduction
I personally think Pointers are 50% of the reason C/C++ is so turbulent and boundless.
Our programs, variables, etc., must eventually be stored in physical hardware. Running programs are generally in memory, and the allocated space is also in memory. You need an address to find something, and C/C++ are low-level languages because they can manipulate memory units directly with Pointers!
A pointer is an address, and an address is a pointer. An address is the number of a memory location. A pointer variable is a variable that holds a pointer/address. The address returned after the memory is allocated is the starting address of the memory unit, and then the specific data can be accessed through the offset.
Some advantages of Pointers: access to hardware, fast data transfer (using Pointers can directly find the starting address of a complex data structure)..
So why Mr Bean?
First, Handle should take the meaning of its Handle, which originates from handle-C. I think you can just take that as a handle. Does that ring a bell? The handle of a handbag? A contra controller? The handle on the bag? Doesn’t matter. It’s kind of like that. What do you mean? A little bit of hold && four two dial a thousand catties of lasting appeal.
- The Handle class has been reimplemented in V8. Let’s take a look at a comment in the source code to see why Mr. Bean is needed:
Roughly speaking:
All objects returned from V8 must be tracked by the garbage collector to know if they are still alive. Because the garbage collector may move objects, it is not safe to point directly to an object. Instead, all objects are stored in a handle that the garbage collector knows about and updates when the object moves. Handles should always be passed by value (addresses are also values, usually in hexadecimal) (except in cases like out-of-parameters — I don’t know what that means, but it shouldn’t matter for this article), and they shouldn’t be allocated on the heap.
There are two types of handles, local and persistent. Local handles are lightweight and transient and are usually used for Local operations. They are all managed by HandleScope. This means that Handles must be on the stack when they are created inside a valid HandleScope. To pass a local handle to an external HandleScope, you must use the EscapableHandleScope and its Escape() method.
The source code involved in this article is Local type handle code, do not worry about Persistent
Persistent handles can be used when stored objects span multiple independent operations, and must be explicitly released when no longer in use.
It is safe to extract an Object stored in a handle by dereferencing the handle (for example, Object* from Local<Object>), the value will still be controlled by the underlying handle, and the same rules apply to handles for those values.
Type* temp; This represents a variable called temp that holds a pointer to Type Type; Temp is the pointer variable! To retrieve the data stored on the memory location where the pointer/address is located, use (*temp). But it’s dangerous! Because the GC may move the object and cause a new address to be created, no one knows what will be stored in the original address. That is, if temp points to an object and GC changes its address to store the object, then the developer gets the original address and no one knows. Don’t we developers want face? !!!!! So you have to pop out a Handle to help you manage the address of this object. You just hold the Handle. You can think of a Handle as a pointer to a pointer; A smart pointer. ๐ฅ ๐ฅ
Am I making myself clear? Yeah, I’m worried about the baby, picture above!
Am I making myself clear, bro? ๐๐ don’t understand, the an also have no rut, jump over first
So you Get Mr. Bean’s wisdom
I still remember some of Mr. Bean’s works, and I always think he is a man of great wisdom.
Back to business. Don’t you notice that when you compare the identity of the push element to the js object, the result will still be true
- Have you ever wondered why? You don’t think that instead of allocating new memory, you add memory to the existing memory ๏ผ๏ผ๏ผ๏ผ๏ผ๏ผ Both objects and arrays start out with a fixed default amount of memory, but when you exceed this amount, you have to reallocate memory. You don’t want to ask why you have to reallocate rather than append memory or overwrite the data on subsequent addresses. Brothers and sisters cute.. You guys are more naive than I am. Puerile.. It’s nice. ๐ ๐
First of all, append is impossible. Because physical memory is right next to each other, how can you make something out of nothing? The best you can do is overlay whatever you want with the unit that comes after unit X. But that doesn’t work either. Why? You have no right to do that, and even if you did, you definitely can’t!
An inappropriate analogy: On the subway, there’s only one seat left in the middle of a row of five, and your girlfriend gets into it. At the next stop, you get on and try to sit next to your girlfriend. You can’t ask someone next to it to move and make room for you, can you? And why? He was here first, why would he let you have it, and it’s a public place. Well, at least one person is gonna get hurt.
But when it comes to memory, it’s not so simple to get out of the way. Do you know what this “need to give up” memory unit is storing? Can you tell what it was stored for? Do you have the right to touch your family? You an impulse, may be behind n memory units are wrong, that is not a unit of the thing! And all the things that memory serves can go wrong, isn’t it terrible? I asked you if you were scared! All this fuss over a little memory. Of course not! That’s why you need to reallocate the memory!! That means you and your girlfriend get a seat in a row and then have a good time.
Take arrays for example: When you push, v8 has to do this when the expansion condition is reached:
- If a request for new memory space fails, an error message is displayed and a return message is displayed.
- If the application is successful, the data on the old address will be copied to the new memory unit in sequence, and the starting address of the new memory unit will be handed over to Handle and Mr. Bean will manage it for you.
- Whatever happens next, the starting address of the new memory is given to Mr. Bean;
- Your variable will always hold The initial address of Mr. Handle (ever since initialization). So your === = comparison, the result must be true.
Do I make myself clear this time, little brother, little sister? Brother Handle has done so many things for you without complaining. He has to make sure that you will not make mistakes. Do you think a person is smart or not? Still water runs deep
The Curious Case of Benjamin Button — The Science of Compiler principles
Implementing a programming language is essentially implementing a translator, the compiler, because the CPU only knows zeros and ones, and the assembly knows instructions.
Here, taking compiled languages as an example, I will briefly describe the process of compiling programs:
Constant degradation, a return to primitivism, isn’t it a little Benjamin? ๐ ๐
Although JS is an interpreted language, it also has a “compiled” part and uses JIT technology. You can find your own articles on how V8 explains js implementation in the nuggets search.
โญ๏ธโญ๏ธ A hundred Flowers Bloom – Overview of the V8 partial class diagram
You can read the picture below in combination with the above and below. Brothers and sisters adorable! Each square is a class. In fact, it’s ok if you don’t understand the green ones (we don’t care this time), and it’s ok if you don’t understand the other colors. After all, this diagram is just for you to get a sense of the big picture, and LATER I will pull out some of the non-green modules – the JS context & scope that we will talk about this time. (๐ญ๐ญ๐ญ๐ญ, two pictures, more than a day of drawing, cervical pain, split)
โญ๏ธโญ๏ธ Elegant wrapper – OVERVIEW of JS Context
I also wrote a little description of the JS Context in the class diagram above, which is a little sandbox for JS code execution, so I call it nicely wrapped
Please XDJMM read my translation carefully first, because some words are not in the original English (babies can also read it 20 times, if you don't understand, it doesn't matter, I will slowly "pull out" later), then you can translate it yourself. Roughly speaking:
JSFunction comes in pairs (context, function code) and is sometimes called closures. (Note that it does not refer to closures as purely closures for js functions and closures that are understood. The JSFunction also contains a lot of bytecode, operations related to optimizing code, etc.). Context objects are often used to represent function contexts and dynamic push “with” contexts (or “scope” in ECMA-262).
At run time, the context builds a stack parallel to the execution stack! (Note: this is a reminder that the context stack is not originally born in the execution stack!!) The context at the top of the stack is the current context. All contexts have the following slots:
[ scope_info ]
This is scoped information that describes the current context. It contains the name of the statically allocated context slot (just to be clear, many times our variables will be placed directly in the context), and stack-allocated locals. The name needs to be used for dynamic lookups in the presence of “with” or “eval”, and for the debugger.
[previous] Pointer to a previous context.
[extension] Add data. This slot is only available in the following situations
ScopeInfo: : HasContextExtensionSlot returns true.
For a native context, it contains global objects. (Browser exposes the pointer to a global object to V8, which in turn exposes the native context; Native context you can think of as "global execution context" as you learned before.
For the Module context, it contains module objects.
For the await context, it contains the generator object.
For the varblock context, it might contain an “extension Object”.
For the with context, it contains an “Extension Object.”
The ‘extension object’ is used to dynamically extend a context with additional variables, that is, in the implementation of the ‘with’ and ‘eval’ structures.
For example, Context::Lookup also searches for properties of extended objects.
Storing extension objects was the original purpose of this context slot, hence the name.
In addition, function contexts with sloppy eval may statically allocate context slots to store local variables/functions to be accessed from internal functions (via static context addresses) or via ‘eval’ (dynamic context look-ups).
The Native context contains additional slots for fast access to native properties.
Finally, in a harmonious scope atmosphere, JSFunction means that top-level scripts will have ScriptContext instead of FunctionContext. All scriptcontexts from top-level scripts are collected in the ScriptContext table: ScriptContextTable.
Therefore, each Context must have at least three slots in common: scope_info, Previous, and extension
- So where do you think the main information is ๐๐? Bingo! Of course it is definitely in scope_info!!
So the Context itself doesn’t have all those things you used to think of as variable environments, lexical environments, this, outer; None!! These things are involved in Scope! But the Scope is very detailed, so instead of calling it lexical context, you can call it lexical identifier. Also don’t call it variable environment, these concepts are too big, there is no corresponding implementation in the source code. Because all sorts of niceties are broken up in source code implementations. If you don’t break it down into tokens, you can’t handle all the weird syntax and semantics! With enough detailed information, you can be impenetrable.
- Now let’s look at what types of Context are available in reverse, using the method of determining the Context type:
There are 10 of them: native, function, catch, with, debugEvaluate, await, block, Module, Eval, script
- Methods to get GlobalObject and its proxies are already declared in the Context
But! The global_object from the native_context extension is as follows:
A global proxy appears. How does it relate to the global object?global_proxy_object.prototypePoint to the
global_objectThat is as follows:
V8 expects that only global objects can be used as prototypes for global agents, so V8 doesn’t want us to change the pointing of the prototype for global agents, otherwise something unexpected might happen, because it could break the VIRTUAL machine!!
Ps: Global_Object is a global object specified by ES, which is Browser’s window exposed to V8
Below one is above ten thousand –NativeContext
In the class diagram above, I’ve already drawn that NativeContext inherits from the Context, and then overrides the method that gets Global_Object and overrides another method for it. But it’s still calling gobal_object() in the Context; I had to go all the way around to get my stuff, but it was okay? Well, I guess for some security, a performance-oriented team like Chrome won’t do anything unnecessary.
The method on line 722 ignores whether the tag has been loaded. A global object can be read safely in concurrent (oh, no problem) situations because it is immutable after initialization. The method on line 722 cannot be used outside of heap-refs.cc. It’s probably somewhere else if you call it blind, maybe before you load it, you’re going to have an accident when you try to manipulate the global object. So there’s an overloaded function on line 723, so if there’s an overloaded function on line 723, just call the 723 method.
And NativeContext also has Pointers to the microtask queueDo you want to know exactly how and when micromissions work? Well, I don’t talk. I play. I play. ๐ ๐ ๐ ๐
Why is the phrase “below one person, above ten thousand people”? Please continue to read โโ
Key to cross-script sharing –ScriptContext and its Table
First, ScriptContext and ScriptContextTable are available through NativeContext
Take a look at the following comment:
That is to sayScriptContextTable
Holds the top-level lexical declaration variables (let/const) of all loaded top-level scripts.In the source code, I see it first through JSFunction(it is not pure js function we say, it carries V8 on our whole JS control, including optimization information, bytecode and other compiled back-end knowledge, I did not continue to in-depth understanding of JSFunction), Get native_context, and then native_Context accesses ScriptContextTable and iterates through the top lexical variables described above. Those that are not in the table are saved and those that are shared. But look out!! Script_context will still have a slot for these variables, but they share access to the corresponding variables in the table and modify the corresponding variables in the table
. This is why you can use top-level let/const variables across scripts when you introduce multiple script tags in HTML. Note that this is just something you can call across scripts, essentially these top-level lexical variables are not installed into global_Object!! For example, if you use the top-level lexical variable temp across scripts: use temp directly, but you can’t use window.temp!! Because it’s not in global_Object per se!!
Why is there no mention of the variable declared by var? In the source code, the top-level script needs to be installed into the native_context, then the JSFunction gets the Native context, then the Global_object in the Native context extension, and then through a series of functions, The top-level VAR declaration in the final traversal script is stored in global_Object. So you can use var variables directly across scripts, or you can use window.xxx.
Functions are a little bit complicated, depending on how you define them initially, and then there are different representations,The main idea is the same: use JSFunction to get native_context and then script_context_table or global_object to operate or not to operate.
In the NewScriptContext method, the boxes are all entries after a bunch of operations, and each entry is followed by N functions. The same is true for all the other source diagrams I posted, because v8 is so big and Chromium is so big. :
Does native_context feel almost powerful now? Only JSFunction is bigger than others. Of course, the biggest is V8, v8 also has Chrome Browser, ๐๐๐๐
Thriller! Let /const!
But did you notice, if you were at the console
Why can the first and third modes pass and the second and fourth modes are wrong? In fact, the console is a debugging mode! Devtools takes care of you. This will trigger the repL mode scope under certain circumstances, and then every time you hit enter, you’ll need to create a new ScriptContext, and then in the source code:
See me where the box up three small box, that is to say, (let and let | | const and const) && such special circumstances, can be repeated statements, otherwise you will quote the corresponding error.
๐๐ป, ๐ถ why am I giving such a stupid example? Not to brush the sense of existence, just want to prove that more than N cases are not we can explain without source textual research. There are N more details. I’m just giving you an example. I hope everyone is in awe of everything!
The other Context will not be covered, the main body and essence are the various scopes, which will be explained in scopes later.
Chicken or egg – Birth order of Context, Scope, AST
Context is sure to come first, but aren’t you wondering about the birth order of scope and AST? Let’s use the Hello-world.cc file as an entry point for a rough analysis, since it can start to complete v8’s minimal functional flow.
Let me explain the three terms I defined, keeping in mind the following:
The PreParser stage: Identifies the various information in the scope and generates a pre-parsed scope;
Formal parsing stage: Parser, reuse scope generated by pre-parsing with high probability, and then allocate variable space and update partial information and identification according to various information!
Formal execution of the function: all the allocated memory in the previous step is allocated before the formal execution of the function. Initializer_postion starts to move. Only when it moves to a proper range can it access the corresponding lexical variable; otherwise, an error is reported.
Line 39-40 is mistyped, not "enter the context to start compiling and running", but "enter the context to prepare for compiling and running".
V8 was first created, then v8’s isolation container instance isolate, then the root context, then enter the context, format the js code and compile it, then execute the compiled result, and print the return value of the script in c++. I didn’t take a screenshot of the following part of the code, it doesn’t matter much to us.
- Now let’s go straight to the entry function that starts parsing the program,
ParseProgram
:
Line 542 is initialized firstLexical analyzer scanner
In the Initialize method, the first Token supply is scanned.Deterministic finite automata
V8 is implemented with many switch-case+ loops. Then line 543 DoParseProgram passes in the ISOLATE and basic parse_info,Parser starts parsing
Returns a pointer to the AST to result. So when FunctionLiteral is a class, never think of it as a FunctionLiteral! But an AST!!
Then enter theDoParseProgram
: thenParseStatementList
There’s another one in thereParseStatementListItem
Function to parse a statementDifferent measures will be taken for different tokens, in short, sentence by sentence parsing, while scope, AST and other data will be constantly updated. There is an automaton outside that controls the final parsing, and finally generates complete scope information, AST information, and context information.
I didn’t have enough time. I didn’t finish this one until a while later. But I can see: V8 for JS compilation front-end part, is the lexical, syntax, semantic analysis of the basic update. So the grammar analysis should be a top-down recursive descent/prediction analysis. In addition to the first Token, the subsequent generation of Token while doing syntax analysis, and scope and other information, is equivalent to the semantic level, so semantic analysis is also updated!! And to constantly determine whether to report errors. When I wrote a small compiler for C, IT used LR analysis – a bottom-up scaling protocol, so it didn’t immediately reflect what V8 was doing ๐ ๐ . I didn’t see what v8 would do, but the first thing they can do is that their JS grammar is very well designed!! แฆ(ยด ยท แด ยท ‘)โค๏ธโค๏ธ.
- But didn’t many people think scope was born after AST? Seeing my red mark in the picture above, you should find that Scope was born before AST! Why create scope in the first place when you haven’t even finished the source word segmentation yet?
Inside the Scope constructor, we first SetDefaults() once and do other operations, keeping in mind that this is just creating/creating a Scope. Initialize all the information in scope at the beginning, and keep updating the information of scope in various situations.
For example:
Scope is the land, and an AST is just a country on the land
Without the initial environment ๐, how could human beings be born? It’s just that the earth and people are growing and changing!
Without China, where would China come from and where would the term “Chinese” come from?
It must have been the environment that paved the way for us to be born. The environment makes us, but we can only influence the environment! Unless you want to do it both ways. Be in awe of nature. Brothers and sisters adorable!!
The last token of the AST scan must be ‘}’, but before the ‘}’ is reached, the scope has already updated all the data to be updated. Your ‘}’ is quite the state of the end moment. We use v8 debugging tool D8 to print a code to prove that scope completion is completed before AST completion:
//shell: d8 --print-ast --print-scopes a.js
// To ensure that the command parameters do not affect the print order, I also:
//shell: d8 --print-scopes --print-ast a.js
// Scope is complete before ast is complete. I'm not going to show you one more screenshot
function a() {
let b = 3;
console.log("a");
}
a();
Copy the code
To sum up, if you get the point here:
Scope = scope; scope = scope; scope = scope; scope = scope; scope = scope; Scope completes a bit before the AST, and finally puts the results generated by the compile front end into an instance of FunctionLiteral, the AST. But the ast really different, it is a “function” as a minimum unit, (if you are not a function, then give you the packing) will generate the corresponding moments before each “function” to perform the full scope of – completed the distribution of memory space required for the (internal nested multiple scope, usually also necessary dynamic change information, See later) and an AST!
โญ๏ธโญ๏ธScope and its Info–Context
ScopeInfo: Scope family ScopeInfo: Scope family ScopeInfo: Scope family ScopeInfo: Scope family ScopeInfo: Scope family ScopeInfo: Scope family ScopeInfo: Scope family ScopeInfo
Extensive GD maps -ScopeInfo Overview
GD map is really good, GD taxi is also good! I don’t get paid for endorsements!Eh? How about some advertising fee from GD? ๐๐ because ScopeInfo stores a lot of Scope information, it is used to quickly find Scope details at Runtime, like a map. Let’s take a look at the source code one or two:
It describes various information about a Scope, flags, indexes, etc., such as what context you are in, whether it is a Scope of a certain type, whether an internal Scope calls eval, etc. It is good to focus on Scope
One more thing: a context must have a scope-info. But! But! A scope/scope-info does not necessarily correspond directly to a context! Repeat it for me five times, please.
The Magic of Chimen Dun Armor — An overview of Scope
Scopes are so varied, they are the cause of all the strangeness, and they can make people “move” in a bewildering way, that’s why I call them the magic of scopes. Let me explain.
- As I’m sure you saw in the Egg Wars, scopes must be static when they’re born. Internal information is constantly updated with Parser, but these operations are static!! In the case of a function, memory is allocated based on the corresponding scopes information just before the function is executed (not counting immediate initialization, but more on that below), and the function is executed when everything is ready.
Excluding debug related mode, scope types can be roughly divided into the following 8 types:Class, eval, fucntion, module, script, catch, block, with scope
Let’s take a look at the focus object, “criminal suspect” — Scope
Roughly:
The AST construction has a globally invariant policy:
Each reference (that is, identifier) to JavaScript variables (including global attributes) is specified by
VariableProxy
Node represents. After AST construction and before variables are allocated to memory, most VariableProxy nodes are “unresolved”, that is, not bound to the corresponding variable (although some are bound at the time of resolution). Variable allocation binds each unresolved VariableProxy to a variable and assigns a location. Note that many VariableProxy nodes may refer to the same JavaScript variables (since the AST is statically compiled, there must be a small node for a small token. We use the same variable over and over again in our JS code, so multiple nodes mean multiple VariableProxies, but they may refer to the same variable. .The AST cannot reference variables directly, it must obtain information about variables indirectly through the corresponding variable proxy! The variables are actually maintained by scopes –scopes. Please take note of what I said
The JS environment is represented in the parser using Scope, DeclarationScope, and ModuleScope. DeclarationScope for
Can really bear/really hold (taste my translation of this word, which is summed up by my combination of source code. For example: although you write {var a; }, but the block does not hold any scope declared by var. This includes scripts, modules, eval,
varblock
(this little thing took me a long time to find) and function scope. ModuleScope further specialized DeclarationScope.
DeclarationScope includes script, Module, Eval, VarBlock, and Function Scope
Of course scopes can be nested!! Let’s use v8 debugging tool D8 to print:
function a() {//function_scope -- a
{//block_scope
let a = 3;
}
let b = 3;
console.log("a");
}
a();
Copy the code
There’s a word missing from the first red square. There is the top-level function A pre-parsed within the script.
You can take a look at the Scope code. This is just Scope; DeclarationScope is the inherited Scope, and then expands a lot of information.
//Scope constructor
Scope::Scope(Zone* zone, Scope* outer_scope, ScopeType scope_type)
: outer_scope_(outer_scope), variables_(zone), scope_type_(scope_type) {
DCHECK_NE(SCRIPT_SCOPE, scope_type);// DCHECK does not need to be handled
SetDefaults(a);// The first thing you do in the constructor is initialize the default value
set_language_mode(outer_scope->language_mode());/ / strict mode
private_name_lookup_skips_outer_class_ =
outer_scope->is_class_scope() &&
outer_scope->AsClassScope() - >IsParsingHeritage(a);// Skip the external class when looking for private names
outer_scope_->AddInnerScope(this);// Add yourself to the outer scope inners
}
Copy the code
// Default values are false or nullptr; You can either take no or point to a null pointer; Nullptr is not the same as NULL
void Scope::SetDefaults(a) {
#ifdef DEBUGThe following three lines are executed in debug mode
scope_name_ = nullptr;// Scope name
already_resolved_ = false;// Have you finished parsing
needs_migration_ = false;// Do you need to migrate
#endif
inner_scope_ = nullptr;// Scope of the first son
sibling_ = nullptr;// Scope of the next sibling
unresolved_list_.Clear(a);// Clear the unresolved node
start_position_ = kNoSourcePosition;// Set the starting position to -1
end_position_ = kNoSourcePosition;// Set the terminating position to -1
calls_eval_ = false;// Is eval internally called
sloppy_eval_can_extend_vars_ = false;// If eval is invoked in loose mode (i.e., non-strict mode), can we extend variables
scope_nonlinear_ = false;// Nonlinear scope
is_hidden_ = false;// Is it hidden
is_debug_evaluate_scope_ = false;// Do not use it
inner_scope_calls_eval_ = false;// Does the inner scope call eval
force_context_allocation_for_parameters_ = false;// Do you need to force parameters into slots in the context
is_declaration_scope_ = false;// is a declarative scope
private_name_lookup_skips_outer_class_ = false;// Private names need to skip the search, ES new syntax class variables before the # will be privatized
must_use_preparsed_scope_data_ = false;// Do I have to use pre-parsed scoped data
is_repl_mode_scope_ = false;// Is repL mode scoped to disable const inlining in debug mode
deserialized_scope_uses_external_cache_ = false;// Do you want to deserialize scopes with an external cache
needs_home_object_ = false;// Is this the first object that needs to be used when the class inherits
is_block_scope_for_object_literal_ = false;// is a block-level scope generated for object literals, which I've already covered
num_stack_slots_ = 0;// The number of slots allocated by the stack
num_heap_slots_ = ContextHeaderLength(a);// The number of slots allocated by the heap, the baseline is ContextHeaderLength
set_language_mode(LanguageMode::kSloppy);// What is the language mode (sometimes strict), the default is sloppy/loose
}
Copy the code
// Return the following information:
struct VariableLookupResult {
int context_index;// Which context are you in
int slot_index;// Which index slot in the location where this variable is stored
// repl_mode flag is needed to disable inlining of 'const' variables in REPL
// mode.
bool is_repl_mode;// It was said above
IsStaticFlag is_static_flag;// static is used to determine the class static modifier variable
VariableMode mode;// The variable is var? let? const? . The statement?
InitializationFlag init_flag;// Initialize now, need to initialize, or do not initialize for a while?
MaybeAssignedFlag maybe_assigned_flag;// Is the identifier assigned? This identifier is very complex and can be changed in many cases
};
Copy the code
You don’t need to remember the above information, but I need to remember ๐๐. Let’s look at our variables and see how they are declared:Let const var, let const var, let const var, let const var, let const var, let const var
- Let’s see how V8 defines lexical variables:
See?mode<=VariableMode::LastLexicalVariableMode;
While LastLexicalVariableMode looks at line 1266, it is kConst, and the contents of the enum class VariableMode:uint8_t are incremented from 0 to 1. The lexical variable schema is a variable declared by let or const.
- Let’s talk about block-level scopes,
As long as {} contains the variable declared by let or const; Or {} contains declarations of function literals; Or class, which also generates a block_scope; Or for, for-in/of, where variables are declared in parentheses, creating block-level scopes as well;
This block is a block-level scope. Here we see that functions under {} are treated as lets. There’s also the for-in case of lexical variable declarations.
- Let’s look at another paragraph in English
That is, ES6 states that declarative environments can be divided into mutable and immutable bindings, which can be described in two states: initialized and uninitialized. All initialization does is assign an initial value to a variable, but only if the variable has already been allocated space. However, for functions, space allocation starts at the moment before the function is executed according to the scope information obtained in the formal parsing stage of the Parser. When everything is ready, the function can be officially executed. Pre-parsed state does not allocate memory to variables!! Not to mention initialization. Preparsing only analyzes information. Coexist for possible reuse in formal parsing. .
When accessing a binding, check for initialization flags, but there are eight cases in which the binding is initialized as soon as the variable is allocated memory. This then causes the initialization check to be skipped! Those 8, you can see the picture. An tired, write not to move ๐ญ๐ญ
That’s the var variable, the function name, the function argument, the unique variable in the catch, the displayed this and the explicit function argument.
- Now that we’re here, how would you design a so-called temporary dead zone? First of all, of course, there is no such thing in the source code, but people have to implement the function you said right. So how do we do that?
In fact, the variable declaration mode + initialization flag + determine whether the position currently executed is after the pos of the variable. Pos is already in each token and ast_node by the time Parser is done
That initializer_position is what I call “currently executed position”,A variable declared by let/const already exists in the variable declaration table during static parsing, but has not been allocated and initialized. As for whether you can access it? Do you want to visit? You only need to use some information in the scope and the variable itself to take the corresponding behavior. This is the essence of scoped gameplay: they statically initialize all the information and tags, parsing the Parser step by step, slowly completing lexical, syntactic and semantic analysis, and constantly updating the information and tags of the corresponding items (scope, AST, context)
.
- About the two types of error:
console.log(aaa);
let aaa = 10;
Copy the code
This is the fundamental reason why the above two errors are reported. The first one is because it is declared in scope. Parser puts its declaration in the table when it sees the object (“let/const variable declaration creation is enhanced, but initialization is not “), but the initializer position is not up to aaa yet, so V8 won’t let you access it. If you call BBB directly, the error is not defined. The root cause is that there is no variable in the declaration table.
- The following are the possible positions to which variables can be assigned. Ignore this for now, and you will see more clearly in the following step by step series:
enum VariableLocation {
// The variable will be allocated to an unallocated slot before it is allocated or an attribute that does not exist in the global
UNALLOCATED
// Function parameters are assigned to parameter by default, parameter[index]
PARAMETER
// In some cases local variables are stored in local slots, local[index], which must be stack-allocated variables
LOCAL
// In some cases, variables must be allocated to slots in the context, which must be heap-allocated. For example, a variable is assigned to context[3]
CONTEXT
// Variables used for dynamic lookup will be allocated to the lookup slot
LOOKUP
// Module exported variables are allocated to module slots
MODULE
// In the script context slot. For cross-script references mentioned earlier
REPL_GLOBAL
// Don't worry about that
kLastVariableLocation
};
Copy the code
(: ๐๐๐๐๐๐) I don't really know what I'm talking about anymore. It's getting late again and I have a reason why I have to stay up all night tonight! Let's take a rest and eat ๐๐. I really lack of time, writing may have disorder, I hope the follow-up brothers and sisters in the comments section to help me point out & criticize my shortcomings!
โญ๏ธโญ โญ โญ โญ๏ธโญ step by step – check Scope from v8 source details
If you don’t think Scopes has anything to say, I’m heartbroken ๐ญ๐ญ. In fact, everything above is my preparation for this section. Just hope d you have a general understanding first, then look at scopes. Guaranteed to constantly refresh your knowledge!!
The three Pointers of Scope resonate well with React Fiber
One extra note: Windows, Console, setTimeout, etc. are not native to V8, but are injected into V8 by Chrome Browser
There are actually three important Pointers in each scope:
outer_scope
React Fiber: returns to the parent scopeinner_scope
React Fiber: refers to the first child of the react Fiber objectsibling_scope
: points to the next sibling scope — sibling sibling of the react Fiber sibling
Isn’t it? Hit it off! ๐ ๐ป ๐ ๐ป ๐ ๐
As mentioned above, scopes can be nested at will (as long as the stack does not burst). Each scope establishes its relationship through these three Pointers, and these three Pointers are also needed to continuously cooperate to do some “add, delete, change and check”.
I just want to find you โค๏ธ–Lookup
Let’s look at where variables are storedVariableMap
:
- The Declare method stores variables (requiring space allocation) in this map. The Declare method first looks for the variable in the map and returns it without reallocating the space. If it does not, it allocates space and inserts the variable into the table with other parameter information (whether initialization is required).
Why check if this variable is in the map in the first place? The var variable can be declared repeatedly. In V8, the var repeats the declaration and continues in the list in order. There is no coverage here! V8 itself commented that this might waste a little more performance, since every var that is repeated is still added to the list), and then went through the list to retrieve the corresponding information before calling Declare and allocating space. Therefore, determine whether the variable already exists in the map.
- The Lookup method is used to Lookup variables, just the name of the variable and its hash, and return nullptr if it does not find a variable.
There is no need to remove and add variables. V8’s hash operations are already wrapped up and the corresponding arguments are passed in.
After I talk about Declare and Lookup, everyone nothing โโ? Don’t you see why everything is so smooth and logical? Don’t you think it’s suspicious? I’m responsible to tell you, there’s nothing suspicious, there’s nothing wrong, there’s just one thing THAT I haven’t told you yet, but I have to lead you to think about it step by step.
Do you think it makes sense to say, “look in the lexical context, look in the variable context, whatever”? XDJMM, does that make sense to you? If I want to reach you, I have your cell number and I’ll call you as soon as I call you, so why do I bother? Don’t you exist anymore? Have you cut off all contact? If you are really born in this world, as long as you are not disconnected from the world, so we can quickly find you! This is the logic of human nature! V8 fits perfectly!
What do you mean? In the scopes, everything is marked statically (which scope you used, what type declared variables, whether they were initialized or not). But there is also a very small amount of information and flag need to be processed at runtime), in advance to determine whether the declaration of conflict (later, this is the key we can rest assured to find variables) and other errors, and then the variable declaration & storage memory for you to prepare, all the environment, contact, v8 are ready for you! All you have to do now is tell MAP who you’re looking for! Hash will help you find it directly. If you don’t find it, it doesn’t exist. A variable must correspond to a unique hash (v8 will resolve this if a hash conflict occurs), because the physical address of each memory address is unique.
World peace is mine, Tiga- Resolve conflict declaration!
“You can’t win. I don’t understand.” , the great ancient
Brothers and sisters adorable!! Don't you believe in light!! For the sake of world peace, I have to. Deja! Conflict resolution! ๐๐(: this function is very important to check if there are any conflicts in the declaration table. Please look carefully at my comments on each line!!
Declaration* DeclarationScope::CheckConflictingVarDeclarations(
bool* allowed_catch_binding_var_redeclaration) {
if (has_checked_syntax_) return nullptr;// If the scope has already been resolved, return directly
for (Declaration* decl : decls_) {
// Go through the declaration table of the declarative scope domain, the declaration in this table only carries some static information.
// The Variable::Declare does not actually allocate space
Parser::Declare Parser::Declare Parser Parser::Declare Parser Parser::Declare
// The only conflicts that still need to be resolved are lexical variables and nested var variables. Let me give you an example:
//function w () { { {var x = 1; } let x = 1; }}; w();
// You must have code like this to report an error: identifier x has already been declared
// If the variable declaration (var/let/const) && is nested
if (decl->IsVariableDeclaration() &&
decl->AsVariableDeclaration() - >AsNested() != nullptr) {
// Save the original scope of this variable. The conflict is not resolved yet
Scope* current = decl->AsVariableDeclaration() - >AsNested() - >scope(a);DCHECK(decl->var() - >mode() == VariableMode::kVar ||
decl->var() - >mode() == VariableMode::kDynamic);
If a variable with the same name (let/const) is found, the declaration structure of the variable is returned directly, and the outer layer does the corresponding processing
do {
Variable* other_var = current->LookupLocal(decl->var() - >raw_name());
if (current->is_catch_scope()) { *allowed_catch_binding_var_redeclaration |= other_var ! =nullptr;
current = current->outer_scope(a);continue;
}
if(other_var ! =nullptr) {
DCHECK(IsLexicalVariableMode(other_var->mode()));
return decl;
}
current = current->outer_scope(a); }while(current ! =this); }}if (V8_LIKELY(!is_eval_scope())) return nullptr;
if (!is_sloppy(language_mode())) return nullptr;
// There are no conflicts in the declaration table, but there are still some things to check.
// If var is declaring eval in non-strict mode, then this variable is declared
// Will be promoted to the first non-eval declarative scope in the outer layer of eval.
Function w () {let a = 2; {let m = 3; eval(`var a = 2; `); } } w(); This must be an error
// From the outer_scope of the current scope, keep looking outward for the first non-eval declarative scope, and then
// Assign the pointer to the found scope's outer_scope to end
// Why outer as end? Think about it? Note the following circular boundary conditions
Scope* end = outer_scope() - >GetNonEvalDeclarationScope() - >outer_scope(a);// Again, iterate through the variable declaration table first. If it is a lexical variable, don't bother, you can't go to the outer layer anyway.
// If it is a var variable and you encounter a lexical variable in the scope you are looking for before you reach the end scope,
// Return this variable directly; And then the outer layer will do something about it.
// Return a nullptr to scope Harmony. I kept world peace!!
for (Declaration* decl : decls_) {
if (IsLexicalVariableMode(decl->var() - >mode())) continue;
Scope* current = outer_scope_;
do {
Variable* other_var =
current->LookupInScopeOrScopeInfo(decl->var() - >raw_name(), current);
if(other_var ! =nullptr && !current->is_catch_scope()) {
if (!IsLexicalVariableMode(other_var->mode())) break;
return decl;
}
current = current->outer_scope(a); }while(current ! = end); }return nullptr;
}
Copy the code
When the var variable declaration is promoted, the scope through which it passes cannot have a variable of the same lexical nature (let/cosnt)!! Because it has to go up one floor at a time. It's very human. What else can you teleport? Directly from Earth to outer space? Astronauts brother and sister on the way to the air also have to slowly "fly" ah, also can't hit what things ah! To the crew, salute! (: I jump out of my chair to salute. It's your turn. Three, two, one.
Declaration_scope, a six-sided all-in-one warrior
So said, everybody can see, the script/module/function/eval/varblock belong to declarative scope, its power is very big also. At every turn, you have to find the first non-Eval declarative scope, and then do some critical operations in it. By the way, post how to find it:
Easy? That’s it. Start with the current scope and continue to outer as long as it’s not a non-eval declarative scope. AsDeclaration() also returns this, essentially unchanged, but explicitly tells you that this scope is pulled as the first non-eval declarative scope.
- Take function_scope as an example:
Static Parser drives Scanner Token by Token parsing, so parameters must be added to the declaration table first!! By default (so there are other cases, of course) arguments are declared in var mode and resolved. as long as there are no conflicts (as we explained above how V8 checks for conflicts), variable Declarations are added successively to Declarations. Function literal forms are declared with a function name of type var in order (and then marked with lazy resolution). If everything ends well. You can start allocating space before the function is called
. What about the order in which you allocate space for these variables? First allocate space for this (๐ฎ), then allocate space for parameters, then allocate space for non-parameter internal variables, and finally allocate space for the function itself (the function itself must be the last one to allocate space)!
Source code proof:
Just wanted to let you know,
Don’tBelieve in the "precompiled trilogy" that is currently circulating on the Internet. Not true, most of all because you don't write complex scenarios, all sorts of weird nesting, function with block, with, eval, catch... Cause there is no unified theory to explain exactly how, because only in the source code! It is true that a lot of code is consumed in the source code to deal with various cases.
But the essence, I think, is that there’s enough information and enough Pointers to tags && so Chrome can just do its best to humanize cases. The rest is a matter of technical implementation. Right ` `
Invisible guest — Varblock_scope
๐ถ๐ถ Finally time to reveal the “invisible guest”. All the other scopes are valid, but this varblock, you little thing, are you? Is {var x; }? Of course not! It took me four or five days to find it in the source code. What a secret ๐ญ๐ญ
This formals is a parameter list,That is, if the parameter list is not a simple schema, a VARblock_scope will be created
. So the key is to find when is_Simple is false, and when is is_Simple false? Is the parameter complex type data? No! During pre-parsing, it will determine: This is the time to check the parameters of the function, and in both cases,If you assign default values to arguments or use residual arguments, it is not simple mode. Varblock_scope is created!
Finally! Solved!! ๐ ๐
- What exactly is varblock_scope used for? Let’s take a look at a previous time to compare ๐ฅ a problem, I adapted:
var x = 'global X';
function test(
x = 4,
y = function () {
console.log('in y first,', x);
x = 'newX from y';
console.log("in y changed,", x);
},
) {
console.log(x); / / 4
var x = 2;
function newFunc(a = 3, b = 2) {
console.log("in newFunc first,", x);
x = 'newX from newFunc';
console.log('in newFunc changed,', x);
};
y();//in y first, 4; in y changed, newX from y;
console.log(x);/ / 2
newFunc();//in newFunc first, 2; in newFunc changed, newX from newFunc
console.log(x);//newX from newFunc
x = 'middle changed';
y();//in y first, newX from y; in y changed, newX from y
console.log(x);//middle changed
newFunc();//in newFunc first, middle changed; in newFunc changed, newX from newFunc
console.log(x);//newX from newFunc
}
test();
console.log('most outer X, ', x);//most outer X, global X
Copy the code
I wrote my own notes before I looked at the console, and I haven’t done the harder ones yet. Now let’s take a look at the console results:
What’s the most popular explanation on the Internet right now? Parameter scope is independent of function scope, right? . First of all, the source code has stated that there is no such thing as a parameter scope, and according to the Internet, your “parameter scope” and “inside scope” are equal? None of that is correct. D8 prints the scope information:
This is the scope pre-parsed result of the test function and the immediate parsed global object.
This is the formal parse of the test function scope:
Keep in mind that all scope information (except with/eval and a few exceptions) is statically parsed and updated!! So you get the idea that in what scope this variable (function is also a variable) is born (positioned in the text of the JS source), var is promoted to the first non-eval declarative scope (the promotion process cannot conflict with the lexical property declaration). Hold on to these. :
Let’s take a look at the source code I comb. I tell the babies, ๐๐ this paragraph I think is really Chrome team carefully ah, in order to humanize as much as possible. Split, it took me a week to figure it out.I hope you read it well! I think the following 8 points are among the most valuable in this article!!
But I may express the error, and may be the understanding of the source code I also is not in place, I hope the predestined person correct!!
The preparse phase has wrapped a layer of varblock_scope around the function body code. Variables x, y are now in var mode by default and in the immediate scope of the test scope! So it's not like creating a new scope for parameters! Instead, create a new varblock_scope for the function body!!
- The formal parsing of the test function stores the default value of the test argument in the temporary slot, except that the slot stores the value taken from the index corresponding to parameter.
- Change the declaration mode of parameters to let and sequentially allocate memory space for variables within scope. But y has not been assigned. Why? Because:
For parameter default arguments and variable declaration statements, their "=" will not be defined as assignment "="
!!!!! So why isn’t x marked as unassigned? There are two reasons for this:(1) This type of code can be used to forcibly assign x to a slot in the following context, so it can be used to initialize x in the formal parsing phase.
โก Because x is assigned to y, there is no unassigned identifier for x. Only in this code, the root cause is due to condition โ ! Hole initializetion elided. The hole is ided to be initialized
: let variables are filled with hole first, but the code is not run time yet. The position of the initializer has not reached the specified range, so y cannot be initialized as the anonymous function.- All space allocation has been completed, the formal execution of the test, when the initializer in parameters, will put in the temporary storage of y parameter a default value, assigned to the corresponding parameter (because y not mandatory slots assigned to the context, it is still in the local, state needs to perform the initializer for her to go to the corresponding position initialization assignment)!!!!! The first 4 is printed because:
The x in varblock_x was originally 4 == because it forced the context to assign variables in the previous analysis, it was initialized as a variable in TEMPORARY
. - When you take your
var x = 2;
And then you take that line out, and you say why is it so bad? Try printing it yourself and figure out why. The answers are written in the first five points. Varblock_scope: newFunc: x: x: x: x: x: x: x: x: x: x: x: x: x: x In this case, y and newFunc share the same x, so the x you change is the same parameter x that was changed to let mode in the formal parsing phase. What if it’s not in the test parameter? Same thing, look up, report an error if you don’t find it. - Variables are allocated in a context, local, temporary, etc., so you don’t have to worry too much about it. If you put a variable in the context, you sometimes need to go through the context to access a variable. But sometimes you do need to force context assignment.
- Variables in with and eval are labeled DYNAMIC/DYNAMIC_GLOBAL. Don’t worry about it.
Do I make myself clear here? See why I named this section “Invisible Guests”? Personally, I think it’s quite appropriate. ๐ ๐
Disappeared Lover –block_scope for object_literal
Come on, watch “Gone Girl”, don’t get your view of love and marriage distorted
function a() {
// console.log(func); //not defined
var x = 3;
function other () {
let o = 2;
}
let obj = {
func() {
var f_a = 2;
returnx; }};// console.log(func); //not defined
other();
return obj.func;
}
const f = a();
console.log(f());
Copy the code
Focus on func in OBJ, which is a perfectly normal code, but something happened to me, above:
This is the scope of our function A after formal analysis. Have you found anything? Bingo! As I mentioned earlier, function literals are declared in var mode by default. There must be a function name that is promoted to the first non-eval declarative scope above, in this code, in the scope of function A, but!
Var other, but no var func; Function func(){} (ps: function func(){} (ps: it’s just a string. It doesn’t allocate memory right now, it allocates memory just before func executes. And it cannot be assigned to a scope, it must be attached to OBj’s func! So why is that? Come, source code:
When a method appears inside a function literal, a block_scope is created first
, then, look at the following ๐๐ป๐๐ป
// If I were writing the obj and function literals above, the following functions would be called
Scope* Scope::FinalizeBlockScope(a) {
if (variables_.occupancy(a) >0| | -is_declaration_scope() &&
AsDeclarationScope() - >sloppy_eval_can_extend_vars())) {
return this;
}
DCHECK(!is_class_scope());
// 1. Remove the newly created block_scope from function A's scope
outer_scope() - >RemoveInnerScope(this);
// 2. Redirect all subscopes under this block_scope and our func function's outer_scope to function A's scope
if(inner_scope_ ! =nullptr) {
Scope* scope = inner_scope_;
scope->outer_scope_ = outer_scope(a);while(scope->sibling_ ! =nullptr) {
scope = scope->sibling_;
scope->outer_scope_ = outer_scope(a); } scope->sibling_ =outer_scope()->inner_scope_;
outer_scope()->inner_scope_ = inner_scope_;
inner_scope_ = nullptr;
}
// 3. Remove the unparsed node, this step is also in the middle! Otherwise your var func goes from step 2 into the scope of function A
// Func is not parsed until space is allocated for func inside block_scope. The var func inside block_scope is removed because it needs to be bound to the AST node, which needs to be used later.
if(! unresolved_list_.is_empty()) {
outer_scope()->unresolved_list_.Prepend(std::move(unresolved_list_));
unresolved_list_.Clear(a); }Var (); var ()
if (inner_scope_calls_eval_) outer_scope()->inner_scope_calls_eval_ = true;
// This block does not need a context.
num_heap_slots_ = 0;
return nullptr;
}
Copy the code
After the analysis in my comments above, V8 does the perfect thing: since func is a property of obj, we cannot directly reference func anywhere in a function. The outer_scope allows func to find variables in function A. The outer_scope allows func to find variables in function A. The outer_scope allows func to find variables in function A. And even return out to form a closure. ๐ฎ๐ฎ in order to meet the humanization of the phenomenon, V8 really hard, delicate ah. I like I like ๐๐ป๐๐ป ๐ป
Now does everyone understand why I’m called “Gone Lover”?” We used to be inseparable, now you’re gone ๐๐”
Eval&with scope
Scope chain in the case of some “SAO grammar” is not mentioned, there is no need. A few more points:
- Var variables declared in with_scope are promoted to the first non-eval declarative scope.
If {} with contains a lexical variable --let/const/ function literal, then in addition to the with_scope generated by with itself, it also generates a block_scope enclosing the with curly braces.
- With_scope memory leak ->global
The var variable in the eval_scope is stored in the slot of the lookup, which can be searched dynamically during execution
In other words:
function a(){
//console.log(e); // An error was reported because the lookup slot has not yet been added
eval(`var e = 1; `);
console.log(e);//1, a's scope will lookup slot E,
}
a();
Copy the code
Get it, 2 idiots are messing with Scope, 3 idiots are messing with Bollywood.
The Lonely and brilliant God: Closure –Closure
“Every year when the first snow, party B should answer party A’s call, because Party A will wait ~”, oh, sorry, wrong set, ๐๐๐๐
(Although dead once, but not dead ๐ and powerful. Is Agassiz similar to closures? If you haven’t seen ghosts, don’t look at this paragraph, when I am in the wind ๐๐) my outer context functions are destroyed (Agassiz), your inner return function (Chi en tao/jingaoyin?) “, and a chance to use my stuff? There must be a contract to summon Agassiz. What is it? Ta-da ta-da! The most critical of course is our three Pointers!
Take the following code for example:
function w1 () {
let w1_1 = 1;
const w2 = function () {
let w2_2 = 2;
console.log(w1_1);
returnw1_1; }}const w2 = w1();
console.log(w2());
Copy the code
W1 will finish creating the context and so on, and then return w2. W1 needs to destroy the context after execution, so do something before it destroys. Must be to save the whole scene, and save the information to go back out. In the Scope: source: DeserializeScopeChain method, this method means deserialization Scope chain, it do is roughly:
According to the original scope chain/tree (because three Pointers, can also be said to be tree), from our innermost scope (w2 to return) to the current script_scope (excluding), apply for a new space clone. Then add the scope closest to Script_scope (in this case, w1's scope) to script_scope. The scope to return (in this code example, w2's scope) is then returned as the value of the deserialization scope chain method. This way, the closure's data can be recovered from the outside even if the old function context is destroyed. Because it cloned the entire link and saved it to Script_scope
.
There’s also a way to get the closure scope, just to show you, and sometimes it’s used.But the real closure is that deserialization method!
You just start at the scope where the method was called and work your way out to the first declarative scope or block-level scope and go straight back
Eat a hundred rice to grow up –JSArray
The reason why it eat a hundred rice to grow up, because JS array, a bit ๐ฎ๐ฎ ah. You see ha:
Can hold various types of data, can dynamically expand and shrink capacity; Can also be used as a stack push&pop; Can also act as queue unshift&shift; It can also be a dictionary; Finally, it exposes a variety of convenient apis for higher-order functions. โค ๏ธ โค ๏ธ
I’m just going to show you the source code for the js array traversal and sort API
Retractable natural gas foot – dynamic expansion and shrinkage
Arrays push and POP can cause dynamic expansion and shrinkage without causing problems of their own. ๐
The reason why memory changes are identical is explained by Mr. Bean above. The main content everybody reads this article good, this eldest brother writes of quite good. Explore the JS V8 engine under the “array” implementation
I’ll simply add that arrays need to be dynamically scaled up and down, but the root cause is that objects need to be dynamically scaled up and down.
Iteration API
Okay, well, actually, I just wanted to show you how the human V8 goes through the world with us. Look at this factory. A function implements 8 iterations
I can’t help it. I had to comment the entire file line by line! Later accidentally without the garbage basket to delete! Well, you’ll have to see for yourself ๐๐. Chromium for js array traversal method is directly implemented with js, so it is easier than c++, but the efficiency is certainly not c++ high
//third_party/devtools-frontend/src/node_modules/core-js-pure/internals/array-iteration.js
var bind = require('.. /internals/function-bind-context');
var IndexedObject = require('.. /internals/indexed-object');
var toObject = require('.. /internals/to-object');
var toLength = require('.. /internals/to-length');
var arraySpeciesCreate = require('.. /internals/array-species-create');
var push = [].push;
// `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterOut }` methods implementation
var createMethod = function (TYPE) {
var IS_MAP = TYPE == 1;
var IS_FILTER = TYPE == 2;
var IS_SOME = TYPE == 3;
var IS_EVERY = TYPE == 4;
var IS_FIND_INDEX = TYPE == 6;
var IS_FILTER_OUT = TYPE == 7;
var NO_HOLES = TYPE == 5 || IS_FIND_INDEX;
return function ($this, callbackfn, that, specificCreate) {
var O = toObject($this);
var self = IndexedObject(O);
var boundFunction = bind(callbackfn, that, 3);
var length = toLength(self.length);// There is no error tolerance in the file. // There is no error tolerance in the file.
var index = 0;
var create = specificCreate || arraySpeciesCreate;
var target = IS_MAP ? create($this, length) : IS_FILTER || IS_FILTER_OUT ? create($this, 0) : undefined;
var value, result;
Arr.map = arr.map = arr.map = arr.pop(); In this way, you can avoid being executed after pop
for(; length > index; index++)if (NO_HOLES || index in self) {
value = self[index];
result = boundFunction(value, index, O);
if (TYPE) {// I can only say that this iteration is beautifully written
if (IS_MAP) target[index] = result; // map
else if (result) switch (TYPE) {
case 3: return true; // some
case 5: return value; // find
case 6: return index; // findIndex
case 2: push.call(target, value); // filter
} else switch (TYPE) {
case 4: return false; // every
case 7: push.call(target, value); // filterOut}}}return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : target;
};
};
module.exports = {
forEach: createMethod(0),
map: createMethod(1),
filter: createMethod(2),
some: createMethod(3),
every: createMethod(4),
find: createMethod(5),
findIndex: createMethod(6),
filterOut: createMethod(7)};Copy the code
โญ Be born in a troubled world –sort API
๐๐ just want to say, let’s see, V8 how to pacify the “chaos” array.
First, the latest V8s are written in Torque, their own language: V8 /third_party/ V8 /builtins/array-sort.tq
I’m using a hybrid sort algorithm from Python called Timsort, which no longer uses insert + quicksort, but binary insert + merge sort, I guess because quicksort is not stable, and the time complexity is O(n^2) at worst; Merge sort is stable and remains order nlogn.
- If the array length is less than 2, return
- As long as the remaining subarray is not zero, you get a subarray, run for short, of length currentRunLength
- Calculate the minimum array length minRunLength (which dynamically varies according to the array length, from 32 to 64)
- CurrentRunLength and minRunLength are compared. If currentRunLength < minRunLength, insert sort and complement array length to minRunLength. Otherwise, run is pushed into the stack pending-runs.
- Whenever a new run is pressed into pending-runs, ensure that any three consecutive runs (run0, run1, run2) in the stack satisfy run0 > run1+run2 && run1>run2, otherwise the stack is adjusted to satisfy.
- If the number of subarrays remaining is 0, the loop ends and all runs in the && merge stack are sorted.
Array.sort (()=>(0.5- math.random ())); Remember that it doesn’t randomly shuffle the array equally! We can do this 384. Scramble the array.
Your good object –JSObject
In fact, there are many articles on the Internet to explain the good (although some places may not be accurate), I was originally want to take you to explore, I really think so at first, ๐๐ believe me, I love you, I love you, hear!! โค ๏ธ โค ๏ธ
But… I cracked!! XDJMM. My time is really not enough, boil so many days of the night, today all night, or can not liver over, I tomorrow morning (12.11) โ๏ธโ๏ธ, this JS object I can only ๐๐. I’m sorry. Oh, god, god, I’m sorry. ๐ญ ๐ญ ๐ญ ๐ญ
โญ๏ธ you want to read the chromium source? For you to learn from my experience:
In fact, it’s really not recommended for chicks like me (well, I’m not even a chick ๐๐) to read chromium! It is best to have a good computer foundation and a deep understanding of the upper application of the people to research.
๏ผ๏ผ๏ผ๏ผ is really not recommended ๏ผ๏ผ๏ผ๏ผ is really not recommended ๏ผ๏ผ๏ผ๏ผ is really not recommended Say the important words three times! Let me explain why I said that.
1. Preparation before reading
- Need science to get online! And ๐ช quality must be good! Speed up the Internet! (This condition has stopped many people.)
- Configure the environment, install DevTools, fetch source code with it (I don’t have fetch history commit record, also has 21 GB), and then configure the environment
- Build & build Chromium with GN and Ninja (it took me 12 hours to do this), you can use GClient Sync to reconnect (but you’d better not)
- Follow the above steps (see the official website for detailed steps) and if everything goes well, it will take 2-3 days if there is no accident. I didn’t start with the above steps, and there were accidents in the middle, which added up to an effective week. Finally my entire Chromium folder is 69.22G
- Then you can just click launch Chromium
Ps: V8 can fetch and debug separately (definitely easier than Chromium), after all processing, my V8 folder size is 14.09 GB.
Second, about debugging
Unfortunately ๐ญ๐ญ๐ญ, I couldn’t debug on the MAC’s Xcode (which requires some command processing if you want to debug with Xcode) and got stuck with indexing as soon as I got in. Chromium was not debugged, and THE V8 I fetch alone was not debugged either (better yet, stuck in the Run building). !!!!! Because the compiler did not build the index, many functions can not be used, and finally choose to hard read in Chromium (it has index jump, find references and other functions).
After excluding the analysis, it should be caused by the following two reasons:
- Either the whole Chromium or the V8 I fetch alone, Xcode failed to build a complete index (without index, you can’t jump, and plain text is the same). There are two possible reasons: one is the loss of index information, the other is that the file is too big for index to run ๏ผ๏ผ๏ผ๏ผ
- Maybe this computer is not configured enough. The storage space is 256. Now I’m down to 10 gigabytes. I can’t do anything. I have to leave it to work
Third, reading
If you can already debug with an IDE and know C++. Then I highly recommend you follow the debug mode to read the source, ๐๐ and then share with everyone.
In fact, as long as you can debug, what will be convenient. But if you don’t debug it, don’t you want to read it like ME? I almost gave up. I was devastated for the first two weeks. Don’t be an idiot like me. Be sensible.
If you are in V8, you can start with V8 /samples/hello-world.cc and see the flow. I also found this file later, ๐ญ๐ญ
Measure ROI– return on investment
If you have a deep functional understanding of what you want to read && can debug perfectly && have C++ foundation && computer foundation (preferably compiler principles and OS), go for it and the ROI will be high
But the vast majority of people may not be able to debug, or even in the first few steps were stopped at the door and give up. And the latest chromium is too big for performance -> gimmicky, ‘456’? ‘5’ : ‘normal’; It’s not fit to be read.
Then you compare your investment with your return. Do you think it’s worth it? Unless you want to be like me, you don’t think ROI, you are very interested, you are obsessive-compulsive, in order to grasp the essence of something, to lay other foundations for it, you have to read. OK, so whatever brother or sister you are, I like you. โค ๏ธ โค ๏ธ โค ๏ธ โค ๏ธ
In this case (no debugging, hard reading), here are a few things I learned from reading Chromium/V8:
- Guess: if you were a developer, how would you name the variable and search in Chromium for clues
- Fight back: The first week or two you will definitely read will be autistic, survive the first week, the rest will be much better
- Building connections -> Delayed enjoyment: As you continue to look for clues and read line by line, you will accumulate pieces of knowledge that you won’t be able to piece together in a short time. Because you don’t have a debugger, you can’t see what’s going on in the stack. But you have to write it down in time, and the information will accumulate, and then you’ll be able to break it down into pieces. You’ll be “in a good place” and ready to enjoy the rest of your reading.
- Practice: to see if the actual situation is as I read; If you have a tool like D8, it is also a good choice
- Repeat the above four steps
“The Road to W Mining” Season 1, Episode 1 finale – Summary
I don’t know why so many late nights can be peaceful
knock
The flutter of the fingertips
At the end is still the original faith..
Ok, fine, just a little summary.
In this sharing, I worry that I will not be clear enough to make everyone confused, so I first make a macro foundation and explanation: Mr. Hanlde’s great wisdom, popular knowledge of compilation principle, class (and its simple description) diagram, Context&Scope basic description, V8 sample macro execution process.
Context&Scope (Context&Scope); Context&Scope (Context&Scope);
Then comes a bit of js array API and object (๐ ๐) source code analysis. Finally, I shared my Chromium reading experience for everyone to learn from.
As I failed to debug up, so may be some aspects of my understanding is wrong, especially hope to understand the comrades correct me
โค ๏ธ โค ๏ธ
The road of Chromium exploration is far from over, and the road of life growth can not see the edge. I hope you can keep moving forward with sincere heart! ๐ ๐
After that, I will find time to learn C++ systematically (after all, I only spent 3 hours to temporarily enter the door, and then read the source code while learning C++ grammar for a month), and then switch to a high level MBP after graduation (I can’t stand the company’s MBP.). , and then read carefully & sort out chromium content and share it with everyone. But in the meantime, the chromium “walkthrough” will be suspended, after all, the ROI is too low. Besides, I will graduate next year. I will have more things to do, so I will have no time to see them before graduation. ๐ญ๐ญ but I may share other knowledge from time to time.
Ps: This article all content is original, quote/reprint please note the source ha! Infringement must be investigated ๏ผ๏ผ๏ผ๏ผ -- W
To introduce myself
Hello, you can call me W. In order not to interfere with your reading time and experience, deliberately put this at the end of the article. After all, I am the first time to open the creation of articles, always come to some sense of ceremony right, a small memorial ๐๐.
Currently a senior, FEer intern for four or five months. I don’t like eight-part essay very much. I like to practice and explore by myself. Frequent business leads to frequent communication and code knocking, and I have been constantly improving the code quality (here I also want to thank my mentor, who needs N CR for one time; And helped me a lot in other ways). I also often pay attention to micro-optimization at the code level, and have my own understanding of programming — I think the code world itself is another interpretation of the real world, so it must try its best to be humanized && not anti-human at the beginning of design.
Practice is the sole criterion for testing truth. Ripping open the source code is the only way to check the truth.
In fact, I also want to read more, wait for a deeper understanding and then share. But I have to โ๏ธโ๏ธ tomorrow morning to do something else, and I won’t touch the code for more than half a month, so I want to share it with you. Otherwise, a lot of things may be forgotten later, and rearranging is a new investment.
In fact, MY heart to you, there are so many, so great, but due to the time to share only so much (in fact, there are 90% pull), young I left unwilling, guilty, happy (because I do not code word pull) tears. ๐ญ ๐ญ ๐ญ ๐ญ
If you find my shortcomings/mistakes, you are especially welcome to point out immediately, you can also add me QQ: 705249953, welcome to exchange and discussion. Also can help you push, welcome to our department Bytedance – commercial cash ๐๐ (a few days ago also heard Leader said HC).
We always say that we stand on the shoulders of giants to see the world, but where so many giants carry the weight of so many of us. And the giants that exist will be tired, and will eventually be faded by time. So we need to continue to care for the giant, while improving themselves, even if not become the next giant, but also to guide the next giant to make a contribution! You’re going to die, you’re going to leave a mark on this world.
This is one of the reasons why I read & share regardless of ROI.
I have been selflessly helped by others, so I also hope to have the opportunity to make a modest contribution to the society. I hope my exploration and sharing can create more sharing, sharing and open source ideological atmosphere for the community.
โญ๏ธโญ๏ธ ๏ธโญ
Please look at the officer (handsome little brother, beautiful little sister, mature steady big brother, gentle and virtuous big sister, in short is cool and sayin you), give a three even (like collection attention)+ comment!! Please!! Please!! Your support and response is my biggest motivation to share!!
Finally on my treasure – incomparable lovely and charming pig fart town building, at the same time I wish everyone a happy New Year’s day! The e future: smooth sailing, two dragons fly, Sanyang Kaitai, four seasons peace, five blessings, 66 dashun, seven stars, eight party to money, 99 concentric, perfect, all the best, horse! ๐๐๐๐๐(cough, cough, cough…)
See you๐๐๐๐ป port