• 深入js——变量对象


    前言

    文章深入js——执行上下文栈主要讲了代码执行过程中,执行上下文栈的变化,从文本开始,主要研究下执行上下文内部。
    与执行上下文相关的3个概念:

    • 变量对象(Variable object,VO)
    • 作用域链(Scope chain)
    • this
      本文首先研究下变量对象。

    变量对象VO

    变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的数据。包括

    • 变量 (var, 变量声明);
    • 函数声明 (FunctionDeclaration, 缩写为FD);
    • 函数的形参(仅在函数执行上下文中存在)
      全局上下文和函数上下文中的变量对象有些不同,因此以下分开讨论。

    浏览器控制台查看VO

    在讨论前,我们先看下在浏览器控制台如何查看VO,以帮助我们更好的理解后续内容。

    之前在文章深入js——执行上下文栈中提到通过Call Stack查看执行上下文栈,变量对象也可以在控制台中看到。
    变量对象

    图中红框的部分确切来说是用来看作用域链的,但这块还没讲到,我们可以暂且这样做:需要观察哪个执行上下文的变量对象,就debugger到对应的可执行代码块中,然后观察Local内变量的状态。

    全局上下文中的变量对象

    全局对象

    全局对象Global是JavaScript最特别的一个对象,是在进入任何执行上下文之前就已经创建了的对象。
    全局对象在初始阶段就将Math、String、Date、parseInt等属性作为自身属性初始化,同样在全局作用域创建的所有变量或方法,也就是我们通常称为的全局对象或全局方法,也都是全局对象的属性。

    区别全局对象和window
    window只是Global在浏览器环境下的一种体现,也就是在浏览器中Global === window,但不能混淆这2个概念。比如在Node.js中,Global仍然存在,但不等于window了。

    全局上下文中的VO

    通过以上对全局上下文的解释,我们可以知道,
    全局上下文中的变量对象就是Global。即

    VO(globalContext) === global;
    

    也就是在全局上下文中的变量,都可以通过Global的属性来访问。

    debugger
    var x = 1;
    function bar (b) {
        debugger
        var a = 1;
        console.log(arguments)
        function foo () {
            console.log(a)
        }
        foo()
    }
    bar(1)
    

    运行上述代码,打开控制台。在第一个debugger处,可以看到右侧是Global,展开会发现里面包含了很多Global自带的属性(Math、String等),同时也包含了我们声明的x变量和bar函数。
    因此,说了这么多,就是一句话:全局上下文中的VO就是全局对象!

    函数上下文的VO

    在函数上下文中,将活动对象(activation object, AO)作为变量对象。
    活动对象是在进入函数上下文时刻被创建的,它通过函数的arguments属性初始化。
    注释掉bar中的其他变量声明

    可以看到,进入bar时,arguments就已经初始化了。

    整体流程

    除了形参和arguments的差异,全局上下文和函数上下文的变量对象的变化过程都是一样的,因此下面统一讨论。

    进入执行上下文

    在进入执行上下文时,变量对象VO已经包含了如下属性

    • 函数形参(仅存在与函数执行上下文)
      由名称和对应值组成的一个变量对象的属性被创建;没有传递对应参数的话,那么由名称和undefined值组成的一种变量对象的属性也将被创建。
    • 函数声明
      由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建;如果变量对象已经存在相同名称的属性,则完全替换这个属性
    • 变量声明
      — 由名称和对应值(undefined)组成一个变量对象的属性被创建;如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

    还是上面那段代码,在第二个debugger处查看函数的变量对象

    说明:除了形参和arguments,其他变量在全局环境中观察也是一样的,只是Global中太多自身属性,要找到我们定义的变量比较麻烦,因此就在函数中观察,但结果可扩展到全局上下文中。

    函数变量对象
    可以看到,包含了arguments、函数foo、形参b、变量a

    这里要特别说明2个问题:

    1.函数声明会覆盖同名的变量声明,后面的函数声明会覆盖同名的前面的函数声明

    function bar () {
    		foo();
    		function foo () {
    			console.log(1)
    		}
    		function foo () {
    			console.log(2)
    		}
    		var foo = 1;
    	}
    	bar()
    

    打开控制台,可以看到进入执行上下文时,即使var foo = 1在函数声明后面,最终foo也是函数而不是变量,且console会打印出2(后面的函数声明覆盖了前面的)。
    结果

    2.变量只能通过使用var关键字才能声明

    变量声明要通过var, 即var b = 10是声明了变量foo,但b = 10没有。

    console.log(a); // undefined
    console.log(b); // 报错,"b" 没有声明
    b = 10;
    var a = 20;
    

    console.log(b)会报错,因为在进入b还没有声明,查看右侧Global中也会发现,在进入执行上下文时,只有a,没有b

    var b = 10可以分解为2个步骤:变量声明和变量赋值,但b = 10只是一个单纯的赋值操作(这里是Global.b = 10)。而变量声明是在进入执行上下文发生,而赋值操作是在执行赋值代码时发生,因此在赋值操作前是访问不到b的。

    代码执行

    在代码执行阶段,就会按顺序执行代码,根据代码修改变量的值。
    沿用上面的一个例子,修改下

    function bar () {
    debugger
    		var foo = 1;
    		foo();
    		function foo () {
    			console.log(1)
    		}
    		function foo () {
    			console.log(2)
    		}
    	}
    	bar()
    

    打开控制台,会发现,在debugger处(进入执行上下文),foo是函数,且没有报错;但继续顺序执行代码会报错,因为执行到var foo = 1,此时foo已经被赋值为1,不为函数,故执行foo()时会报错。
    通过以上例子,能比较好的分清楚进入执行上下文阶段和代码执行阶段的区别。

  • 相关阅读:
    CStringArray序列化处理
    【转】C++ Incorrect Memory Usage and Corrupted Memory(模拟C++程序内存使用崩溃问题)
    【转】Native Thread for Win32 C- Creating Processes(通俗易懂,非常好)
    【转】Native Thread for Win32 B-Threads Synchronization(通俗易懂,非常好)
    【转】Native Thread for Win32 A- Create Thread(通俗易懂,非常好)
    【转】关于OnPaint的工作机制
    Window发声函数Beep、MessageBeep
    Sqlite
    VC++ Splash Window封装类CSplash
    通过代码注册COM、DLL组件
  • 原文地址:https://www.cnblogs.com/youhong/p/12206037.html
Copyright © 2020-2023  润新知