问个 JS 变量提升,块作用域与重复声明的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a Javascript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
Javascript 权威指南第 5 版
Closure: The Definitive Guide
ZacharyM

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

  •  
  •   ZacharyM 2020 年 8 月 10 日 3697 次点击
    这是一个创建于 2083 天前的主题,其中的信息可能已经有所发展或是发生改变。

    先直接抛出问题:

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

    (目测是受 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 条回复    2020-08-28 00:13:03 +08:00
    dartabe
        1
    dartabe  
       2020 年 8 月 10 日
    好像就是重复申明了变量啊 改了一下就对了

    {
    var foo = 1; // 该句报错,“foo 重复声明”
    foo = function (){};
    console.log(typeof(foo));
    }
    ZacharyM
        2
    ZacharyM  
    OP
       2020 年 8 月 10 日
    @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
        3
    dartabe  
       2020 年 8 月 10 日   1
    @ZacharyM
    The function declaration in the block uses ES6 declaration semantics (like let or const), which does not allow redeclarations

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

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

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

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

    其实没必要纠结这些,你也知道不同执行环境有不同执行结果,那就要避免写出这种代码,谁写生产环境写出这种代码那他会被捶死
    Doracis
        8
    Doracis  
       2020 年 8 月 10 日
    @ochatokori 大佬正解,最后一句高亮,一般这样的代码是真的不会放生产的,不符合代码规范不说,一旦出现 bug 会被 leader 锤死,老老实实搬砖不香吗
    palmers
        9
    palmers  
       2020 年 8 月 10 日
    ```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
        10
    yaphets666  
       2020 年 8 月 10 日
    @ochatokori 好像是这样的
    var foo // function 声明的效果
    var foo // 该句报错,“foo 重复声明”
    foo = 1;
    foo=function(){};
    console.log(typeof(foo));
    krapnik
        11
    krapnik  
       2020 年 8 月 10 日
    1.ES6 规定,块级作用域之中,函数声明语句的行为类似于 let ;
    2.函数声明还会提升到所在的块级作用域的头部。
    https://es6.ruanyifeng.com/#docs/let#块级作用域
    dartabe
        12
    dartabe  
       2020 年 8 月 10 日
    @krapnik 为什么楼上那么多瞎回答的还能得赞... V2EX 水平堪忧
    Arrowing
        13
    Arrowing  
       2020 年 8 月 10 日
    本来呢,var 确实是可以重复声明。
    但是你用块级作用域({}) + 函数声明语句( function a(){})之后,就不可以重复声明了。
    ES6 规定,块级作用域之中,函数声明语句的行为类似于 let 。

    只要使以上 2 个条件任意一个失效即可。
    1 、块级作用域干掉,改为用 function 包裹的方式;
    2 、用函数表达式声明函数,即 var foo = function(){} 。
    frankkai
        14
    frankkai  
       2020 年 8 月 10 日
    工作几年会发现 这是个无聊的问题。。
    yzqtdu
        15
    yzqtdu  
       2020 年 8 月 10 日
    这个问题跟 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
        16
    ChanKc  
       2020 年 8 月 19 日 via Android
    无聊的问题
    养成习惯,手动提升,万事大吉
    u823tg
        17
    u823tg  
       2020 年 8 月 28 日
    这种问题没必要研究, 这是 js 缺陷问题。 除非考 js 各种奇淫巧计
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2810 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 120ms UTC 11:38 PVG 19:38 LAX 04:38 JFK 07:38
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86