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

关于词法作用域和闭包的一点疑问

  •  
  •   Aloehuang · 2020-05-04 11:45:36 +08:00 · 3572 次点击
    这是一个创建于 1450 天前的主题,其中的信息可能已经有所发展或是发生改变。

    let a = 0;

    function addone() { let a = 10; addtwo(); }

    function addtwo() { let a = 20; addthree(); }

    function addthree() { console.log(a); }

    addone();

    结果为 0,为什么不是 20 呢?不是说一层一层从调用上下文查找直到全局上下文吗?

    38 条回复    2020-05-06 01:25:05 +08:00
    YadongZhang
        1
    YadongZhang  
       2020-05-04 11:48:55 +08:00 via Android
    log 的是全局的 a,let 和 var 是不是有区别
    Aloehuang
        2
    Aloehuang  
    OP
       2020-05-04 11:49:40 +08:00
    YadongZhang
        3
    YadongZhang  
       2020-05-04 11:51:22 +08:00 via Android
    @Aloehuang #2 javascript.info 了解一下,有图解
    Aloehuang
        4
    Aloehuang  
    OP
       2020-05-04 11:53:36 +08:00
    @YadongZhang var 和 let 在这里没有区别,结果都是 0.
    你给的网站内容太多了,找不到具体要查找的问题
    rabbbit
        5
    rabbbit  
       2020-05-04 11:54:07 +08:00
    js 的作用域是词法作用域,不是动态作用域.
    secondwtq
        6
    secondwtq  
       2020-05-04 11:54:25 +08:00 via iPhone   ❤️ 1
    楼主所期望的行为恰恰是 Lexical Scoping 的反面,即 Dynamic Scoping
    把这两个的定义都找来,对比一下就能明白了
    YadongZhang
        7
    YadongZhang  
       2020-05-04 11:56:39 +08:00 via Android
    @Aloehuang #4 有搜索框,关键词 Closure,addThree() 调用的是全局 a,函数里的变量只能在函数块里使用,log 找不到的
    ljpCN
        8
    ljpCN  
       2020-05-04 11:57:05 +08:00 via Android
    这个例子应该没有涉及闭包吧,闭包主要是把一些局部作用域的变量保持着不被回收掉。
    然后,这个你只看词法作用域就行了,只有 this 那种东西才要看运行时的调用吧。
    Aloehuang
        9
    Aloehuang  
    OP
       2020-05-04 11:57:13 +08:00
    @rabbbit 有没有这方面的文章可以看看?您这样说不是特别理解
    rabbbit
        10
    rabbbit  
       2020-05-04 11:57:23 +08:00
    词法作用域: 在哪定义,在哪往上找变量
    静态作用域: 在哪调用,在哪往上找变量
    rabbbit
        11
    rabbbit  
       2020-05-04 11:58:21 +08:00
    静态作用域 -> 动态作用域
    rabbbit
        12
    rabbbit  
       2020-05-04 12:00:11 +08:00   ❤️ 2
    上边那个"静态作用域"写错了

    词法(静态)作用域: 在哪定义,在哪往上找变量
    动态作用域: 在哪调用,在哪往上找变量
    Dyon
        13
    Dyon  
       2020-05-04 12:13:53 +08:00 via Android   ❤️ 1
    全局有一个 a,前两个函数又在自己的作用于中定义了一个新的 a,当他们调用名为 a 的变量的时候会现在自己的作用域中找,找到了就调用它,这时候全局的那个 a 就被忽略了。第三个函数的作用域中没有名为 a 的变量,于是它调用了全局的 a 。这里用 let 还是 var 结果是一样的因为这里都是函数,如果是在 for 等块级语法中,let 和 var 才会不一样。
    Aloehuang
        14
    Aloehuang  
    OP
       2020-05-04 12:14:23 +08:00
    @secondwtq 谢谢,一针见血
    Aloehuang
        15
    Aloehuang  
    OP
       2020-05-04 12:15:54 +08:00
    @Dyon 那第三个函数为什么不从第二个函数里面找 a
    mrcode
        16
    mrcode  
       2020-05-04 12:18:52 +08:00
    JavaScript 是词法作用域,不是动态作用域
    YadongZhang
        17
    YadongZhang  
       2020-05-04 12:21:48 +08:00 via Android
    @Aloehuang #15 全局 a 注释掉,其他两个函数体都加一个 console.log(a) 就有答案了
    Dyon
        18
    Dyon  
       2020-05-04 12:36:31 +08:00 via Android
    @Aloehuang 哦刚刚误解楼主的问题了,楼主应该是想问动态作用域与静态作用域,不过即使不了解这些,根据运行结果也能推断出来 js 的作用域链是根据定义的位置确定而非调用的位置,是静态的
    huangbangsheng
        19
    huangbangsheng  
       2020-05-04 13:47:21 +08:00 via iPhone
    高性能 JavaScript 开篇解释了你的问题
    zhengjian
        20
    zhengjian  
       2020-05-04 13:58:30 +08:00
    @YadongZhang 全局 a 注释掉就报错了
    YadongZhang
        21
    YadongZhang  
       2020-05-04 14:00:40 +08:00 via Android
    @zhengjian #20 所以说 addThree() 函数里的 a 和其他两个函数体的 a 没有关系
    Junh
        22
    Junh  
       2020-05-04 14:40:17 +08:00 via iPhone
    @ljpCN 建议重新看闭包🐶
    whoami9894
        23
    whoami9894  
       2020-05-04 15:48:48 +08:00
    你对比一下

    let a = 0;
    let addone = () => { let a = 10; addtwo(); }
    let addtwo = () => console.log(a);
    addone()

    /*===*/

    let a = 0;
    let addone = () => { let a = 10; let addtwo = () => console.log(a); addtwo(); }
    addone()
    xiaoming1992
        24
    xiaoming1992  
       2020-05-04 16:13:24 +08:00 via Android
    为什么要研究这样的东西?明明一个 use strict 就能解决的问题
    ZehaiZhang
        25
    ZehaiZhang  
       2020-05-04 17:08:05 +08:00
    面试题吧,考察对 js 作用域的理解=
    leihongtao1230
        26
    leihongtao1230  
       2020-05-04 17:31:20 +08:00 via Android
    作用域和执行栈不是一个概念啊
    iMusic
        27
    iMusic  
       2020-05-04 18:15:17 +08:00
    词法作用域是在写代码时,函数的位置确定的
    autoxbc
        28
    autoxbc  
       2020-05-04 18:46:34 +08:00   ❤️ 1
    @Junh #22 这里确实没有闭包,不要看到闭合结构包着一个变量就说闭包
    ljpCN
        29
    ljpCN  
       2020-05-04 19:47:03 +08:00 via Android
    @Junh 😂好的我回炉重造
    zackwan95
        30
    zackwan95  
       2020-05-04 20:20:06 +08:00 via iPhone   ❤️ 4
    你把代码写成这样在我们组 code review 都过不去,我最不明白国内的一点就是拿这些要么是错误要么是极差的代码当作考题。唯一的答案不应该是永远别写成这样么
    xieranmaya
        31
    xieranmaya  
       2020-05-05 09:51:02 +08:00
    函数里对变量的访问仅仅取决于函数的定义位置,不取决于函数的调用位置(这才是词法作用域的关键)。
    具体来说,你的 addthree 函数不管在哪调用(就算是被引擎调用),他都之访问到外面这个 a 。
    Aloehuang
        32
    Aloehuang  
    OP
       2020-05-05 11:47:49 +08:00
    @whoami9894 谢谢,上午抽空继续学习了下,理解了。我其实是把变量查找的原理(词法作用域)和执行栈、执行上下文混在一起了。
    你上面那段代码在 addtwo 中没有找到 a,于是从父级执行上下文寻找,但这个父级执行上下文有两种理解方式:第一种,执行上下文栈层面上的父级,也就是从哪个函数调用那么该函数就是父级执行上下文;第二种,作用域链层面上的父级(或者词法层面上的父级)。虽然有执行上下文栈,每调用一个函数就生成新的执行上下文并压入栈中,但是作用域链并不是和执行上下文栈一一对应的,作用域链由词法作用域导出,通常作用域链长度小于等于执行上下文栈的长度。
    上面那段代码在 addtwo 中没有找到 a,就从父级执行上下文找,但 addone 和 addtwo 实际上在词法层面上是同级的,所以这个父级执行上下文就是全局执行上下文。自然输出 0 。
    下面的父级执行上下文是 addone 函数执行时创建的执行上下文,所以输出 10 。
    Aloehuang
        33
    Aloehuang  
    OP
       2020-05-05 11:51:52 +08:00
    @xiaoming1992 不是 use strict 的问题
    Aloehuang
        34
    Aloehuang  
    OP
       2020-05-05 11:53:19 +08:00
    @leihongtao1230 对啊,我搞混了,现在清晰了很多。大佬一针见血说出了我迷惑的地方。
    Aloehuang
        35
    Aloehuang  
    OP
       2020-05-05 11:57:12 +08:00
    @zackwan95 真写代码肯定不会这样写啊,只是面对和自己预期不相符的结果时,是不是应该探究一下为什么和预期不符呢。要是真写代码这样写自己都会疯掉,无时无刻不要注意 js 语言的规则。
    Junh
        36
    Junh  
       2020-05-05 13:01:44 +08:00 via iPhone
    @autoxbc 亲,这里建议您看下闭包的定义呢
    lbw
        37
    lbw  
       2020-05-05 14:43:52 +08:00
    词法作用域是 compiling/parsing 期间确定,而不是运行时
    xiaoming1992
        38
    xiaoming1992  
       2020-05-06 01:25:05 +08:00 via Android
    @Aloehuang 好吧,我原本以为"use strict"会管这些,原来不管,那就添加 eslint 规则,no-redeclare 。

    我的意思是,不管结果怎么样,这样的代码根本就不应该出现。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3586 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 11:00 · PVG 19:00 · LAX 04:00 · JFK 07:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.