资讯专栏INFORMATION COLUMN

理解Javascript之执行上下文(Execution Context)

Miracle / 2902人阅读

摘要:浏览器总是运行位于作用域链顶部的当前执行上下文。不同执行上下文之间的变量命名冲突通过攀爬作用域链解决,从局部直到全局。它将攀爬作用域链检查每一个执行上下文的变量对象,寻找和变量名称匹配的值。

1>什么是执行上下文

Javascript中代码的运行环境分为以下三种:
全局级别的代码 - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境。
函数级别的代码 - 当执行一个函数时,运行函数体中的代码。
Eval的代码 - 在Eval函数内运行的代码。
javascript是一个单线程语言,这意味着在浏览器中同时只能做一件事情。当javascript解释器初始执行代码,它首先默认进入全局上下文。每次调用一个函数将会创建一个新的执行上下文。
每次新创建的一个执行上下文会被添加到作用域链的顶部,有时也称为执行或调用栈。浏览器总是运行位于作用域链顶部的当前执行上下文。一旦完成,当前执行上下文将从栈顶被移除并且将控制权归还给之前的执行上下文。

不同执行上下文之间的变量命名冲突通过攀爬作用域链解决,从局部直到全局。这意味着具有相同名称的局部变量在作用域链中有更高的优先级。
简单的说,每次你试图访问函数执行上下文中的变量时,查找进程总是从自己的变量对象开始。如果在自己的变量对象中没发现要查找的变量,继续搜索作用域链。它将攀爬作用域链检查每一个执行上下文的变量对象,寻找和变量名称匹配的值。

2>执行上下文的建立过程

我们现在已经知道,每当调用一个函数时,一个新的执行上下文就会被创建出来。然而,在javascript引擎内部,这个上下文的创建过程具体分为两个阶段:
建立阶段(发生在当调用一个函数时,但是在执行函数体内的具体代码以前)
建立变量,函数,arguments对象,参数
建立作用域链
确定this的值
代码执行阶段:
变量赋值,函数引用,执行其它代码
实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:

executionContextObj = { variableObject: { /* 函数中的arguments对象, 参数,
内部的变量以及函数声明 / }, scopeChain: { / variableObject
以及所有父执行上下文中的variableObject */ }, this: {} }

3>建立阶段以及代码执行阶段的详细分析
确 切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,但是在函数体被真正执行以前所创建的。函数被调用时,就是我 上述所描述的两个阶段中的第一个阶段 - 建立阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象 (executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。
上述第一个阶段的具体过程如下:

1.找到当前上下文中的调用函数的代码
2.在执行被调用的函数体中的代码以前,开始创建执行上下文
3.进入第一个阶段-建立阶段:
    建立variableObject对象:
        建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值
        检查当前上下文中的函数声明:
        每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用
        如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。
        检查当前上下文中的变量声明:
        每找到一个变量的声明,就在variableObject下,用变量名建立一个属性,属性值为undefined。
        如果该变量名已经存在于variableObject属性中,直接跳过(防止指向函数的属性的值被变量属性覆盖为undefined),原属性值不会被修改。
    初始化作用域链
    确定上下文中this的指向对象
4.代码执行阶段:
    执行函数体中的代码,一行一行地运行代码,给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: { ... }
}
我们看到,只有在代码执行阶段,变量属性才会被赋予具体的值。

4>局部变量作用域提升的缘由
在网上一直看到这样的总结: 在函数中声明的变量以及函数,其作用域提升到函数顶部,换句话说,就是一进入函数体,就可以访问到其中声明的变量以及函数。这是对的,但是知道其中的缘由吗?相信你通过上述的解释应该也有所明白了。不过在这边再分析一下。看下面一段代码:

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。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83649.html

相关文章

  • JavaScript优化管理作用域

    摘要:当被创建时,它的作用域链初始化为当前运行函数的属性中的对象,这些值按照他们出现在函数中的顺序,被复制到执行环境的作用域链中。然后这个对象被推入作用域链最前端。 在计算机科学中,数据存储的位置关系到代码执行过程中数据的检索速度,有一个经典的问题即为:通过改变数据的存储位置来获得最佳的读写性能。 Javascript中四种基本的数据存储位置 字面量字面量只代表自身,不存储在特定的位置。...

    fox_soyoung 评论0 收藏0
  • # JavaScript中的执行下文和队列(栈)的关系?

    摘要:为什么会这样这段代码究竟是如何运行的执行上下文堆栈浏览器中的解释器单线程运行。浏览器始终执行位于堆栈顶部的,并且一旦函数完成执行当前操作,它将从堆栈顶部弹出,将控制权返回到当前堆栈中的下方上下文。确定在上下文中的值。 原文:What is the Execution Context & Stack in JavaScript? git地址:JavaScript中的执行上下文和队列(...

    DangoSky 评论0 收藏0
  • 深入学习js——执行下文

    摘要:当遇到函数调用时,引擎为该函数创建一个新的执行上下文并把它压入当前执行栈的顶部。参考链接理解中的执行上下文和执行栈深入之执行上下文栈 开篇 作为一个JavaScript的程序开发者,如果被问到JavaScript代码的执行顺序,你脑海中是不是有一个直观的印象 -- JavaScript 是顺序执行的,可事实真的是这样的吗? 让我们首先看两个小例子: var foo = functio...

    Lucky_Boy 评论0 收藏0
  • 谈谈javascript语法里一些难点问题(二)

    摘要:讲作用域链首先要从作用域讲起,下面是百度百科里对作用域的定义作用域在许多程序设计语言中非常重要。原文出处谈谈语法里一些难点问题二 3) 作用域链相关的问题 作用域链是javascript语言里非常红的概念,很多学习和使用javascript语言的程序员都知道作用域链是理解javascript里很重要的一些概念的关键,这些概念包括this指针,闭包等等,它非常红的另一个重要原因就...

    Enlightenment 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<