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

一道 js 脑筋急转弯求解

  •  1
     
  •   cheese · 2021-12-30 16:15:55 +08:00 · 2812 次点击
    这是一个创建于 820 天前的主题,其中的信息可能已经有所发展或是发生改变。
        {
          function a(){}
          a=1
          function a(){}
        }
        console.log(a,"1");
        {
          function a(){}
          a=1
          
        }
        console.log(a,"2");
        //输出
        //1 '1'
        //ƒ a(){} '2'
    

    为啥第二个块的 a = 1 没有覆盖到全局的 a

    第 1 条附言  ·  2021-12-30 16:55:01 +08:00
    倒也不是面试题。就是朋友发群里了,我尝试 debugger 下,发现了一个奇怪的点。
    对于普通{}块内定义的元素都是直接进入全局中的,函数类型的会同时提升到全局顶部和块级顶部,题目在第一个块内对 a 赋值的时候,会同时改变块内和全局的变量值。
    而奇怪的地方就在于,执行第二个块的 fun a 时,块内,全局的 a 都是同时改变了的。
    而执行到第二个块的 a = 1 时,从浏览器 debugger 监控里可以看到。块内的 a 改变了,而全局却没有变化。也就是表现和第一个块是不同的。
    题目对于工作肯定是没有什么用,所以我把它成为 js 脑筋急转弯。希望评论里的老哥不要太激动,就是想知道下运行时对这种情况处理机制的原因。
    33 条回复    2022-01-01 11:18:35 +08:00
    murmur
        1
    murmur  
       2021-12-30 16:20:31 +08:00
    function a(){} 1
    function a(){} 2

    这是 IE 的结果

    所以出题人傻逼,完毕。
    murmur
        2
    murmur  
       2021-12-30 16:21:36 +08:00
    对了,刚才的结果也适用于 edge 浏览器,edge 够现代吧,不适用于 chrome
    所以这不是出题人傻逼是什么
    abear
        3
    abear  
       2021-12-30 16:21:46 +08:00
    先定义的不管,后定义的招殃
    mxT52CRuqR6o5
        4
    mxT52CRuqR6o5  
       2021-12-30 16:22:25 +08:00
    vanton
        5
    vanton  
       2021-12-30 16:23:50 +08:00
    这个题目不成立啦,不同环境不一样的。
    mxT52CRuqR6o5
        6
    mxT52CRuqR6o5  
       2021-12-30 16:28:42 +08:00
    "use strict";
    let a₀ = undefined;
    {
    let a₁ = function ( ){ };
    a₁ = function ( ){ };
    a₀ = a₁;
    a₁ = 1;
    a₀ = a₁;
    }
    console.log(a₀, "1");
    {
    let a₁ = function ( ){ };
    a₀ = a₁;
    a₁ = 1;
    }
    console.log(a₀, "2");
    大概就是这么个逻辑
    也是因为非严格模式下块作用域有非常奇怪的难以理解的表现,所以搞了个严格模式出来
    mxT52CRuqR6o5
        7
    mxT52CRuqR6o5  
       2021-12-30 16:30:49 +08:00
    es6 标准浏览器的非严格模式下运行这段代码是有一个固定确定的结果的,就是上面说的那个非常奇怪的难以理解的运行逻辑
    iikebug
        8
    iikebug  
       2021-12-30 16:33:24 +08:00
    @mxT52CRuqR6o5 严格模式下,上面那段代码都跑不起来吧
    libook
        9
    libook  
       2021-12-30 16:33:50 +08:00   ❤️ 3
    这个不是脑筋急转弯,这个是作用域提升,声明变量省略的 var 和 funciton 关键字都有作用域提升的特性。

    第一个块的执行顺序实际上是先把两个 funciton 提到全局作用域,因为下面 console.log 输出 a ,所以稍后 a 也提到全局作用域,那么 a 的值变化就是先赋值为第一个 function ,再赋值为第二个 function ,最后赋值为 1 ,块下面 console.log 就输出 a 值为 1 。

    第二个块里面,同样把 function 提到全局,后续因为此时全局已经有 a 了,后面的 console.log 能找到 a ,所以块内的 a 被认为是局部变量没有被提升作用域,所以块下面的 console.log 输出的是第二个块提升出去的 funciton a 。

    换个写法可以帮助你理解这个顺序:

    {
    console.log(a(),"1");
    function a(){return 2}

    a=1
    function a(){return 3}
    }
    console.log(a,"4");
    {
    function a(){return 5}
    a=2
    console.log(a,"6");
    }
    console.log(a(),"7");
    mxT52CRuqR6o5
        10
    mxT52CRuqR6o5  
       2021-12-30 16:34:56 +08:00
    @iikebug 是的,会有 runtime error
    EPr2hh6LADQWqRVH
        11
    EPr2hh6LADQWqRVH  
       2021-12-30 16:37:13 +08:00
    我不知道,我只知道谁敢拿这个给我看我当场让他滚蛋
    66beta
        12
    66beta  
       2021-12-30 16:39:33 +08:00
    {
    console.log(1, window.a, a);
    function a() {}
    console.log(2, window.a, a);
    a = 1;
    console.log(3, window.a, a);
    function a() {}
    console.log(4, window.a, a);
    }
    console.log('========', a, "1");
    {
    console.log(5, window.a, a);
    function a() {}
    console.log(6, window.a, a);
    a = 1;
    console.log(7, window.a, a);
    }
    console.log('========', a, "2");

    1 、函数声明提升到块级顶部
    2 、块内访问 a 是块内的,外面访问的是全局的
    Biwood
        13
    Biwood  
       2021-12-30 16:40:24 +08:00
    脑筋急转弯可还行
    cheese
        14
    cheese  
    OP
       2021-12-30 16:44:08 +08:00
    @murmur #2 edge 及 chrome 我都复现了,chrome 内核下的非严格模式表现应该是一致的。
    iikebug
        15
    iikebug  
       2021-12-30 16:44:30 +08:00
    不能跑严格模式的都叫他滚。真就面试八股文?
    murmur
        16
    murmur  
       2021-12-30 16:45:39 +08:00
    @cheese edge 非 chrome 内核是我的结果,直接命令行打出来啊,要是别人出题有严格模式你跟我们说没严格模式,那是你的问题
    cheese
        17
    cheese  
    OP
       2021-12-30 16:46:43 +08:00
    @mxT52CRuqR6o5 #6 我自己的推测也是类似这个逻辑,但是我没法理解为什么。第二个块到 a₁ = 1;就停止了,而没有 a₀ = a₁; 这个步骤
    churchill
        18
    churchill  
       2021-12-30 16:54:03 +08:00   ❤️ 1
    https://tc39.es/ecma262/#sec-block-level-function-declarations-web-legacy-compatibility-semantics

    > Prior to ECMAScript 2015, the ECMAScript specification did not define the occurrence of a FunctionDeclaration as an element of a Block statement's StatementList. However, support for that form of FunctionDeclaration was an allowable extension and most browser-hosted ECMAScript implementations permitted them. Unfortunately, the semantics of such declarations differ among those implementations.
    churchill
        19
    churchill  
       2021-12-30 16:54:59 +08:00
    js 版的 ub 而已,:)
    mxT52CRuqR6o5
        20
    mxT52CRuqR6o5  
       2021-12-30 16:58:40 +08:00
    murmur
        21
    murmur  
       2021-12-30 16:59:44 +08:00
    你们吐槽谭浩强的勇气呢,怎么现在开始讨论这种恶心人的东西呢,生产写这种东西有几次你被打死几次
    mxT52CRuqR6o5
        22
    mxT52CRuqR6o5  
       2021-12-30 17:04:50 +08:00   ❤️ 1
    @cheese
    第一个块 a₁ = 1 后面有个 a₀ = a₁是因为你原代码第一个块的 a=1 后面有一句 function a(){}啊
    你仔细读读我发的那个 sof 的链接就都明白了


    cheese
        23
    cheese  
    OP
       2021-12-30 17:08:31 +08:00
    @mxT52CRuqR6o5 #22 好的,感谢
    liubaicai
        24
    liubaicai  
       2021-12-30 17:17:47 +08:00
    有什么意义啊,谁生产会写这种代码?面试出这种题的也是 zz 。
    iwasthere
        25
    iwasthere  
       2021-12-30 21:20:34 +08:00 via iPhone
    因为如果变量名称跟已经声明的函数相同,则变量声明不会干扰已经存在的这类属性。
    enchilada2020
        26
    enchilada2020  
       2021-12-30 23:03:53 +08:00 via Android
    我承认我不会 JS
    litel
        27
    litel  
       2021-12-31 11:16:33 +08:00
    大家好像都没解释题主的“第二个块的 a=1 为什么没有提升至全局”
    我也搞不清楚这里……
    litel
        28
    litel  
       2021-12-31 11:29:23 +08:00
    @mxT52CRuqR6o5 那为什么第二个块的 a = 1 不会抛出到全局作用域呢?
    就算是
    b = 1
    { b = 2 }
    b
    // => 2
    块作用域的 b 也会到全局,为什么最后不加同名 funtion 就不会到全局呢?
    这里比较疑惑
    mxT52CRuqR6o5
        29
    mxT52CRuqR6o5  
       2021-12-31 11:34:27 +08:00
    @litel
    /t/825334#r_11227399
    作用域提升相关的东西我不知道他说的对不对,但他所描述的代码执行过程完全就是错的
    我没有说过作用域提升相关的东西,你问我怎么个提升的我也不清楚
    mxT52CRuqR6o5
        30
    mxT52CRuqR6o5  
       2021-12-31 11:40:48 +08:00
    @litel
    你所说的东西我尝试解释一下(一下内容为马后炮,根据结果猜测运行原理)
    es6 非严格模式在块内声明 function 会同时在全局和块内生命同名变量,所以在块内对同名变量赋值只会影响块内

    b = 1
    { b = 2 }
    只有全局的 b ,没有块内的 b ,块内的赋值语句就直接修改了全局的 b
    wangtian2020
        31
    wangtian2020  
       2021-12-31 14:10:37 +08:00
    有 globalThis 我就是不用
    欸我就是玩儿
    wangtian2020
        32
    wangtian2020  
       2021-12-31 14:12:27 +08:00
    qeqv
        33
    qeqv  
       2022-01-01 11:18:35 +08:00
    @wangtian2020 学了这个会不会被同事打死
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2869 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 13:12 · PVG 21:12 · LAX 06:12 · JFK 09:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.