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

记 js 的一个小坑

  •  
  •   zhy0216 · 2016-09-23 13:28:44 +08:00 · 5848 次点击
    这是一个创建于 3015 天前的主题,其中的信息可能已经有所发展或是发生改变。
    第 1 条附言  ·  2016-09-23 20:46:28 +08:00
    总结两点: 1. var 的声明会拉到函数顶部. 2. closure 闭包内保留的是变量引用

    确实是忘了第二点了...

    不过还是有点没明白为什么用 let 就解决这个问题了呢?
    第 2 条附言  ·  2016-09-24 20:38:53 +08:00
    其实不仅仅是作用域的问题, 关键是: 引用吓 @wizardforcel 的话, 每个函数在调用时都会有一个帧,局部变量当然是存到帧里面。但是有的帧在函数返回时会销毁, lz 这种情况不会。因为函数中的函数会关联外层函数的帧,这个帧还有函数在用就没法销毁
    41 条回复    2016-09-24 20:40:01 +08:00
    swirling
        1
    swirling  
       2016-09-23 13:36:31 +08:00
    var i = 0;
    for(; i<3; i ++){
    funcList.push(function(){
    console.log(i);
    })
    }

    这样不就好理解了吗。。。
    viko16
        2
    viko16  
       2016-09-23 13:40:51 +08:00
    执行函数的时候才去读 i 的值
    learnshare
        3
    learnshare  
       2016-09-23 13:46:12 +08:00
    经典坑 Number 0
    hronro
        4
    hronro  
       2016-09-23 13:47:55 +08:00 via Android
    for 小括号里面第一个语句,定义的变量 i ,是全局变量 i ,没什么不好理解的吧
    lovedebug
        5
    lovedebug  
       2016-09-23 13:51:12 +08:00
    JS 在 ES6 之前 for 循环没有块作用域
    loushizan
        6
    loushizan  
       2016-09-23 13:52:09 +08:00
    var funcs = [];
    for (var i = 0; i < 3; i++) {
    (function(i) {
    funcs.push(function() {
    console.log(i);
    });
    })(i);
    };
    kimown
        7
    kimown  
       2016-09-23 13:54:28 +08:00
    论 ES6 的巨大进步
    Skyxianbo
        8
    Skyxianbo  
       2016-09-23 13:55:40 +08:00
    没什么不好理解的, var 定义的 i 是全局的,等你在第二个循环里面调用的时候才去读 i 的值,这个时候的 i 已经经过第一个循环然后变成 3 了。你把 var 换成 let ,这样定义的 i 只在第一个循环里面起效果,就不会出现这样的问题了。可以去看看 ES6 的块级作用域。
    aristotll
        9
    aristotll  
       2016-09-23 13:56:05 +08:00
    用 let...
    think2011
        10
    think2011  
       2016-09-23 14:05:00 +08:00
    let let let
    wallax
        11
    wallax  
       2016-09-23 14:09:39 +08:00
    不用 let 也能解决 只要控制好作用域就行了

    var funclist = [];

    var func = function(a){
    funclist.push(function(){console.log(a)});
    };

    for(var i = 0; i < 3; i++){
    func(i);
    }

    for(var j = 0; j < 3; j++){
    funclist[j]();
    }
    iyangyuan
        12
    iyangyuan  
       2016-09-23 14:10:15 +08:00
    入门问题
    SuperMild
        13
    SuperMild  
       2016-09-23 14:22:30 +08:00 via iPad
    如非必要就别用旧标准了, let 已经得到广泛支持。
    js0816
        14
    js0816  
       2016-09-23 14:22:54 +08:00
    let 替换 var 就 O 了
    prefere
        15
    prefere  
       2016-09-23 14:51:52 +08:00
    问个小白问题,这个菊部变量 i 存哪了?
    http://imgur.com/KV5NjMA.jpg
    prefere
        16
    prefere  
       2016-09-23 14:52:46 +08:00   ❤️ 1
    更正: 菊>局
    prefere
        17
    prefere  
       2016-09-23 14:54:44 +08:00
    zhouyg
        18
    zhouyg  
       2016-09-23 16:07:18 +08:00
    一点开我就猜是这个问题,😄太经典。
    defia
        19
    defia  
       2016-09-23 17:10:13 +08:00 via iPhone
    闭包内保留的是变量引用
    cheneydog
        20
    cheneydog  
       2016-09-23 17:13:06 +08:00
    典型的闭包问题
    FrankFang128
        21
    FrankFang128  
       2016-09-23 17:22:06 +08:00
    搞不懂 var ,最后去用 let 。
    呵呵。
    wangcansun
        22
    wangcansun  
       2016-09-23 17:22:27 +08:00
    所以 es6 中使用 let 了
    ianva
        23
    ianva  
       2016-09-23 17:30:39 +08:00
    ES6 的实现:

    [1,2,3].map( num=>()=>console.log(num) ).forEach(fn=>fn())

    在大部分的情况下 map, filter, reduce, forEach 代替 for 要简单的多
    ianva
        24
    ianva  
       2016-09-23 17:40:43 +08:00
    再彻底一点:

    [...Array(3).keys()].map( num=>()=>console.log(num) ).forEach(fn=>fn())
    21grams
        25
    21grams  
       2016-09-23 17:50:12 +08:00
    别再用 var 了,全部用 let
    longear
        26
    longear  
       2016-09-23 17:50:27 +08:00
    @prefere 嘘。。。 无需更正,了解你了
    fundon
        27
    fundon  
       2016-09-23 18:27:27 +08:00
    安利下 「 ES6: 快速体验,及实用小贴士」 https://github.com/fundon/ES6-QuickStart-and-Tips
    vwok
        28
    vwok  
       2016-09-23 18:35:08 +08:00   ❤️ 1
    @prefere ![]( http://imgur.com/Ob8Y855) 看的讲闭包的都没讲到原型结构啊,今天终于知道 closure 是怎么来的了
    sudoz
        29
    sudoz  
       2016-09-23 18:36:39 +08:00
    入队的时候没有执行 console.log 啊
    hronro
        30
    hronro  
       2016-09-23 18:40:35 +08:00 via Android
    楼上的各位说用 let 的,现在没人敢直接在生产环境下直接用 let 吧?都是要 babel 编译之后才能用的。如果只是一个小程序的话,上 babel 的成本又太高了
    yhxx
        31
    yhxx  
       2016-09-23 22:56:08 +08:00
    因为 let 提供了块级作用域
    magicdawn
        32
    magicdawn  
       2016-09-24 02:12:29 +08:00
    mingyun
        33
    mingyun  
       2016-09-24 08:26:43 +08:00
    @magicdawn
    @ianva 卧槽,看不懂

    @fundon 先学习了
    YuJianrong
        34
    YuJianrong  
       2016-09-24 08:30:31 +08:00 via iPhone
    @hronro 成本高在哪?编译出来大小增加不大啊。
    如果用到一些增强的内置函数需要挂 polyfill ,但这个可以自己挑需要什么啊,即使全部挂上也没多大。
    Bensendbs
        35
    Bensendbs  
       2016-09-24 10:03:23 +08:00
    用 let...var 有作用域提升的问题..
    winiex
        36
    winiex  
       2016-09-24 10:31:13 +08:00
    let 的作用于是我们常见意义上的块级作用于,而 var 的作用域则仅适用于 function ,一般意义上的 code block 对它没有作用域的限制意义。从编程语言的角度来看, var 声明的变量的作用域部分是不完整的。建议用 let 。

    [“ let ” keyword vs “ var ” keyword in Javascript]( http://stackoverflow.com/questions/762011/let-keyword-vs-var-keyword-in-javascript)
    zongren
        37
    zongren  
       2016-09-24 10:34:23 +08:00
    用闭包也能解决吧
    wizardforcel
        38
    wizardforcel  
       2016-09-24 11:16:58 +08:00
    @prefere 每个函数在调用时都会有一个帧,局部变量当然是存到帧里面。但是有的帧在函数返回时会销毁, lz 这种情况不会。因为函数中的函数会关联外层函数的帧,这个帧还有函数在用就没法销毁。
    poke707
        39
    poke707  
       2016-09-24 16:30:22 +08:00
    这个可以说是 Javascript 的坑也可以说是 feature 。虽然有意这样写的情况不多见吧。
    重要的是要理解编程语言本身,变量的值和引用啊,作用域啊,语法和表达式啊。
    如果不重视基本功, ES6 照样会踩坑。

    比如 [1,2,3].map(n => {value: n}) 有人觉得会返回他以为的东西,有人觉得会报错(比如我,我都忘了有 label 这回事)。
    但结果是不但不报错还会返回 [undefined, undefined, undefined] 呢。
    neone
        40
    neone  
       2016-09-24 18:58:10 +08:00
    JS 的经典问题。
    `var` 声明是没有块级作用域的,所以上面的代码等于:
    ```
    var i;

    for (i = 0; i < 3; i++) {
    funcList.push(function() {
    console.log(i);
    });
    }

    ```
    ES5 中可以使用闭包解决, ES6 中直接使用`let`即可。
    `let`声明的变量具有块级作用域,所以 3 次循环会产生 3 个不相同(仅同名)的变量`i`,添加入数组中的函数引用的是不同的变量。事实上你在循环的外部先用`let`定义变量`i`,那么结果也都是 3 3 3 ,如下:
    ```
    let funcList = [];

    let i;
    for (i = 0; i < 3; i++) {
    funcList.push(() => {
    console.log(i);
    });
    }

    for (let j = 0; j < 3; j++) {
    funcList[j]();
    }

    ```
    itisthecon
        41
    itisthecon  
       2016-09-24 20:40:01 +08:00
    想通了这个基本就能填上变量申明提升的坑了
    > var myvar = 'global value';
    > (function() {console.log(myvar);var myvar='local value';})();
    undefined
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5336 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 03:43 · PVG 11:43 · LAX 19:43 · JFK 22:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.