JavaScript running mechanism sample code analysis
Start with a simple question:
<script type="text/javascript"> alert(i); var i = 1; </script>
Output results is undefined. This phenomenon is called "pre-parsing": the JavaScript engine will parse var variables and function definitions first. The code is not executed until pre-parsing is complete. If a document stream contains multiple script code segments (js code separated by script tags or imported js files).
step1. Read the first code Section
step2. Do syntax analysis. If there are errors, a syntax error will be reported (such as mismatched brackets, etc.), and jump to step5
step3. Do "pre-parsing" of var variables and function definitions (no errors will ever be reported) , because only correct declarations are parsed)
step4. Execute the code segment, and report an error if there is an error (for example, the variable is undefined)
step5. If there is another code segment, read the next code segment, and repeat step2
step6. End
The above analysis has been able to explain many problems, but I always feel that there is something missing. For example, in step 3, what exactly is "pre-parsing"? And in step 4, look at the following example:
<script type="text/javascript"> alert(i); // error: i is not defined. i = 1; </script>
Why does the first sentence cause an error? In JavaScript, don’t variables need to be undefined?
Time flies by like a white horse. Next to the bookcase, I open the "Principles of Compilation" that seems to be from another world. There is such a note in the familiar yet unfamiliar blank space:
For traditional compiled languages, the compilation steps are divided into: lexical analysis, syntax analysis, semantic checking, code optimization and byte generation.
But for interpreted languages, after the syntax tree is obtained through lexical analysis and syntax analysis, interpretation and execution can begin.
Simply put, lexical analysis is to convert a character stream (char stream) into a token stream (token stream), such as converting c = a – b; to:
NAME "c" EQUALS NAME "a" MINUS NAME "b" SEMICOLON
The above is just an example , for further understanding, please check out Lexical Analysis.
Chapter 2 of "The Definitive Guide to JavaScript", which talks about lexical structure (Lexical Structure), which is also described in ECMA-262. Lexical structure is the basis of a language and is easy to master. As for the implementation of lexical analysis, that is another research area and will not be explored here.
We can use the analogy of natural language. Lexical analysis is a one-to-one hard translation. For example, if a paragraph of English is translated into Chinese word by word, what we get is a bunch of token streams, which is difficult to understand. Further translation requires grammatical analysis. The following figure is a syntax tree of a conditional statement:
When constructing the syntax tree, if it is found that it cannot be constructed, such as if(a { i = 2; }, it will Report a syntax error and end the parsing of the entire code block. This is step 2 at the beginning of this article.
After constructing a syntax tree through syntax analysis, the translated sentences may still be ambiguous. Next, further semantic checking is required. For traditional strongly typed languages, the main part of semantic checking is type checking, such as whether the actual parameter and formal parameter types of the function match. For weakly typed languages, this step may not be available ( I have limited energy and do not have time to look at the JS engine implementation. I am not sure whether there is a semantic check step in the JS engine)
It can be seen from the above analysis that for the JavaScript engine, there must be lexical analysis. and syntax analysis, followed by semantic checking, code optimization and other steps. After these compilation steps are completed (any language has a compilation process, but interpreted languages are not compiled into binary code), the code will start to be executed
.The above compilation process still cannot explain the "pre-parsing" at the beginning of the article in more depth. We have to carefully explore the execution process of the JavaScript code
Through compilation, the JavaScript code has been translated into a syntax tree, and then it will be. Immediately execute according to the syntax tree.
For further execution, you need to understand the scope mechanism of JavaScript. JavaScript uses lexical scope. In layman's terms, the scope of JavaScript variables is defined. It is determined at the time of execution rather than at execution time, which means that the lexical scope depends on the source code and can be determined by the compiler through static analysis. Therefore, the lexical scope is also called static scope (static scope). However, you need to pay attention to the semantics of with and eval. It cannot be achieved only through static technology. In fact, it can only be said that the scope mechanism of JS is very close to lexical scope.
The JS engine creates an execution context when executing each function instance. The context contains a call object. The call object is a scriptObject structure, which is used to save the internal variable table varDecls, the embedded function table funDecls, the parent reference list upvalue and other syntax analysis structures (note: varDecls and funDecls and other information are It has been obtained during the syntax analysis stage and saved in the syntax tree. When the function instance is executed, this information will be copied from the syntax tree to the scriptObject). scriptObject is a set of static systems related to functions, consistent with the life cycle of function instances.
lexical scope是JS的作用域机制,还需要理解它的实现方法,这就是作用域链(scope chain)。scope chain是一个name lookup机制,首先在当前执行环境的scriptObject中寻找,没找到,则顺着upvalue到父级scriptObject中寻找,一直 lookup到全局调用对象(global object)。
当一个函数实例执行时,会创建或关联到一个闭包(closure)。 scriptObject用来静态保存与函数相关的变量表,closure则在执行期动态保存这些变量表及其运行值。closure的生命周期有可能比函数实例长。函数实例在活动引用为空后会自动销毁,closure则要等要数据引用为空后,由JS引擎回收(有些情况下不会自动回收,就导致了内存泄漏)。
别被上面的一堆名词吓住,一旦理解了执行环境、调用对象、闭包、词法作用域、作用域链这些概念,JS语言的很多现象都能迎刃而解。
至此,对于文章开头部分的疑问,可以解释得很清楚了:
step3中所谓的“预解析”,其实是在step2的语法分析阶段完成,并存储在语法树中。当执行到函数实例时,会将varDelcs和funcDecls从语法树中复制到执行环境的scriptObject上。
step4中,未定义变量意味着在scriptObject的变量表中找不到,JS引擎会沿着scriptObject的upvalue往上寻找,如果都没找到,对于写操作i = 1; 最后就会等价为 window.i = 1; 给window对象新增了一个属性。对于读操作,如果一直追溯到全局执行环境的scriptObject上都找不到,就会产生运行期错误。
理解后,雾散花开,天空一片晴朗。
最后,留个问题给大家:
<script type="text/javascript"> var arg = 1; function foo(arg) { alert(arg); var arg = 2; } foo(3); </script>
请问alert的输出是什么?
The above is the detailed content of JavaScript running mechanism sample code analysis. For more information, please follow other related articles on the PHP Chinese website!