zoukankan      html  css  js  c++  java
  • 前端基础进阶(三)-史上最详细的变量对象详解

    hello,大家好!先摆一张图,大家可以看一下,是否能看懂呢

    img

     

    OK,在JavaScript,声明变量和函数是做项目开发不可避免的,甚至大部分都是声明变量和函数,JavaScript最核心的就是函数.js编译器是如何找到这些变量的呢?

    我们还得对执行上下文有一个进一步的了解。

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

    • 创建阶段

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

    • 代码执行阶段

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

    img

    执行上下文生命周期

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

    变量对象(Variable Object)

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

     

    // 这里a为属性名,20是属性值
    {
     a: 20
    }

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

    函数参数

    二、检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用

    三、检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined

    如果变量与函数同名,则在这个阶段,以函数值为准

     

    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;

    img

    我知道有的人不喜欢看文字

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

    在上面的规则中我们看出,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
    }

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

    let/const

    ES6中,新增了使用let/const来声明变量。我想他们的使用肯定难不倒大家。可是有一个问题不知道大家思考过没有,let/const声明的变量,是否还会变量提升?

    是的,这个刁钻的问题也成为了各大面试官爱问的细节。很贱!可也没办法,还是要弄明白怎么回事!

    我们来做个试验,验证一下这个问题:

    第一步,我们直接使用一个未定义的变量

     

    console.log(a);

    报错信息如下:

    img

    not defined

    第二步,我们在let之前调用变量

     

    console.log(a);
    let a = 10;

    会发生什么?会打印出undefined吗?

    看看结果

    img

    不能在初始化之前访问a

    这个报错说明了什么问题呢?变量定义了,但是没有初始化。

    所以在这里我们就可以得出结论:let/const声明的变量,仍然会提前被收集到变量对象中,但和var不同的是,let/const定义的变量,不会在这个时候给他赋值undefined。

    因为完全没有赋值,即使变量提升了,我们也不能在赋值之前调用他。这就是我们常说的暂时性死区

    当然,变量提升的现象有的时候确实会对我们的代码造成一些负面影响,所以在开发中要养成好的习惯,把将要声明的变量放在最前面来写.

    感谢大家的阅读,谢谢!

     

  • 相关阅读:
    iOS “请在微信客户端打开链接” UIWebview加载H5页面携带session、cookie、User-Agent信息 设置cookie、清除cookie、设置User-Agent
    iOS AR技术初体验,使用EasyAR示例程序的小白指南
    导入GPUImage,实时滤镜相机,GUPImage遇到的问题解决,_OBJC_METACLASS_$_GBGPUImageView in GBGPUImageView.o
    iOS 除去图片的白色背景(接近白色),或者其它颜色的替换,获取像素点的ARGB值
    用const取代宏定义更好的管理内存
    一些字体设计的练习
    Appium 解决锁屏截屏问题(java篇)
    解决Appium无元素可选的如何定位(java篇)
    解决Appium 抓取toast(java篇)
    Appium 解决手势密码 (java篇)
  • 原文地址:https://www.cnblogs.com/coderhf/p/12770043.html
Copyright © 2011-2022 走看看