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

关于 js 闭包的联想,为什么匿名函数内的变量不能直接访问呢 具体看代码

  •  
  •   sodadev · 2020-12-26 20:15:06 +08:00 · 4654 次点击
    这是一个创建于 1429 天前的主题,其中的信息可能已经有所发展或是发生改变。

    var Counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })();

    45 条回复    2020-12-28 18:03:57 +08:00
    cyrbuzz
        1
    cyrbuzz  
       2020-12-26 20:20:10 +08:00
    粘贴在控制台没啥报错。
    sodadev
        2
    sodadev  
    OP
       2020-12-26 20:21:03 +08:00
    很抱歉我不会格式化代码 = = 大家将就看一下吧 这个是 MDN 上的原版示例
    var Counter = (function() {
    var privateCounter = 0;
    function changeBy(val) {
    privateCounter += val;
    }
    return {
    increment: function() { changeBy(1); },
    decrement: function() { changeBy(-1); },
    value: function() { return privateCounter; }
    }
    })();
    des
        3
    des  
       2020-12-26 20:23:10 +08:00 via iPhone
    你这问题不是很怪吗,设计上就是不能直接访问,所以不能直接访问
    sodadev
        4
    sodadev  
    OP
       2020-12-26 20:23:18 +08:00
    @cyrbuzz 是不报错 我在想为什么 console.log(Counter.privateCounter); 返回值会是 undefined
    sodadev
        5
    sodadev  
    OP
       2020-12-26 20:24:10 +08:00
    @des 我就是这个意思,。。。为什么设计成不能访问。。。
    ck65
        6
    ck65  
       2020-12-26 20:35:31 +08:00   ❤️ 3
    先抛开闭包不想。

    Counter 是一个 object,所以你能 .privateCounter,这在语法上不犯规,所以不报错。

    但 Counter 里没有 privateCounter 这个属性,所以读它就是 undefined 。

    再回来看为什么 Counter 里没有 privateCounter 这个属性呢?因为这个对象的值是个匿名函数,仅仅是这个匿名函数里有个变量叫 privateCounter 而已。

    这个变量不属于 Counter,它被匿名函数包在一个封闭的作用域里了。闭包二字说的就是这个作用域。
    sodadev
        7
    sodadev  
    OP
       2020-12-26 20:53:46 +08:00
    @ck65 谢谢指教! 那我再提问一下, 因为 Counter 这个对象只有一个匿名函数,但因为是匿名的函数 ,所以没法用 "
    xx.xx " 这种形式调用是么
    ck65
        8
    ck65  
       2020-12-26 21:01:34 +08:00
    1 、JS 里函数本身也是 object,不管匿名不匿名
    2 、不是没法用 xx.xx ,相反可以,就像你对任意一个 object 去 xx.xx 一样

    感觉你的基础概念有点不熟,可以阅读一下《你不知道的 JS 》几本小册子,特别易懂。
    vision1900
        9
    vision1900  
       2020-12-26 21:03:03 +08:00
    避开闭包不谈,请问你能这么搞吗:
    var privateCounter = 0;
    var Counter = {
    increment: function() { changeBy(1); },
    decrement: function() { changeBy(-1); },
    value: function() { return privateCounter; }
    }

    // 现在你想
    vision1900
        10
    vision1900  
       2020-12-26 21:03:41 +08:00   ❤️ 1
    // 现在你想访问 Counter.privateCounter? Excuse Me?
    shintendo
        11
    shintendo  
       2020-12-26 21:33:46 +08:00   ❤️ 1
    啊这,虽然函数是对象,但从来没说过函数里面的变量是这个对象的属性吧?你为什么会得出这个结论
    sodadev
        12
    sodadev  
    OP
       2020-12-26 21:40:24 +08:00   ❤️ 1
    @ck65 谢谢指教!已经找到资源了!
    sodadev
        13
    sodadev  
    OP
       2020-12-26 21:45:19 +08:00
    @vision1900 #9 按照我目前的理解来说, 应该是不可以的。。
    我的理解是
    privateCounter 和 Counter 是同级的(同一个作用域)
    所以不可以去读取
    请问这样的理解对吗
    sodadev
        14
    sodadev  
    OP
       2020-12-26 21:49:15 +08:00
    @shintendo 请问! 那么这样说的话,函数的里的(指花括号里的)变量是不能被外部访问到的对吗,但是,函数可以使用它上一级作用域的变量,这样是对的吗
    Kasumi20
        15
    Kasumi20  
       2020-12-26 21:53:16 +08:00   ❤️ 1
    找一本书看看吧......................
    akira
        16
    akira  
       2020-12-26 22:08:02 +08:00
    @shintendo 有没那个语言是把函数内变量,作为函数这个对象的属性的呢
    johnkiller
        17
    johnkiller  
       2020-12-26 22:19:47 +08:00 via iPhone
    Counter 只是一个对象而已,只有最后 return 的三个属性。
    ljpCN
        18
    ljpCN  
       2020-12-26 22:50:20 +08:00 via iPhone
    这个函数不是 Counter 的内容,这个函数的返回值才是 Counter 的内容。。函数被立即执行了。。
    oott123
        19
    oott123  
       2020-12-27 01:13:48 +08:00 via Android   ❤️ 1
    你没有写 Counter.privateCounter = xxx,那么你就访问不了 Counter.privateCounter,就这么简单。
    至于函数里面的 var,那和 Counter 无关。
    Zhuzhuchenyan
        20
    Zhuzhuchenyan  
       2020-12-27 03:55:30 +08:00   ❤️ 1
    混淆了太多概念,我来尝试指一条明路吧
    1. 理解 Js 中对象和属性访问器的概念,参考阅读
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Property_Accessors

    此处重点是要理解 JS 中一切皆是对象,并且对象中的属性和方法可以被属性访问器访问到

    2. 理解什么是闭包,参开阅读
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
    此处需要重点理解词法作用域的概念,进而理解闭包的本质就是你持有的的某个可以被调用的函数会保持着自己作用域链(又称词法环境)的引用。

    3. 理解 IIFE,参考阅读
    https://developer.mozilla.org/zh-CN/docs/Glossary/立即执行函数表达式

    4. 理解 var,参考阅读
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/var

    此处重点其实是这句话,“用 var 声明的变量的作用域是它当前的执行上下文,它可以是嵌套的函数,或者对于声明在任何函数外的变量来说是全局。”

    5. 在 1,2,3,4 的基础上就可以推导出,privateCounter 只是一个在匿名函数词法作用域中的一个变量,并且和对象没有任何关系

    6. 你可能还会一知半解,延伸阅读推荐,you don't know js,有中文版,开源地址为
    https://github.com/getify/You-Dont-Know-JS/tree/2nd-ed/scope-closures
    这一章对词法作用域,作用域链,全局作用域,对象生命周期,闭包有详实的讲解

    7. 千万别和 this 问题混淆,那是另一个大坑
    iceheart
        21
    iceheart  
       2020-12-27 08:21:54 +08:00 via Android
    14 楼的理解有点上路了,问题关键点是楼主对作用域概念理解不够。
    多数情况可以理解为花括号里边的变量 /代码不能被外部直接访问。对象算是有点例外。
    作用域基本是可见性的另一种说法。函数作用域、文件作用域。其他语言里有类作用域,对应 js 里的对象作用域。相关内容不多,关键词:作用域,可见性
    uselessVisitor
        22
    uselessVisitor  
       2020-12-27 08:58:56 +08:00 via Android
    不太懂 js 但我感觉你可以把有 privateCounter 这个变量的函数抽象出来,那么这样就可以看出来这个产量是局部的,外部没法访问到?
    kiwier
        23
    kiwier  
       2020-12-27 09:20:12 +08:00
    弄明白 js 作用域你就恍然大悟了,函数声明后如果没有执行是无法获知其内部的匿名函数的内部的,最多知道存在一个或者多个匿名函数,但是肯定不会知道自己内部的匿名函数里边的变量的,其内部的匿名函数在被执行的时候会生成自己的 ao 对象,外部函数才能通过匿名函数获取到内部变量
    xiangyuecn
        24
    xiangyuecn  
       2020-12-27 09:48:02 +08:00
    var Counter = (function() {
    //var privateCounter = 0;
    function changeBy(val) {
    Counter.privateCounter = (Counter.privateCounter||0) +val;
    }
    return {
    increment: function() { changeBy(1); },
    decrement: function() { changeBy(-1); },
    value: function() { return Counter.privateCounter; }
    }
    })();

    变量谁要就给谁,不然函数里面的除了自己能访问谁都访问不了。特例是全局下面的,被划给了 window 对象
    hujun528
        25
    hujun528  
       2020-12-27 10:54:27 +08:00
    @sodadev 新手学闭包却实有点难啃,建议基础学透  http://www.jianxue.mobi/treep/2500/2775
    dartabe
        26
    dartabe  
       2020-12-27 11:37:20 +08:00
    建议把 javascript 的闭包从头开始看 不是什么三言两语说的清楚的
    kiwier
        27
    kiwier  
       2020-12-27 12:08:02 +08:00
    @dartabe 搞懂作用域,很容易理解闭包
    dartabe
        28
    dartabe  
       2020-12-27 12:19:03 +08:00
    @kiwier 我知道是作用域链的问题 但是还是看个来龙去脉比较容易懂
    kiwier
        29
    kiwier  
       2020-12-27 12:57:31 +08:00
    @dartabe 是的,作用域连 原型链 啥的
    across
        30
    across  
       2020-12-27 13:00:51 +08:00
    这个就是基础的重要性了。

    外部变量不能访问 -> 变量作用域 -> 编译器的处理方式( OOP 语言会封装成类对象) -> 底层堆栈访问控制原则。
    但是认真看看 js 的书,前两个也会明确告诉你了。
    px920906
        31
    px920906  
       2020-12-27 13:09:47 +08:00
    反过来想,如果是这样设计的
    ```js
    function sayHello(name) {
    var hello = 'Hello'
    console.log(hello + ' ' + name)
    }
    sayHello.hello = 'Fxck you'
    sayHello('Jack')
    ```
    就完全可以写成
    ```js
    var hello = 'Hello'
    function sayHello(name) {
    console.log(hello + ' ' + name)
    }
    hello = 'Fxck you'
    sayHello('Jack')
    ```
    闭包、局部变量不就没什么意义了么,都用全局变量好了
    FaiChou
        32
    FaiChou  
       2020-12-27 17:04:19 +08:00
    v2lf
        33
    v2lf  
       2020-12-27 17:40:18 +08:00
    var 是声明局部变量的,并添加到最近的作用域对象( js,我记得是都是函数作用域和全局)
    .运算符是访问对象属性的
    v2lf
        34
    v2lf  
       2020-12-27 17:40:52 +08:00
    建议看下 js 高级程序设计。
    v2lf
        35
    v2lf  
       2020-12-27 17:41:25 +08:00
    建议看下 js 高级程序设计。
    @v2lf 看下作用域链,还有原型链
    user8341
        36
    user8341  
       2020-12-27 19:07:28 +08:00
    这个例子很经典的。就是用闭包实现封装,让外界访问不到 counter 的实现细节,只能访问到外部接口。实现细节就是 privateCounter 是匿名函数的内部变量,外部接口是匿名函数的返回值包含的那三个内部函数 inc 、dec 、val 。

    counter 是什么?是匿名函数的返回值,Counter = (function() {...}) (); 注意看,它定义了这个函数对象,然后立刻就调用它,返回值赋给 Counter 。

    涉及到闭包的概念,三个内部函数之所以能访问到 var privateCounter 是因为它属于他们定义时的上下文环境。
    user8341
        37
    user8341  
       2020-12-27 19:08:51 +08:00
    在一些语言比如 C++、Java 要实现封装很容易,只要访问控制声明成 private 就行了。但是 js 就是要绕一点。
    ck65
        38
    ck65  
       2020-12-27 19:33:04 +08:00
    确实如 #18 @ljpCN 所说,Counter 的值是匿名函数返回的对象。我马虎了。但竟然也歪打正着。。总之最终赋给 Counter 的值,也就是 return 的那个对象里面没有 privateCounter 这个 key,访问一个对象里不存在的 key 就得到 undefined 。原因是作用域封闭( closure )这一思路还是有效。
    meepo3927
        39
    meepo3927  
       2020-12-28 09:28:08 +08:00
    因为暴露接口更好,对比直接暴露成员变量,可以将代码修改对外部调用的影响降到最低。

    假如有一天,因为一些原因,你要改成员变量( privateCounter )的名字,如果没有 value 接口的话,外部调用者岂不是都要改。

    有了 value 接口,你就随便改了,甚至可以在 value 接口中写很多逻辑。
    no1xsyzy
        40
    no1xsyzy  
       2020-12-28 09:29:30 +08:00
    那么,为了帮助楼主之类的新手,是否存在可以查看 js 变量作用域的( IDE 插件|独立演示用工具)?
    0x11901
        41
    0x11901  
       2020-12-28 10:21:51 +08:00
    说句题外话,匿名函数和闭包本身就不是同一个概念
    ck65
        42
    ck65  
       2020-12-28 11:12:56 +08:00
    @no1xsyzy 还真搜到一个差不多齐活的 visualizer https://ui.dev/javascript-visualizer/
    @sodadev 右边下方点击「 Complex Closures 」有惊喜 lol
    cczeng
        43
    cczeng  
       2020-12-28 11:26:29 +08:00
    作用域、执行上下文
    theohateonion
        44
    theohateonion  
       2020-12-28 16:13:00 +08:00
    @no1xsyzy 不在词法作用域的变量是个 ide 都会报错了把
    no1xsyzy
        45
    no1xsyzy  
       2020-12-28 18:03:57 +08:00
    @theohateonion 是直接显示范围,而不是得等有错才会报错
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5854 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 06:06 · PVG 14:06 · LAX 22:06 · JFK 01:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.