美文网首页哥要学前端!!码无界前端%……%
前端基础进阶(三):变量对象详解

前端基础进阶(三):变量对象详解

作者: 这波能反杀 | 来源:发表于2017-02-11 18:34 被阅读20512次

    在JavaScript中,我们肯定不可避免的需要声明变量和函数,可是JS解析器是如何找到这些变量的呢?我们还得对执行上下文有一个进一步的了解。

    在上一篇文章中,我们已经知道,当调用一个函数时(激活),一个新的执行上下文就会被创建。而一个执行上下文的生命周期可以分为两个阶段。

    • 创建阶段
      在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向。

    • 代码执行阶段
      创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。

    执行上下文生命周期

    从这里我们就可以看出详细了解执行上下文极为重要,因为其中涉及到了变量对象,作用域链,this等很多人没有怎么弄明白,但是却极为重要的概念,它关系到我们能不能真正理解JavaScript。在后面的文章中我们会一一详细总结,这里我们先重点了解变量对象。

    变量对象(Variable Object)

    变量对象的创建,依次经历了以下几个过程。

    1. 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。

    2. 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。

    3. 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。

    许多读者在阅读到这的时候会因为下面的这样场景对于“跳过”一词产生疑问。既然变量声明的foo遇到函数声明的foo会跳过,可是为什么最后foo的输出结果仍然是被覆盖了?

    function foo() { console.log('function foo') }
    var foo = 20;
    
    console.log(foo); // 20
    

    其实只是大家在阅读的时候不够仔细,因为上面的三条规则仅仅适用于变量对象的创建过程。也就是执行上下文的创建过程。而foo = 20是在执行上下文的执行过程中运行的,输出结果自然会是20。对比下例。

    console.log(foo); // function foo
    function foo() { console.log('function foo') }
    var foo = 20;
    
    // 上栗的执行顺序为
    
    // 首先将所有函数声明放入变量对象中
    function foo() { console.log('function foo') }
    
    // 其次将所有变量声明放入变量对象中,但是因为foo已经存在同名函数,因此此时会跳过undefined的赋值
    // var foo = undefined;
    
    // 然后开始执行阶段代码的执行
    console.log(foo); // function foo
    foo = 20;
    
    我知道有的人不喜欢看文字

    根据这个规则,理解变量提升就变得十分简单了。在很多文章中虽然提到了变量提升,但是具体是怎么回事还真的很多人都说不出来,以后在面试中用变量对象的创建过程跟面试官解释变量提升,保证瞬间提升逼格。

    在上面的规则中我们看出,function声明会比var声明优先级更高一点。为了帮助大家更好的理解变量对象,我们结合一些简单的例子来进行探讨。

    // demo01
    function test() {
        console.log(a);
        console.log(foo());
    
        var a = 1;
        function foo() {
            return 2;
        }
    }
    
    test();
    

    在上例中,我们直接从test()的执行上下文开始理解。全局作用域中运行test()时,test()的执行上下文开始创建。为了便于理解,我们用如下的形式来表示

    // 创建过程
    testEC = {
        // 变量对象
        VO: {},
        scopeChain: {}
    }
    
    // 因为本文暂时不详细解释作用域链,所以把变量对象专门提出来说明
    
    // VO 为 Variable Object的缩写,即变量对象
    VO = {
        arguments: {...},  //注:在浏览器的展示中,函数的参数可能并不是放在arguments对象中,这里为了方便理解,我做了这样的处理
        foo: <foo reference>  // 表示foo的地址引用
        a: undefined
    }
    

    未进入执行阶段之前,变量对象中的属性都不能访问!但是进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。

    这样,如果再面试的时候被问到变量对象和活动对象有什么区别,就又可以自如的应答了,他们其实都是同一个对象,只是处于执行上下文的不同生命周期。不过只有处于函数调用栈栈顶的执行上下文中的变量对象,才会变成活动对象。

    // 执行阶段
    VO ->  AO   // Active Object
    AO = {
        arguments: {...},
        foo: <foo reference>,
        a: 1,
        this: Window
    }
    

    因此,上面的例子demo1,执行顺序就变成了这样

    function test() {
        function foo() {
            return 2;
        }
        var a;
        console.log(a);
        console.log(foo());
        a = 1;
    }
    
    test();
    

    再来一个例子,巩固一下我们的理解。

    // demo2
    function test() {
        console.log(foo);
        console.log(bar);
    
        var foo = 'Hello';
        console.log(foo);
        var bar = function () {
            return 'world';
        }
    
        function foo() {
            return 'hello';
        }
    }
    
    test();
    
    // 创建阶段
    VO = {
        arguments: {...},
        foo: <foo reference>,
        bar: undefined
    }
    // 这里有一个需要注意的地方,因为var声明的变量当遇到同名的属性时,会跳过而不会覆盖
    
    // 执行阶段
    VO -> AO
    VO = {
        arguments: {...},
        foo: 'Hello',
        bar: <bar reference>,
        this: Window
    }
    

    需要结合上面的知识,仔细对比这个例子中变量对象从创建阶段到执行阶段的变化,如果你已经理解了,说明变量对象相关的东西都已经难不倒你了。

    全局上下文的变量对象

    以浏览器中为例,全局对象为window。
    全局上下文有一个特殊的地方,它的变量对象,就是window对象。而这个特殊,在this指向上也同样适用,this也是指向window。

    // 以浏览器中为例,全局对象为window
    // 全局上下文
    windowEC = {
        VO: Window,
        scopeChain: {},
        this: Window
    }
    

    除此之外,全局上下文的生命周期,与程序的生命周期一致,只要程序运行不结束,比如关掉浏览器窗口,全局上下文就会一直存在。其他所有的上下文环境,都能直接访问全局上下文的属性。

    相关文章

      网友评论

      • 35e92a150a2a:按照作者所说的,在创建阶段已经确定的了this的指向,VO中的this的指向应该也可以出来了
      • d2895325f956:对JS的理解真的很深入,感谢!
      • d2895325f956:勘误:倒数第二个例子中:
        // 执行阶段
        VO -> AO
        VO = {
        arguments: {...},
        foo: 'Hello',
        bar: <bar reference>,
        this: Window
        }

        这里应该已经转换为活动对象AO了~
      • ling_ling1314:大神~
      • 1d12bf61967f:确实挺清晰的,但是好像还有个小的知识点没说明白:
        if(false){
        console.log(a());
        function a(){
        console.log('true');
        }
        }
        a();
        像这种情况 是有点问题,感觉说清楚 会好一点
      • b9df74c18e44:大神,这个demo:
        console.log(foo); // function foo
        function foo() { console.log('function foo') }
        var foo = 20;

        输出不应该是 foo() { console.log('function foo') }吗?怎么直接输出 function foo了?
      • quentin2012:函数表达式 不能提升 匿名函数 一等公民
      • a2d7b36d630e:看来扫描创建阶段 和 执行阶段 区分开来才能解释
      • a2d7b36d630e:上次腾讯有个面试就追问怎么提升。。
      • 路某人_lu:看了好一会,终于明白了变量对象的创建与执行阶段的不同点,讲一下自己对两个例子的见解,希望有误解的话大家指出来;
        首先是变量函数对象的创建阶段,此时,所有的‘=’赋值都不会进行,先创建函数声明的对象,在创建var声明的对象,此时var声明的对象名称与已有函数名称一样的话会跳过,跳过的意思是就当什么事情都没发上,其余的var声明的对象值都为undefined。
        因此,在demo1中,函数执行之前只有foo函数变量和a undefined变量,然后开始执行,当执行到第四行时,才开始给a赋值。
        在demo2中,在执行之前只有foo函数变量和var bar变量,然后执行到第四行时才给foo重新赋值为‘hello’,执行到第六行值给bar 赋值为函数。
        建议不懂的同学可以把console.log放在不同位置试一试。
      • Evangelist:个人感觉 , 比汤姆大叔的还要明确 , 还有一图胜前言.:+1:
      • f6f1d302d3eb:大神您好,当调用一个函数时(激活),一个新的执行上下文就会被创建。执行上下文分为两个周期,其中this到底是在执行上下文创建的周期中指向的,还是在执行周期中产生的因为我看您是在// 执行阶段把this的值进行赋值指向的所以不是很明白请指教。
      • 清嘉与羡书:老师,想问一下关于最后的全局上下文的变量对象为什么是window?那全局的变量和函数不属于全局上下文的变量对象吗?
      • nineSean:第二遍,发现前文定义可执行上下文在创建阶段的生命周期确定this指向,但2个例子都是在执行阶段确定this的,这里是否矛盾了?
        f6f1d302d3eb:我也觉得有点问题
      • 85195901c8d7:学的差点连对象都找不到了
      • 9aeda3c0fea8:感谢老师,对我找工作真的很有帮助,写得清楚易懂
      • cdbd3c42e122:不错!赞!
      • hkcmd:有点疑惑:
        在函数被调用(激活)后,JavaScript解释器会构造一个当前函数的执行上下文对象,同时创建VO,然后根据上下文填充VO,最后把上下文对象压入栈内存空间执行函数。是这样的过程吗?
      • 而生lhw:基础太差,每篇文章都仔细的读几遍才能理解,受益匪浅,对学习js重拾信心。
      • edbf68625e18:有一个疑问:demo2中执行阶段foo 是通过var 声明来改变原来为function 的foo变量的,根据上文var 变量在遇到同名属性时应该是跳过而不是覆盖,可是第二次输出foo为'hello',这是为什么呢?
        这波能反杀:@松果子爱吃皮蛋花 执行阶段赋值了
      • 5567c7396dc4:有个问题 function test() {
        function foo() {
        return 2;
        }
        var a;
        console.log(a);
        console.log(foo());
        a = 1;
        }

        test();经过解析以后为什么先var a呢 这时候是不是相当于a是undefined;还有就是为什么给var a赋值是在最后 这个有点没看懂
        这波能反杀:@Iaminjinan 结合上下文对照前后例子看,别单独理解
      • AuthorJim:别拦我,我要点赞
      • 冷小谦:有个问题,执行上下文在函数执行阶段,可是变量提升发生在预编译阶段,应该在执行上下文前
      • 45d78bf2b689:```
        function test() {
        console.log(str);
        console.log(foo());
        var str = 'Hello s';
        console.log(str);
        function foo() {
        return 'hello f';
        }
        }

        test();
        ```

        输出结果为:
        undefined
        hello f
        hello s

        变量没有提升,函数提升了。
        提醒作者,函数名和变量名不要取相同的导致歧义。
        石头上的话多:@william1780 如果没有属性值的话,你写的函数里面第一个console.log(str),怎么会打印出undefined呢。
        45d78bf2b689:实际上,变量对象创建时,只是把变量声明提升了,而赋值表达式未提升。所以我认为,“变量对象”小标题下的第一句话有些问题,应该是“建立该对象下的属性”,而非“建立该对象下的属性与属性值。”
      • 45d78bf2b689:```javascript
        console.log("评论里的小伙伴,你们都不会用markdown缩进吗?看着真难受!");
        ```
        45d78bf2b689:```
        alert("test!");
        ```

        45d78bf2b689:好吧,简书评论竟然不支持markdown,汗!:sweat:
      • cef0ee210a74:// 执行阶段
        VO -> AO
        VO = {
        arguments: {...},
        foo: 'Hello',
        bar: <bar reference>
        }

        这个框里各个参数到底什么意思?<bar reference>又是什么意思,被你整糊涂了。如果你是参考外文的,建议先自己理解清楚再来介绍啊。
      • 罗彬727:大神,在执行阶段,变量名和函数同名,会被变量名覆盖?
      • 勿忘巛心安:想问下,这图是用什么软件画的?
      • df1b85e0c408:ES6中用let、const可以避免变量提升,使用这种方式申明的变量跟变量对象有联系吗?
        这波能反杀:@sterchois 与var差不多,但改变了变量提升相关的规则
      • jmtung:请问下博主是在哪学习到这些知识的,有相关书籍推荐吗:flushed:
      • demo11:反复看了两遍,终于理解了,谢谢
      • c1c38f6f74eb:我看有些文章说活动对象指的是 "在函数执行上下文中,VO是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO的角色..."这点上和您的说法不同,原文地址在这http://www.cnblogs.com/TomXu/archive/2012/01/16/2309728.html,您能帮我解答下吗?
        c1c38f6f74eb:@波同学 那篇文章我理解的是全局上下文中全局对象是变量对象,函数上下文中变量对象不能直接访问到,通过活动对象访问,活动对象中能通过arguments属性访问到函数的参数,感觉变量对象是一个总称,不同上下文通过不同的对象访问,但您的文章中说“进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。”这点上有些不解
        这波能反杀:@lIrUIl0nG 他的意思你没懂
      • 0c09725b07dc:大大的点赞😀
      • wlszouc:和波老师探讨一下个人对执行上下文的理解:
        1. 创建作用域链;
        2. 创建活动对象: 创建argument对象 -> 函数声明 -> 变量声明;
        3. 将活动对象的指针 push 到作用域链。
        4. 初始化活动对象: this、argument和命名参数的值 -> 变量对象;

        这完全是我个人臆想且没有出处,和波老师的VO、AO有所不同,但似乎也能说明问题,是否有错误和忽视,请波老师指正。
      • de96d6727049:在讲变量申明过程那三点的时候,其实容易导致一个误解,就是当函数名和变量名同名时,可能会让人误以为最终结果是函数覆盖变量,因为当函数名存在时,变量名不再申明,避免把函数改为undefined。当然上述是个误解啦,不过还是应该明确说,虽然在申明阶段同名变量由于同名函数的存在不会申明,但是在赋值阶段变量赋值会覆盖这个名称,结论当然是同名情况变量覆盖函数,只是覆盖时机是赋值阶段而不是申明阶段。波老师写的很棒~:+1:
      • 全凭一口仙气儿活着:假如当函数b写在for循环里面时候,我在global下查看发现b一开始undefined,而没有函数的引用,后来循环执行完,函数引用在被放在全局中,for也没有块级运用域,为什么在全局上下文的时候,没有把函数的引用给提取出来,这怎么解释呢
      • 全凭一口仙气儿活着:举的例子没有带参数的函数,希望能补充上这种情况,怎么在控制台看到对参数的操作呢?
      • geekWeb:请问波同学 你是用什么软件画图的
      • 5d6055729b64:最开始学JS的时候常听说JS有预编译阶段和执行阶段,这两个阶段对应的是全局环境的执行上下文的创建阶段和代码执行阶段吗
        f6f1d302d3eb:@这波能反杀 那预扫描阶段到底是啥?
        这波能反杀:@fclive 不是
      • ac0d7c6cc785:function test() {
        console.log(foo);
        var foo = 'Hello';
        function foo() {
        return 'hello';
        }
        console.log(foo);
        }

        test();
        输出结果:
        function foo() {
        return 'hello';
        }
        Hello

        谁能给我讲一下,为什么第二个console输出的不是函数体了?
        function test() {
        console.log(foo);
        function foo() {
        return 'hello';
        }
        var foo = 'Hello';
        console.log(foo);
        }

        test();
        把函数声明和变量定义位置换一下也不行,输出结果不变,为什么?
        ac0d7c6cc785: @波同学 又看了两遍,是变量对象创建过程的第2条说的,函数名的属性已存在,该属性将会被新的引用所覆盖,所以不管换不换位置都会输出新的引用,是这个原因吧
        ac0d7c6cc785: @波同学 这块是没太搞懂,不是函数声明先被执行么
        这波能反杀:@Rchi 你好像没怎么理解到文章的内容
      • 罗彬727:<script type="text/javascript">
        function test(){
        function foo(){
        return 'hello';
        };
        var foo;
        var bar;
        console.log(foo); // undefined
        console.log(bar); // undefined
        foo='hello';
        console.log(foo); // hello
        bar=function(){
        return 'world';
        }
        };
        test();
        </script>
        demo2 执行顺序是这样的吗 ?
      • 罗彬727:老师 ,在创建阶段 ,定义的变量 与 函数名 都是赋值为 undefined么? 当执行阶段创建活动对象吃开始 赋予真正的值?
      • 5d6055729b64:关于文章中说的变量对象创建的三个阶段我有一个疑问,创建arguments对象、检查函数声明、检查变量声明这三个阶段是有先后顺序的吗
        这波能反杀:@fclive you
      • 670c95f959d1:终于明白怎么回事了啊啊啊谢谢波天使👼
      • b0e94107a567:最喜欢在工作的时候看文章了,这是病么
      • 代码驴:function t3(greet){
        var greet = 'hello';
        alert(greet);
        function greet(){
        }
        alert(greet);
        }
        t3(null);
        --------------------------------------------
        创建过程:
        t3EC = {
        //变量对象
        VO:{}
        }
        VO = {
        //分析参数,建立该对象下的属性和属性值
        arguments:{greet:null},
        //分析函数声明
        greet : <greet reference>
        //分析变量声明,找到"var greet",但greet已经存在,所以在此跳过
        }
        ----------------------------------------------
        //执行过程
        VO->AO
        AO = {
        arguments:{greet:null},
        //原来的greet是指向一个函数引用的地址,但此时被'hello'覆盖了
        greet:'hello'
        }

        波老师,我这样分析对么
        代码驴:因为你上面的例子都没有穿参的,所以我来了个传参的例子:grin:
      • fb1fdf20a5c2: @波同学 我对比了javascript中对于产量对象这一块的描写,发现有一些冲突,在4.2节中有一句话"如果这个环境是函数,则将其活动对象作为变量对象。活动对象最开始时只包含一个变量,即arguments对象",这句话让我对你说说的,活动对象就是变量对象在进入执行阶段后转变而来,这种理解产生了怀疑,而且你说说的执行上下文和函数调用栈书中的说法是执行环境和环境栈,不知道你所说的这些名词是自己理解的,还是一些比较权威的名词,希望得到你的解答😬
      • a267748c9b68:demo2创建阶段foo:指向的是函数,执行阶段foo又指向了‘Hello’,不是很懂,执行阶段的变量处理难道不是按照创建阶段的顺序来的吗?
        function foo(){
        console.log(1);
        }
        var foo=function(){
        console.log(2);
        }
        foo();//2
        按照上面的理论函数优先于变量,那这个不应该输出的是1吗?
      • jia58960:特地登录来评论。恰巧最近在看红宝书,觉着楼主讲的红宝书更细节,也更通俗易懂。必须给赞!:+1:
      • 一个胖猴:波老师最后一个的例子执行顺序能说一下吗?我看有个朋友写出的顺序是这样的
        function test3(){
        function foo(){
        return "hello";
        }
        var foo;
        var bar;
        console.log(foo);
        console.log(bar);
        foo="Hello";
        console.log(foo);
        bar=function(){
        return "word";
        }
        }
        test3();
        感觉有点问题啊,要这样的顺序,第一个console.log(foo)应该是underfined啊,而且console.log() 什么时候才执行?
        一个胖猴:好像明白了,是不在有等于号时,变量对象就会变成活动对象
      • 28b9797224a8://自己根据您的例子,写了一下分析;
        有点不太懂在fn和innerFoo都指向同一个变量时,
        bar()函数中调用fn()时,
        //fn()函数执行上下文入栈
        //?此处到底是fn()执行上下文入栈还是innerFoo()执行上下文入栈?
        以及在创建变量对象与活动对象时,
        只有fn会有属性跟属性值还是两者都?
        var fn = null;
        function foo(){
        var a = 2;
        function innerFoo(){
        console.log(a);
        }
        fn = innerFoo; //将innerFoo的引用赋值给全局变量中的fn
        }
        function bar(){
        fn();//此处保留innerFoo的引用
        }
        foo();
        bar();

        //执行上下文出入栈
        //global上下文入栈
        //执行代码foo(),foo()函数执行上下文入栈
        //foo()执行上下文出栈
        //bar()函数执行上下文入栈
        //fn()函数执行上下文入栈
        //?此处到底是fn()执行上下文入栈还是innerFoo()执行上下文入栈?
        //fn()执行上下文出栈
        //bar()执行上下文出栈
        //直到网页关闭global上下文出栈

        //代码执行分为两个阶段:编译器编译、执行代码
        //执行代码阶段完成执行上下文创建
        //执行上下文创建分为两个阶段:上下文创建阶段、代码执行阶段
        //创建过程
        //fooEC = {
        // VO:{},//变量对象
        // scopeChain:{},//作用域链
        // this:{} //this的指向
        // }
        //其中VO为变量对象
        //VO = {
        // arguments:{..},//参数
        // a: undefined,
        // innerFoo: <innerFoo reference>, //表示地址引用
        //innerFoo与fn指向都一个地址该如何处理??
        // fn: <fn reference>
        //}
        //执行阶段
        //VO->AO 进入执行阶段,变量对象转变为活动对象
        //AO = {
        // arguments:{...},
        // a: 2,
        // innerFoo: <innerFoo reference>,
        // fn: <fn reference>
        //}
        石头上的话多:他俩指向的不是同一个函数对象吗?fn,innerFoo只是属性名称,指向是相同的。我是这么理解的
      • 28b9797224a8:在函数环境中,arguments指的就是包括参数,参数值的对象吧?
        这波能反杀:@嬡莪莂趉 嗯
      • 050fd8e44481:由变量对象到活动对象的转变,具体做了些什么操作,比如变量是什么时候被赋值的
      • 土豪码农:感觉看一次看不太懂,要多看几次,大神:kissing_heart:
      • 2519d4d01140:你好,大神!我有一个疑问需要请教你:
        function setName (obj){
        obj.name = "Nicholas";
        obj = new Object(); //函数内部新实例化一个 obj 对象
        obj.name = 'Greg';
        console.log(obj.name);
        }
        var person = new Object();
        setName(person);
        console.log(person.name)
        console.log(obj.name) //obj is not defined
        此处在函数内部新实例化的obj为什么在函数外面访问不到呢?
        这波能反杀:@拖拉机要飞 你这样写,obj也是全局
        2519d4d01140:@波同学
        function(){
        obj = new Object()
        a = 7
        }
        在函数外面可以访问到 a ,但访问不到 obj。 函数内部写的 a = 7 是全局的,为什么同样方式写的 obj 是局部的? 这是什么原因造成的?我在这一点上比较疑惑
        这波能反杀:函数外部肯定访问不了函数内部的变量呀,这是作用域决定的
      • c1c38f6f74eb:执行上下文在创建阶段会先进行函数声明提升,会在变量对象中创建一个同名的属性,指向函数的引用,再执行变量声明提升,如果变量名与函数声明的名称相同,会跳过,原属性值不会被修改,我这样理解对吗~~
        这波能反杀:@lIrUIl0nG 对的
      • d01d8d7404c0:我看有的文章写的是在活动对象创建arguments对象,这里有点疑惑。:no_mouth:
        这波能反杀:我学到的是变量对象时创建此对象
      • chenpipy:波老师,这个打印e结果是1,岂不是说明var声明的函数也会覆盖同名的函数属性吗?
        var t = function(){
        console.log(1);
        }

        function t(){
        console.log(2);
        }

        t();
        chenpipy: @chenpipy 我明白了,🤔
        这波能反杀:var t = xxx 是要分成2个步骤来理解的,文中应该有说
      • 土豪码农:冒昧的问一句,波老师这样水平的在外面工作大概多少钱工资呢
        9ab719968975:年薪50w+:smirk:
        这波能反杀:不可说
      • denvey:这节有点问题,应该是
        var声明并赋值 > function声明 > formal parameter > 预定义变量 > var声明不赋值 > 函数外部作用域的其他所有声明

        function test(a){

        // function声明 > formal parameter
        /*console.log(a);
        function a() {
        return '1';
        }*/

        // formal parameter > 预定义变量
        var a;
        console.log(a);

        // 预定义变量 > var声明不赋值
        console.log(arguments);
        // var arguments;

        // var声明不赋值 > 函数外部作用域的其他所有声明
        console.log(test);
        var test;

        }
        test(100);
      • 9ab719968975:对于function关键字申明的函数
        function foo() {
        return 'hello';
        }
        在变量提升时,会在变量对象中以【函数名】建立一个属性,属性值为指向该函数所在内存地址的引用。

        那么,对象字面量方法申明的函数
        var bar = function () {
        return 'world';
        }
        由于创建时是无名函数,变量对象中的新建的属性名该是什么呢?
        这波能反杀:@Ludis 你把后面的理解成一个值就行了,不要纠结这个
        9ab719968975:@波同学
      • 代码驴:var tmp = new Date();

        function f() {
        console.log(tmp);
        if (false) {
        var tmp = 'hello world';
        }
        }

        f(); // undefined
        谢谢你的进阶系列,写的通俗易懂,我想问的是,在我这个例子中,为什么在if语句里面的声明也提前了呢?
        这波能反杀:@代码驴 if不会创建新的作用域
      • 4cadc4053b78:变量对象VO是储存在stack 还是heap里面呢?
        这波能反杀:@huholmes heap
      • 807932529775:很厉害,有些我知道怎么回事,但要我说就说不明白,学习了。
        这波能反杀:@807932529775 :pray:
      • 北鸟南游:创建过程和执行过程这两个概念区分太重要了,而且过程中执行的任务是有明确分工。这些基础太不容易理解。谢谢作者倾囊相助!
      • 54efb55e3c64:function test(foo){
        var foo;
        function foo(){console.log(111)}
        console.log(foo)
        }

        test(4) // function foo(){console.log(111)}

        ****************************
        function test(foo=7){
        var foo;
        function foo(){console.log(111)}
        console.log(foo)
        }

        test(4) //4

        ************************

        第一个demo,波同学可不可以解释一下这种情况吗? 按照波同学的意思,参数arguments在创建阶段应该是优先级最高的,那么执行test(4)时, foo被赋值为“4”的这一步是在Context的执行阶段进行,还是在创建阶段进行呢?如果是在执行阶段,那这个算是赋值操作吧,理应会覆盖原本函数foo的声明吧?

        第二个demo更疑惑,如果用了ES6的默认值,最终的输出值就只会是4
        54efb55e3c64:@波同学 测试了一下,感觉这边文章的讲解更合理一些:http://enml.github.io/site/2014/06/13/js-resolution/
        这波能反杀:@C晓zoom 奥,我之前还没认真思考过这个问题呀。因为函数参数时按值传递,所以直接传值其实是没有什么赋值操作的,就是穿了一个值进来。但是如果指定了默认参数,就会存在一个赋值的操作,这样赋值操作就会在声明的后面执行。
      • bc61c7d9f8fa:波老师我想问一个问题:js的编译阶段一般就发生在执行前的几微秒,编译过程中会将变量声明,函数声明进行词法分析,如果遇到语法错误他是立即停止编译抛出异常还是要等到执行阶段再抛出异常?还有一个就是函数里面的变量声明和内部函数的声明的编译是在这个函数执行的阶段前几微秒进行的还是在全局环境进行编译的时候就已经编译好了?
        bc61c7d9f8fa:我之前看来你不知道的js系列里面有讲到,为了优化有时候会进行延迟编译推迟到后面,这怎么理解
      • 6536e0a782f4:引擎是怎么知道有重名的变量和函数呢
      • 6536e0a782f4: function test() {
        console.log(a);
        console.log(b); //执行时这儿就会报b is not defined
        console.log(foo());

        var a = 1;
        function foo() {
        return 2;
        }
        }

        test();

        b is not defined ,说明如果在AO中没有b,在程序中添加上var b;(不赋值)时,console.log(b)就会显示为undefined,在AO中有b这个变量,只不过值是undefined
      • 车仔面:变量提升个人理解:每次当控制器(js引擎)转到可执行代码的时候,就会进入一个执行上下文。也就是当前代码的执行环境,而在执行上下文的生命周期中,有两个阶段,一个创建阶段,一个执行阶段。创建阶段的第一步是创建变量对象,而创建变量对象的三步:(变量函数的形参)、(var, 变量声明)、(函数声明)。 而执行阶段才会变量赋值,就是先声明,后赋值。
        回归线_3c5b:而创建变量对象的三步:(变量函数的形参)、(var, 变量声明)、(函数声明)。 是有顺序的 (函数声明)>(var, 变量声明)
        这波能反杀:@车仔面 good
      • liangh:function test() {
        console.log(foo());
        console.log(bar);

        var foo = 'Hello';
        console.log(foo());
        var bar = function () {
        return 'world';
        }

        function foo() {
        return 'hello';
        }
        }
        test();

        波波,如果我把第二个例子改成这样。提示:foo is not a function 这个原因是??
        5c7650875e94:你说的这个是第二个 console.log(foo()); 里报的吧?执行到这里的时候,foo 已经不是一个 function了,属性值变成字符串 'Hello' 了,当然就是 not a function 了。
      • 年轻小子:问一个问题啊 ,执行上下文创建阶段,如果函数中用到全局的var声明的变量,那个变量也放到vo中吧??
      • 小小码世界:推荐《你不知道的js》里面有详细说明
        4cadc4053b78: @yhhwpp 您好,第几本有啊,谢谢您
      • d75717b37870:我也是对demo2当中的foo值有疑惑,根据文中的理解,变量对象foo是先声明为foo() {};之后就声明为underfined;那当执行第一次console.log(foo);时;为什么会输出foo() {}而不是underfined?
        6536e0a782f4:@_RAt 程序怎么知道有重名的呢?
        dadadahui:@Stussury_41 跳过是啥意思?
        是允许同名的变量和function,同时存在么?
        41d4e5c3e748:@_RAt a 因为function等级比var高,当var保存属性名的时候和function属性名冲突了,冲突不会覆盖,而是跳过,文章开头的变量对象的过程有说。
      • 0979e81ac054:专门注册了简书来关注你,写的非常好,大有受益
      • 907f1df1dffe:说明的很清楚,谢谢作者
      • 发不动车的老司机:看过的最全面易懂的解析:+1:
      • 沁浒:for (var i = 1; i <= 5; i++) {
        (function(i) {
        setTimeout(function timer() {
        console.log(i);
        }, i * 1000)
        })(i);
        }

        可否理解成
        AO1 = {
        arguemnts: {
        i: 1
        }
        }
        setTimeout(function timer() {
        console.log(AO1.arguments.i);
        }, AO1.arguments.i * 1000)

        AO2 = {
        arguemnts: {
        i: 2
        }
        }
        setTimeout(function timer() {
        console.log(AO2.arguments.i);
        }, AO2.arguments.i * 1000)

        ...
      • 69a7b6c3b4c7:昨天看了一遍,今天又来看一遍,依然受益匪浅,感谢
      • mmmage:第二个例子,为什么属性foo对函数的引用会被后来的「hello」覆盖?
        这波能反杀:你要结合生命周期的不同阶段来理解,主要是创建阶段和执行阶段的不同,在文章里都说清楚了
      • 我就不信这个昵称也不合法:有意思,demo2中,执行上下文创建阶段,由于存在function foo,所以var foo会被忽略。然后在执行阶段,语句var foo='Hello',使得foo函数变为字符串了。
        DHFE:都是变量声明,属性相同情况下,后来声明将被跳过,但赋值不会跳过。
        var a = 1; var a = 10;第二个a声明跳过,但执行阶段赋值是10对吗?

        如果是属性相同的函数声明和变量声明,函数声明优先级最高,所以执行阶段前将会是持有函数引用,但是由于赋值操作在执行阶段,最后总会被重新赋值,覆盖原先的函数引用

        波波大神我这样解释对吗?
        0ee2b173518e:@波同学 **总结**
        - 变量未初始化(未赋值)就 调用该变量 的时候,*创建阶段,function声明优于var 声明*
        1. 若存在 function 属性,则调用该函数体(同名则覆盖);若不存在则 undefined

        - 变量初始化就 调用该变量 的时候,*执行阶段,var 声明优于function声明*
        1. 若存在 var 属性,优先执行,同名则覆盖。
        2. 若不存在var属性,存在 function 属性,则执行 function 属性函数体,否则为undefined
        这波能反杀:创建阶段,function声明优先级比var高,因此var的同名会被忽略。执行阶段,赋值的操作是由foo='Hello'来完成。var foo = 'Hello'是要分成两步来执行的
      • 我就不信这个昵称也不合法:在同一个执行上下文中,有function foo和var foo 。后者会被忽略?
      • 公子宸:看完了,受益匪浅
      • 越夜鸣:再次回顾文章的时候,不理解这里创建过程中创建的arguments里面到底包含什么内容呢?
        越夜鸣:@波同学 哦!这里指的是函数传入参数,我想多了(・∀・ ≡=-
        这波能反杀:@越夜鸣 你在函数内部,加一句这个,console.log(arguments);在浏览器里就能看到arguments的具体内容了
      • 48b740514c05:就问一个问题,TDZ也是在VO(AO)里面吗
        这波能反杀:不知道TDZ是什么,没见过 ~ ~
      • e3097d76ee9d:终于搞懂了,挺开心的,还有意外收获,console.log(foo)会打印函数体,console.log(foo())才打印函数执行结果。
      • 1891f2bdc81a:老师,您好。我想问下,在创建阶段:var a ,声明a,,并给a undefined;执行阶段:给a 值。那么在这个过程中,创建阶段和执行阶段,函数都是引用 地址,这句:“foo: <foo reference>。”似乎没变呢!那么上面说的 执行阶段 函数引用是引用的什么 啊。地址已经赋值给foo 了。
        老师,这个地方不是很懂。
        1891f2bdc81a:@波同学 好的,谢谢!
        这波能反杀:var a = 0 ,分为var a; a =0 2个步骤执行的。但是function声明的不会分成2个步骤
      • 向布谷鸟说早安:如果是没有带var的变量,如var a=2变成a=2又该放到哪里呢?
        这波能反杀:@向布谷鸟说早安 全局
      • 向布谷鸟说早安:变量对象是个什么,好抽象,这么说arguments是变量对象中的对象?它又有属性和值?
        这波能反杀:变量对象就是专门用来存储变量的特殊对象,arguments就是函数的参数对象,你可以打印出来看一看
      • 70caf26359c9:"声明的变量当遇到同名的属性时,会跳过而不会覆盖",这种情况仅限于同一个作用域(当前执行上下文),如:
        funtion test(){
        var a = 1;
        var a ;
        console.log(a);//1
        }
        而处于不同作用域(不同执行上下文)时,变量会从新声明:
        var a = 1;
        funtion test(){
        var a;
        console.log(a);//undefined
        }
        对否?
        这波能反杀:@立子530 结论是对的,但是理解有点偏差。不同的作用域,变量对象都不一样了,所以就谈不上覆盖与否了,建议把这几篇文章结合起来理解。
      • 70caf26359c9:个人理解简单来讲:
        1.变量提升,就是奖变量的声明提升到该变量所属的作用域顶部,但是变量值不会提升,只有在实际赋值的时候才会有值。
        console.log(a);//undefined
        var a =1;
        console.log(a);//1

        2.函数提升,分为声明式函数( function test() )和表达式函数( var test = funtion()),而函数提升只针对于声明式函数,这是是为什么很多时候我们可以通过如下方式调用函数,不会出错:
        test();
        function test(){
        console.log(1);
        }

        而通过这种方式会提示函数未定义:
        test();
        var test = function(){
        console.log(1)
        }
        这波能反杀:@立子530 有点小偏差。
      • 夏目祐太:function test3(){
        function foo(){
        return "hello";
        }
        var foo;
        var bar;
        console.log(foo);
        console.log(bar);
        foo="Hello";
        console.log(foo);
        bar=function(){
        return "word";
        }
        }
        test3();
        最后一个例子是否变成了这样,感觉理解起来还是时间,基础不好
        4ae8933d134e:@744bb97e6c1a 在创建阶段,如果有第三步检查变量声明时,如果已经存在相同的属性名,则会跳过。所以var foo 是没有的。
        744bb97e6c1a:@波同学 那个 var foo; 应该不会申明吧? 因为如果申明了第一个console应该是undefined,而实际上是函数
        这波能反杀:@关于郑州想的全是你 good
      • kerush:// 执行阶段
        VO -> AO
        VO = {
        arguments: {...},
        foo: 'Hello',
        bar: <bar reference>
        }
        按以上的执行阶段来说
        demo2 里的 console.log(bar) 结果为啥是undefind?



        这波能反杀:@Ludis 有点问题。你要记住,只要是var声明的,都是变量声明,而不是函数声明
        9ab719968975:var bar = function () {
        return 'world';
        }
        //创建阶段:
        function () {
        return 'world';
        }
        //地址指针:x000001
        var bar = undefined;
        //执行阶段:
        var bar = x000001
        这波能反杀:仔细看看文中说的变量对象的具体变化过程就知道了
      • 07d09d5b6dca:想问下如果像demo2里面的var bar = function () {}这种声名函数的方式,在创建变量对象的时候,会不会有一个匿名的function呢?
        这波能反杀:@月半羊 不会,你的表述不太准确,有优先级之分的只是var 声明 与 function声明
        07d09d5b6dca:@波同学 也就是说变量对象在创建的过程中,遇到函数表达式的时候会略过是吧?
        这波能反杀:不会,匿名函数指的是没有引用的函数。你的这个例子里叫做函数表达式,有一个引用被保存在了变量对象里,是可以被找得到的
      • f01a8555aa1a:var bar = function () {
        return 'world';
        }
        想问下,这个有var变量声明,又有function函数声明,在创建变量的时候,该如何理解?
        这波能反杀:function后面直接括号的这种写法不算是一种声明,可以粗略的理解为一种创建了一个函数对象吧。
        a26b64abc4ee:声明阶段执行的是 var bar 和 function() {return 'world';} ; 假如function 在栈中的索引是 0x3993999afd; 执行阶段就是 bar = 0x3993999afd;
        3b486b7b0896:个人理解函数表达式是把函数赋值给一个变量,遵循的应该还是变量声明
      • Neuro_annie:波比 你多大了……
        77d555eb8d43:@波同学 厉害了 word波比
        这波能反杀:@风暴阿呆 :smirk_cat:20cm
      • Yard:看完收益匪浅,以前一知半解的特性,只是知道怎么出现这种问题和怎么使用,这次终于深入的了解了整个原理。
      • 0c84e50e56b9: 请问全局上下文环境一直是处于创建阶段么?
        5c7650875e94:@波同学 如果是这样,那闭包那一篇的《作用域链图示》最后的global对象是不是也应该是 AO 才对?
        这波能反杀:一直处于执行阶段

      本文标题:前端基础进阶(三):变量对象详解

      本文链接:https://www.haomeiwen.com/subject/nekoittx.html