最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 温故而知新 - 重新认识JavaScript的Hoisting

    正文概述 掘金(文之)   2021-04-03   501

    为JavaScript里面的概念,温故而知新。

    概念:Hositing是什么

    在JavaScript中,如果试图使用尚未声明的变量,会出现ReferenceError错误。毕竟变量都没有声明,JavaScript也就找不到这变量。加上变量的声明,可正常运行:

    console.log(a);
    // Uncaught ReferenceError: a is not defined
    
    var a;
    console.log(a); // undefined
    

    考虑下如果是这样书写:

    console.log(a); // undefined
    var a;
    

    直觉上,程序是自上向下逐行执行的。使用尚未声明的变量a,按理应该出现ReferenceError错误,而实际上却输出了undefined。这种现象,就是Hoistingvar a由于某种原因被"移动"到最上面了。可以理解为如下形式:

    var a;
    console.log(a); // undefined
    

    需要注意

    1. 实际上声明在代码里的位置是不会变的。

    2. hoisting只是针对声明,赋值并不会。

      console.log(a); // undefined
      var a = 2015;
      
      // 理解为如下形式
      var a;
      console.log(a); // undefined
      a = 2015;
      

      这里var a = 2015理解上可分成两个步骤:var aa = 2015

    3. 函数表达式不会hoisting

      fn(); // TypeError: fn is not a function
      var fn = function () {}
      
      // 理解为如下形式
      var fn;
      fn();
      fn = function () {};
      

      这里fn()undefined值进行函数调用导致非法操作,因此抛出TypeError错误。

    函数声明和变量声明,都会hoisting,需要注意的是,函数会优先hoisting

    console.log(fn);
    var fn;
    function fn() {}
    
    // 理解为如下形式
    function fn() {}
    var fn; // 重复声明,会被忽略
    console.log(fn);
    

    对于有参数的函数:

    fn(2016);
    
    function fn(a) {
        console.log(a); // 2016
        var a = 2015;
    }
    
    // 理解为如下形式
    function fn(a) {
        var a = 2016; // 这里对应传参,值为函数调用时候传进来的值
        var a; // 重复声明,会被忽略
        console.log(a);
        a = 2015;
    }
    fn(2016);
    

    总结一下,可以理解Hoisting是处理所有声明的过程。需要注意赋值及函数表达式不会hoisting

    意义:为什么需要Hoisting

    可以处理函数互相调用的场景:

    function fn1(n) {
        if (n > 0) fn2(n);
    }
    
    function fn2(n) {
        console.log(n);
        fn1(n - 1);
    }
    
    fn1(6);
    

    按逐行执行的观念来看,必然存在先后顺序,像fn1fn2之间的相互调用,如果没有hoisting的话,是无法正常运行的。

    规范:Hoisting的运行规则

    具体可以参考规范ECMAScript 2019 Language Specification。与Hoisting相关的,是在8.3 Execution Contexts

    一篇很不错的文章参考Understanding Execution Context and Execution Stack in Javascript

    参考里面的例子:

    var a = 20;
    var b = 40;
    let c = 60;
    
    function foo(d, e) {
        var f = 80;
        
        return d + e + f;
    }
    
    c = foo(a, b);
    

    创建的Execution Context像这样:

    GlobalExectionContext = {
      LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          c: < uninitialized >,
          foo: < func >
        }
        outer: <null>,
        ThisBinding: <Global Object>
      },
      VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          // Identifier bindings go here
          a: undefined,
          b: undefined,
        }
        outer: <null>, 
        ThisBinding: <Global Object>
      }
    }
    

    在运行阶段,变量赋值已经完成。因此GlobalExectionContext在执行阶段看起来就像是这样的:

    GlobalExectionContext = {
      LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          c: 60,
          foo: < func >,
        }
        outer: <null>,
        ThisBinding: <Global Object>
      },
      VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          // Identifier bindings go here
          a: 20,
          b: 40,
        }
        outer: <null>, 
        ThisBinding: <Global Object>
      }
    

    当遇到函数foo(a, b)的调用时,新的FunctionExectionContext被创建并执行函数中的代码。在创建阶段像这样:

    FunctionExectionContext = {
      LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          Arguments: {0: 20, 1: 40, length: 2},
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>,
      },
      VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          f: undefined
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>,
      }
    }
    

    执行完后,看起来像这样:

    FunctionExectionContext = {
      LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          Arguments: {0: 20, 1: 40, length: 2},
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>,
      },
      VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          f: 80
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>,
      }
    }
    

    在函数执行完成以后,返回值会被存储在c里。因此GlobalExectionContext更新。在这之后,代码执行完成,程序运行终止。

    细节:var、let、const在hoisting上的差异

    回顾规范:Hoisting的运行规则,可以注意到在创建阶段,不管是用letconstvar,都会进行hoisting。而差别在于:使用letconst进行声明的时候,设置为uninitialized(未初始化状态),而var会设置为undefined。所以在letconst声明的变量之前访问时,会抛出ReferenceError: Cannot access 'c' before initialization错误。对应的名词为Temporal Dead Zone(暂时性死区)。

    function demo1() {
        console.log(c); // c 的 TDZ 开始
        let c = 10; // c 的 TDZ 结束
    }
    demo1();
    
    function demo2() {
        console.log('begin'); // c 的 TDZ 开始
        let c; // c 的 TDZ 结束
        console.log(c);
        c = 10;
        console.log(c);
    }
    demo2();
    

    总结

    果真是温故而知新,发现自己懂得其实好少。鞭策自己,后续对thisprototypeclosuresscope等,进行温故。

    参考资料

    1. 我知道你懂 hoisting,可是你了解到多深?
    2. MDN: Hoisting
    3. You-Dont-Know-JS 2nd-ed
    4. ECMAScript 2019 Language Specification
    5. Understanding Execution Context and Execution Stack in Javascript
    6. JavaScript高级程序设计

    起源地下载网 » 温故而知新 - 重新认识JavaScript的Hoisting

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元