在本文中,我们将深入解释执行上下文JavaScript中最基本和最重要的概念。相信看完这篇文章,你会明白javascript引擎在执行代码之前做了什么,为什么有些函数和变量可以在声明之前使用,以及它们的最终值是如何定义的。
什么是执行上下文
有三种运行Javascript代码的环境:
全局级代码这是默认的代码运行环境。一旦代码被加载,引擎首先进入这个环境。
函数级代码当函数被执行时,函数体中的代码被运行。
评估代码在评估函数中运行的代码。
网上可以找到很多资源来解释范围。为了使这篇文章易于大家理解,我们可以将“执行上下文”视为当前代码的运行环境或范围。让我们看一个例子,它包括全局和函数级执行上下文:
在上图中,有四个执行上下文。紫色代表全球语境;绿色代表人的功能范围内的上下文;蓝色和橙色代表人物功能中其他两个功能的上下文。请注意,在任何情况下,只有一个全局上下文可以被任何其他上下文访问。也就是说,我们可以在person的上下文中访问全局上下文中的sayHello变量,当然这个变量也可以在函数firstName或lastName中访问。
函数上下文的数量没有限制。每次调用和执行函数时,引擎都会自动创建一个新的函数上下文。换句话说,它将创建一个新的局部范围,在其中可以声明私有变量。不能在外部上下文中直接访问此本地范围内的元素。在上面的例子中,内部函数可以在外部上下文中访问声明的变量,否则它将不起作用。那么,是什么原因呢?发动机内部是如何处理的?
执行上下文堆栈
在浏览器中,javascript引擎在一个线程中工作。也就是说,在某个时刻,只有一个事件被激活处理,其他事件被放入队列,等待处理。下面的示例图描述了这样的堆栈:
我们已经知道,当浏览器加载javascript代码文件时,默认情况下会首先输入全局执行上下文。当一个函数在全局上下文中被调用和执行时,程序流进入被调用的函数,引擎为该函数创建一个新的执行上下文,并将其推到执行上下文堆栈的顶部。浏览器总是在堆栈顶部执行当前上下文。一旦执行完成,上下文将从堆栈顶部弹出,然后在它下面输入上下文来执行代码。这样,堆栈中的上下文将依次执行,堆栈将弹出,直到它返回到全局上下文。请看下面的例子:
(function foo(I){ if(I===3){ return;} else { foo(I);} }(0));在上面的foo被声明之后,它被()操作符强制直接运行。函数代码调用自己三次,每次局部变量I增加1。每次foo函数被自己调用时,都会创建一个新的执行上下文。每当执行一个上下文时,上层上下文从堆栈中弹出,返回到前一个上下文,直到它再次返回到全局上下文。整个过程抽象如下图:所示
可以看出,执行上下文的抽象概念可以概括如下:
单线程
同步执行
独特的全球背景
函数的执行上下文数量没有限制
每次调用一个函数,都会为它创建一个新的执行上下文,甚至是调用函数本身。
执行上下文的建立过程
我们现在知道,每次调用一个函数,都会创建一个新的执行上下文。然而,在javascript引擎中,该上下文的创建过程具体分为两个阶段:
构建阶段(在调用函数时,但在执行函数体中的特定代码之前发生)
建立变量、函数、参数对象和参数
建立范围链
确定这个的值
代码执行阶段:
变量赋值、函数引用、其他代码的执行
实际上,执行上下文可以看作一个对象,它包含以上三个属性:
(executioncontextobj={ variable object : {/*详细分析参数对象、参数、内部变量和函数声明*/},scope Chang : {/* variable object和variableobject */}在所有父执行上下文中,这是3360 {}}建立阶段和代码执行阶段
准确地说,执行上下文对象(上面提到的executionContextObj)是在调用函数时创建的,但在实际执行函数体之前。当函数被调用时,它是我上面描述的两个阶段中的第一个——建立阶段。此时,引擎将检查函数中的参数、声明的变量和内部函数,然后基于这些信息构建一个executionContextObj对象。在这个阶段,将确定变量对象、范围链以及由此指向的对象。
第一阶段的具体流程如下:
查找在当前上下文中调用函数的代码
在执行被调用函数体中的代码之前,开始创建执行上下文
进入第一阶段-构建阶段:
建立变量对象对象:
建立参数对象,检查当前上下文中的参数,并在该对象下建立属性和属性值
检查当前上下文中的函数声明:
每次找到函数声明时,都会在variableObject下用函数名创建一个属性。属性值是对内存中函数地址的引用
如果上述函数名已经存在于变量对象下,相应的属性值将被新引用覆盖。
初始化范围链
确定它在上下文中指向的对象
代码执行阶段:
执行函数体中的代码,逐行运行代码,并为variableObject中的变量属性赋值。
让我们看一个具体的代码示例:
函数foo(I){ var a=' hello ';var b=函数privateB(){ };函数c(){ } } foo(22);当调用foo(22)时,建立阶段如下:
foexecutioncontext={ variable object : { arguments : { 0: 22,length: 1 },i: 22,c:指向函数c()的指针a:未定义,B:未定义},scopechan: {.},这: {.}}由此可以看出,在建立阶段,除了参数、函数声明和参数被赋予特定的属性值外,其他变量属性默认是未定义的。一旦上述建立阶段结束,引擎将进入代码执行阶段。该阶段完成后,上述执行上下文对象如下:
foexecutioncontext={ variableobject : { arguments : { 0: 22,length: 1 },i: 22,c:指向函数c()的指针a: 'hello ',B:指向函数privateb ()}的指针,作用域链: {.},这个: {.}}我们可以看到,只有在代码执行阶段,变量属性才会被赋予特定的值。
扩大局部变量范围的原因
我在网上一直看到这样一个总结:函数中声明的变量和函数的范围被提升到函数的顶部,换句话说,其中声明的变量和函数一进入函数体就可以被访问。这是真的,但是你知道为什么吗?相信你也应该通过上面的解释来理解。但是在这里再分析一下。
请看下面的代码:
(function(){ console . log(foo的类型);//函数指针console.log(类型为bar);//undefined var foo='hello ',bar=function(){ return ' world ';};function foo() {返回“hello”;} }());上面的代码定义了一个匿名函数,该函数被迫通过()运算符来理解和执行。那么我们知道此时将创建一个执行上下文。我们可以看到,foo和bar变量可以在示例中立即访问,foo作为函数引用输出,bar通过typeof未定义。
为什么我们可以在声明foo变量之前访问foo?
因为在上下文建立阶段,首先处理参数和参数,然后是函数的声明,最后是变量的声明。然后,在发现foo函数的声明后,将在variableObject下建立一个foo属性,其值是对该函数的引用。处理变量声明时,发现有var foo的声明,但variableObject已经有foo属性,所以直接跳过。当您进入代码执行阶段时,您可以访问foo属性,因为它已经存在并且是一个函数引用。
为什么bar是未定义的?
因为bar是变量的声明,所以在构建阶段分配给它的默认值是未定义的。因为只有在代码执行阶段才会给它一个特定的值,所以调用typeof(bar)时的输出值是未定义的。
好了,就这样。我相信你应该对执行环境有所了解。这个执行上下文的概念非常重要。一定要理解好!
以上对Javascript执行上下文的全面理解是边肖与大家分享的全部内容。希望能给大家一个参考,支持我们。