V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
ZacharyM
V2EX  ›  JavaScript

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

  •  
  •   ZacharyM · 2020-08-10 02:28:23 +08:00 · 3182 次点击
    这是一个创建于 1596 天前的主题,其中的信息可能已经有所发展或是发生改变。

    先直接抛出问题:

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

    (目测是受 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-08-10 02:55:48 +08:00
    好像就是重复申明了变量啊 改了一下就对了

    {
    var foo = 1; // 该句报错,“foo 重复声明”
    foo = function (){};
    console.log(typeof(foo));
    }
    ZacharyM
        2
    ZacharyM  
    OP
       2020-08-10 03:31:14 +08:00
    @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-08-10 03:32:20 +08:00   ❤️ 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-08-10 03:38:29 +08:00 via iPhone
    @dartabe 这里用的是 var,不算 ES6 declaration semantics 吧。
    dartabe
        5
    dartabe  
       2020-08-10 04:16:47 +08:00
    @ZacharyM

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

    {
    var foo = 1; // 该句报错,“foo 重复声明”
    let foo = function(){};
    console.log(typeof(foo));
    }
    ianva
        6
    ianva  
       2020-08-10 05:45:08 +08:00
    这个要看 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-08-10 08:06:50 +08:00 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-08-10 09:27:52 +08:00
    @ochatokori 大佬正解,最后一句高亮,一般这样的代码是真的不会放生产的,不符合代码规范不说,一旦出现 bug 会被 leader 锤死,老老实实搬砖不香吗
    palmers
        9
    palmers  
       2020-08-10 09:37:09 +08:00
    ```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-08-10 09:46:06 +08:00
    @ochatokori 好像是这样的
    var foo // function 声明的效果
    var foo // 该句报错,“foo 重复声明”
    foo = 1;
    foo=function(){};
    console.log(typeof(foo));
    krapnik
        11
    krapnik  
       2020-08-10 10:26:16 +08:00
    1.ES6 规定,块级作用域之中,函数声明语句的行为类似于 let ;
    2.函数声明还会提升到所在的块级作用域的头部。
    https://es6.ruanyifeng.com/#docs/let#块级作用域
    dartabe
        12
    dartabe  
       2020-08-10 10:52:09 +08:00
    @krapnik 为什么楼上那么多瞎回答的还能得赞... V2EX 水平堪忧
    Arrowing
        13
    Arrowing  
       2020-08-10 11:14:38 +08:00
    本来呢,var 确实是可以重复声明。
    但是你用块级作用域({}) + 函数声明语句( function a(){})之后,就不可以重复声明了。
    ES6 规定,块级作用域之中,函数声明语句的行为类似于 let 。

    只要使以上 2 个条件任意一个失效即可。
    1 、块级作用域干掉,改为用 function 包裹的方式;
    2 、用函数表达式声明函数,即 var foo = function(){} 。
    frankkai
        14
    frankkai  
       2020-08-10 11:18:02 +08:00
    工作几年会发现 这是个无聊的问题。。
    yzqtdu
        15
    yzqtdu  
       2020-08-10 13:33:08 +08:00
    这个问题跟 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-08-19 12:41:57 +08:00 via Android
    无聊的问题
    养成习惯,手动提升,万事大吉
    u823tg
        17
    u823tg  
       2020-08-28 00:13:03 +08:00
    这种问题没必要研究, 这是 js 缺陷问题。 除非考 js 各种奇淫巧计
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3596 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 04:53 · PVG 12:53 · LAX 20:53 · JFK 23:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.