In this article, we will explain in depth the execution context - the most basic and important concept in JavaScript. I believe that after reading this article, you will understand what is done inside the JavaScript engine before executing the code, why certain functions and variables can be used before they are declared, and how their final values are determined. Defined.
The running environment of code in Javascript is divided into the following three types:
Global level code – this is the default code Running environment, once the code is loaded, this is the environment that the engine first enters.
Function-level code – When a function is executed, the code in the function body is run.
Eval’s code – the code that runs inside the Eval function.
You can find many resources explaining scope on the Internet. In order to make this article easier for everyone to understand, we can think of "execution context" as the running environment or scope of the current code. Let's look at an example below, which includes global and function-level execution contexts:
In the above figure, a total of 4 execution contexts are used. Purple represents the global context; green represents the context within the person function; blue and orange represent the context of the other two functions within the person function. Note that no matter what the situation, there is only one global context, which can be accessed by any other context. In other words, we can access the sayHello variable in the global context in the context of person. Of course, we can also access the variable in the function firstName or lastName.
There is no limit to the number of function contexts. Every time a function is called and executed, the engine will automatically create a new function context. In other words, it will create a new local scope, which can be If private variables are declared in a local scope, the elements in the local scope cannot be directly accessed in the external context. In the above example, the inner function can access the variables declared in the outer context, but not vice versa. So, what is the reason for this? How is it handled inside the engine?
In the browser, the JavaScript engine works as a single thread. That is to say, only one event is activated for processing at a certain time, and other events are placed in the queue, waiting to be processed. The following example diagram describes such a stack:
We already know that when the javascript code file is loaded by the browser, the first one entered by default is a global Execution context. When a function is called and executed in the global context, the program flow enters the called function. At this time, the engine creates a new execution context for the function and pushes it to the top of the execution context stack. The browser always executes the context currently at the top of the stack. Once execution is complete, the context is popped from the top of the stack and then executes code in the context below it. In this way, the contexts in the stack will be executed sequentially and popped off the stack until returning to the global context. Please look at the following example:
(function foo(i) { if (i === 3) { return; } else { foo(++i); } }(0));
After the above foo is declared, it is forced to run directly through the () operator. The function code calls itself three times, and each time the local variable i is increased by 1. Each time the foo function is called on itself, a new execution context is created. Whenever a context completes execution, the previous context is popped off the stack and returned to the previous context until it returns to the global context again. The entire process is abstracted as follows:
It can be seen that the abstract concept of execution context can be summarized as the following points:
Single thread
Synchronous execution
The only global context
function There is no limit to the number of execution contexts
Every time a function is called, a new execution context will be created for it, even if it is the calling function itself.
We now know that whenever a function is called, a new execution context will be created. However, within the JavaScript engine, the context creation process is divided into two phases:
Establishment phase (occurs when a function is called, but when the specific code in the function body is executed Previously)
Establish variables, functions, arguments objects, parameters
Establish scope chain
Determine the value of this
Code execution phase:
Variable assignment, function reference, execution of others Code
实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:
(executionContextObj = { variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ }, scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ }, this: {} }
确切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,但是在函数体被真正执行以前所创建的。函数被调用时,就是我上述所描述的两个阶段中的第一个阶段 – 建立阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象(executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。
上述第一个阶段的具体过程如下:
找到当前上下文中的调用函数的代码
在执行被调用的函数体中的代码以前,开始创建执行上下文
进入第一个阶段-建立阶段:
建立variableObject对象:
初始化作用域链
确定上下文中this的指向对象
建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值
检查当前上下文中的函数声明:
每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用
如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。
代码执行阶段:
执行函数体中的代码,一行一行地运行代码,给variableObject中的变量属性赋值。
下面来看个具体的代码示例:
function foo(i) { var a = 'hello'; var b = function privateB() { }; function c() { } } foo(22);
在调用foo(22)的时候,建立阶段如下:
fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: undefined, b: undefined }, scopeChain: { ... }, this: { ... } }
由此可见,在建立阶段,除了arguments,函数的声明,以及参数被赋予了具体的属性值,其它的变量属性默认的都是undefined。一旦上述建立阶段结束,引擎就会进入代码执行阶段,这个阶段完成后,上述执行上下文对象如下:
fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: 'hello', b: pointer to function privateB() }, scopeChain: { ... }, this: { ... } }
我们看到,只有在代码执行阶段,变量属性才会被赋予具体的值。
在网上一直看到这样的总结: 在函数中声明的变量以及函数,其作用域提升到函数顶部,换句话说,就是一进入函数体,就可以访问到其中声明的变量以及函数。这是对的,但是知道其中的缘由吗?相信你通过上述的解释应该也有所明白了。不过在这边再分析一下。看下面一段代码:
(function() { console.log(typeof foo); // function pointer console.log(typeof bar); // undefined var foo = 'hello', bar = function() { return 'world'; }; function foo() { return 'hello'; } }());
上述代码定义了一个匿名函数,并且通过()运算符强制理解执行。那么我们知道这个时候就会有个执行上下文被创建,我们看到例子中马上可以访问foo以及bar变量,并且通过typeof输出foo为一个函数引用,bar为undefined。
为什么我们可以在声明foo变量以前就可以访问到foo呢?
因为在上下文的建立阶段,先是处理arguments, 参数,接着是函数的声明,最后是变量的声明。那么,发现foo函数的声明后,就会在variableObject下面建立一个foo属性,其值是一个指向函数的引用。当处理变量声明的时候,发现有var foo的声明,但是variableObject已经具有了foo属性,所以直接跳过。当进入代码执行阶段的时候,就可以通过访问到foo属性了,因为它已经就存在,并且是一个函数引用。
为什么bar是undefined呢?
因为bar是变量的声明,在建立阶段的时候,被赋予的默认的值为undefined。由于它只要在代码执行阶段才会被赋予具体的值,所以,当调用typeof(bar)的时候输出的值为undefined。
好了,到此为止,相信你应该对执行上下文有所理解了,这个执行上下文的概念非常重要,务必好好搞懂之!
The above is the detailed content of Discussion about Javascript execution statement context. For more information, please follow other related articles on the PHP Chinese website!