首页 > 解决方案 > pre-parsing and execution context

问题描述

I am trying to better understand what exactly happens when, for example, the V8 engine parses code and defines an execution context for a function.

What most JS courses and books typically discuss is what happens during a "creation phase" and an "execution phase". The language used for JS engines is different: pre-parsing, parsing, AST, baseline and optimized compiling.

My understanding based on my reading so far, although I may be very wrong, is that the "creation phase" is the same as pre-parsing the source code, where the parser does the bare minimum in terms of variable and function declaration so that the code can run. For example, it would not attempt to build an AST for a function before it is invoked in the code. When a function is invoked from the global scope, it will then be pre-parsed (creation phase) and then fully parsed (execution phase). A new execution context with lexical environment, etc. will be created during pre-parsing. This process would then be repeated for each nested function.

Question 1: Are my assumptions correct? Is pre-parsing===creating phase. Question 2: This is the bit that I am mostly unclear about. Does pre-parsing create a function object for a function that is not yet invoked. Is the function object shown in Chrome dev tools at the debugger below, actually created based on pre-parsing alone. Are the argument and caller properties then just updated during the subsequent execution phase.

let fun1 = function(name){
    let newname="Tom";
    console.log(name, newname);
    };
debugger
fun1("John");

//fun1 function as it appears in the dev tools before it is invoked (and presumably before it is added to the execution stack) Script fun1: ƒ (name) arguments: null caller: null length: 1 name: "fun1" prototype: {constructor: ƒ} proto: ƒ ()

//fun1 function after it is invoked and added to the execution stack. Local name: "John" newname: undefined this: Window Script fun1: ƒ (name) arguments: Arguments ["John", callee: ƒ, Symbol(Symbol.iterator): ƒ] caller: null length: 1 name: "fun1" prototype: {constructor: ƒ} proto: ƒ ()

标签: javascriptparsingv8

解决方案


大多数 JS 课程和书籍通常讨论的是......

我不知道“大多数 JS 课程和书籍”,而且该参考资料不够具体,无法查找。如果您想与某处的特定术语用法进行特定比较,请指定您所指的来源。

当从全局范围调用一个函数时,它将被预解析(创建阶段),然后完全解析(执行阶段)。

预解析仅在函数被调用或完全解析之前(很长时间)发生时才有用——这就是它被称为解析的原因。解析不是执行的一部分。解析发生在编译的第一步(或者你可以说:“在编译之前”;同样的事情),这必须在执行开始之前发生。(至少在现代高性能引擎中是这样的;你可以构建一个不编译任何东西而是直接解释源代码的引擎,这意味着有效地解析和执行是一回事,但这会慢得多除了琐碎的脚本之外的所有内容。)

问题1:我的假设是否正确?正在预解析===创建阶段。

一般来说,JS执行的高级概念阶段和引擎内部的实现细节之间没有密切的对应关系。

预解析是(部分)完全可选的性能增强实施策略。如果需要,您可以将其关闭 - 事情会变慢,但可观察到的行为将是相同的。基本思想是引擎将“懒惰地”生成代码,即第一次调用函数时(因为预先“急切地”生成所有代码会导致启动缓慢)。为了能够做到这一点,它必须解决“这里有几百千字节的JS代码,function fun1请编译”的问题,所以它必须知道“fun1”在哪里。这就是“预解析”步骤生成的信息:它基本上创建了一个索引映射函数名称到源文本中定义相应函数的位置。

(作为另一种完全可选的性能增强实现策略,现代引擎对内部变量表示/分配做出了复杂的选择,为了产生正确的行为,需要预先解析以生成有关从其他函数引用的变量的附加数据;所以如果你看在实现中,您会发现它比识别function <name> { ... }范围要复杂得多。您是正确的,预解析不会构建 AST,但出于这个原因它确实构建了范围链。)

问题2:预解析是否为尚未调用的函数创建函数对象。

预解析、完整解析和编译都只影响函数的代码。它们与函数对象本身无关。当你有一个函数对象时,你一般无法判断它是否被预解析,它的代码是否被编译,或者它的代码是否被优化(=再次编译,稍后,将类型反馈带入帐户)与否。函数对象是通过执行相应的外部函数(可能是全局范围内的代码)来创建的。它具有对其执行所需的元数据(代码和其他)的内部隐藏引用。

再次强调这一点:关于引擎何时解析/编译的所有细节是引擎之间不同的内部实现细节,并且随着时间的推移而变化(随着各自团队改进他们的引擎),所以他们通常不能影响程序的执行方式。


推荐阅读