推荐关注
› Meteor
› JSLint - a JavaScript code quality tool
› jsFiddle
› D3.js
› WebStorm
推荐书目
› JavaScript 权威指南第 5 版
› Closure: The Definitive Guide
ZacharyM

问个 JS 变量提升,块作用域与重复声明的问题

  •  
  •   ZacharyM · Aug 10, 2020 · 4317 views
    This topic created in 2276 days ago, the information mentioned may be changed or developed.

    先直接抛出问题:

    • 以下代码报错,求解该怎么具体分析这个错误发生的原因?

    (目测是受 ES6 块作用域 TDZ 的影响在 编译阶段 抛出的问题)

    {
        var foo = 1;  // 该句报错,“foo 重复声明”
        function foo(){};
        console.log(typeof(foo));
    }
    
    • 以下理解是否正确?(希望各位 dalao 不吝赐教

      个人理解的变量提升(只考虑 var 的情况)步骤为:

      1. 区分函数声明/变量声明。
      2. 函数声明的标识符先提升至函数作用域顶部,函数定义提升至块作用域顶部。
      3. 变量声明的标识符提升至函数作用域的顶部。
      4. 赋值语句留在原地等待执行阶段。

    小白的心路历程

    (自学 js 中)最近在看《 you-dont-know-js 》—— js 的变量提升部分(一版,中文)

    在作用域-函数优先部分有如下代码:

    foo(); // "b"
    
    var a = true;
    if (a) {
       function foo() { console.log( "a" ); }
    }
    else {
       function foo() { console.log( "b" ); }
    }
    

    实际运行时发现 foo(); 一行报 TypeError: foo is not a function 错误。

    思考之后觉得应该是 es6 块作用域的问题,导致 foo 的变量提升不如预期。遂更换 node 版本至 4.9 果然成功输出了 "b"

    我的理解如下:

    var foo;
    var a;
    foo();  // 此次相当于 foo 已声明,但未定义,暂为 undefined 。故报错
    a = true;
    if(a){
        foo = function(){console.log("a")};
    }else{
        foo = function(){console.log("b")}
    }
    

    在理解上面这个出错问题的时候发现在块作用域下 var foo 变量声明和 foo 函数声明放在一起会报 重复声明 错。如下:

    {
        var foo = 1;  // 该句报错,“foo 重复声明”
        function foo(){};
        console.log(typeof(foo));
    }
    

    已知 var 可重复声明,该情况(指同标识符的变量声明与函数声明)在全局、函数作用域下无 重复声明 问题。

    (后测试同样的代码 node4.9 版本无此问题,个人考虑定位至块作用域特性相关问题,调试发现在还没执行下去的时候就已报错了,应该是 编译阶段 就抛出的问题。

    个人基础较差,再往下就没啥头绪了,搜索"块作用域 var 函数声明 重复声明"相关字符也没找到结果。

    希望各位 dalao 不吝赐教,指点以下该怎么分析这个问题(特性? 实际代码运行的时候是怎么样的情况?

    17 replies  •  2020-08-28 00:13:03 +08:00
    dartabe
    dartabe
    Aug 10, 2020
    1
    好像就是重复申明了变量啊 改了一下就对了

    {
    var foo = 1; // 该句报错,“foo 重复声明”
    foo = function (){};
    console.log(typeof(foo));
    }
    ZacharyM
    ZacharyM
    OP
    Aug 10, 2020
    2
    @dartabe emm 问题不是怎么改,主要是想知道 js 具体是怎么处理和判断这个错误的。
    另外有趣的是在 Edge(非 chrome 内核版本,Microsoft Edge 44.19041.1.0)、ios(13.5.1)上的 safari 以及 nodejs6.0.0 以下版本执行不报错,直接输出"number"。
    Firefox,safari,chrome,nodejs6.0.0 及以上的才会报重复声明的错
    dartabe
    dartabe
    Aug 10, 2020❤️1
    3
    @ZacharyM
    The function declaration in the block uses ES6 declaration semantics (like let or const), which does not allow redeclarations.

    帮你在 stackOverFlow 上查了 还是多 google 好......
    ZacharyM
    ZacharyM
    OP
    Aug 10, 2020 via iPhone
    4
    @dartabe 这里用的是 var,不算 ES6 declaration semantics 吧。
    dartabe
    dartabe
    Aug 10, 2020
    5
    @ZacharyM

    好像是 function declaration 的行为和 let const 一样 改为 let 有同样问题

    {
    var foo = 1; // 该句报错,“foo 重复声明”
    let foo = function(){};
    console.log(typeof(foo));
    }
    ianva
    ianva
    Aug 10, 2020
    6
    这个要看 ecma262 的规范,才比较好理解,推荐篇两篇文章,看懂执行模型就理解了,第一篇是 08 年的,不过讲的最清楚,不了解规范会有些复杂
    https://www.cnblogs.com/RicCC/archive/2008/02/15/JavaScript-Object-Model-Execution-Model.html
    https://juejin.im/post/6844903704466833421
    ochatokori
    ochatokori
    Aug 10, 2020 via Android❤️3
    7
    你可以理解成 function xx 声明都会被改写成
    var xx // 被提前到代码最开头
    //执行你声明 function 前的代码
    xx=function(){}

    所以你写的代码执行顺序就是
    var foo // function 声明的效果
    var foo = 1; // 该句报错,“foo 重复声明”
    foo=function(){};
    console.log(typeof(foo));

    其实没必要纠结这些,你也知道不同执行环境有不同执行结果,那就要避免写出这种代码,谁写生产环境写出这种代码那他会被捶死
    Doracis
    Doracis
    Aug 10, 2020
    8
    @ochatokori 大佬正解,最后一句高亮,一般这样的代码是真的不会放生产的,不符合代码规范不说,一旦出现 bug 会被 leader 锤死,老老实实搬砖不香吗
    palmers
    palmers
    Aug 10, 2020
    9
    ```js
    {
    var foo = 1; // 该句报错,“foo 重复声明”
    function foo(){};
    console.log(typeof(foo));
    }
    ```
    最后的代码 在 ES5 中应该是会报错的吧? 我想是因为函数声明提升和变量声明提升 但是函数声明提升优先于变量声明提升, 所以变成这样了:
    ```js
    {
    var foo = function() {}
    var foo = 1;
    console.log(typeof(foo));
    }
    ```
    所以在做提升的时候 也就是编译阶段就已经知道重复声明变量了 会提示 Identifier 'foo' has already been declared

    我理解是这样
    yaphets666
    yaphets666
    Aug 10, 2020
    10
    @ochatokori 好像是这样的
    var foo // function 声明的效果
    var foo // 该句报错,“foo 重复声明”
    foo = 1;
    foo=function(){};
    console.log(typeof(foo));
    krapnik
    krapnik
    Aug 10, 2020
    11
    1.ES6 规定,块级作用域之中,函数声明语句的行为类似于 let ;
    2.函数声明还会提升到所在的块级作用域的头部。
    https://es6.ruanyifeng.com/#docs/let#块级作用域
    dartabe
    dartabe
    Aug 10, 2020
    12
    @krapnik 为什么楼上那么多瞎回答的还能得赞... V2EX 水平堪忧
    Arrowing
    Arrowing
    Aug 10, 2020
    13
    本来呢,var 确实是可以重复声明。
    但是你用块级作用域({}) + 函数声明语句( function a(){})之后,就不可以重复声明了。
    ES6 规定,块级作用域之中,函数声明语句的行为类似于 let 。

    只要使以上 2 个条件任意一个失效即可。
    1 、块级作用域干掉,改为用 function 包裹的方式;
    2 、用函数表达式声明函数,即 var foo = function(){} 。
    frankkai
    frankkai
    Aug 10, 2020
    14
    工作几年会发现 这是个无聊的问题。。
    yzqtdu
    yzqtdu
    Aug 10, 2020
    15
    这个问题跟 ECMAScript 语言定义有关,ES5 之前,块内的函数声明是未定义行为,具体表现跟旧浏览器的实现有关,总体来说,这种方式是不推荐的。ES6 之后引入了块级作用域,对块内函数声明的语义也进行了定义,可以理解为 let 或 const,因此会报错。https://www.ecma-international.org/ecma-262/#sec-block-level-function-declarations-web-legacy-compatibility-semantics


    为了历史兼容,其实块内函数声明会带来一些“bug”,具体见 https://www.zhihu.com/question/404772996
    ChanKc
    ChanKc
    Aug 19, 2020 via Android
    16
    无聊的问题
    养成习惯,手动提升,万事大吉
    u823tg
    u823tg
    Aug 28, 2020
    17
    这种问题没必要研究, 这是 js 缺陷问题。 除非考 js 各种奇淫巧计
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   1521 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 44ms · UTC 23:16 · PVG 07:16 · LAX 15:16 · JFK 18:16
    ♥ Do have faith in what you're doing.