• 如何输出1-5题详解


    如何输出1-5

    for (var i = 1; i <= 5; i++) {
      setTimeout(function timer() {
        console.log(i)
      }, i * 1000)
    }
    // 6 6 6 6 6 
    

    分析

    i的作用域链:

    timer scope -> global scope

    i变量会循着以上的作用域链来查找,当然此时是在global作用域链找到了i,但是setTimeout是异步的,会在1s、2s、3s、4s、5s时运行,这个时候都去找全局的i,此时循环早就完成了,i变为6,所以输出5个6,解决问题的关键有两种思路

    解决问题的关键

    为每次循环的函数保存此次循环的变量状态

    1.利用实参

    PS: 参数列表 在ES6时,已经被明确定义为一个作用域

    在timer scope函数体内查找i变量后,在向上查找之前,会查找函数实参列表中的变量,我们将i传入,并在调用函数时使用这个i,每个函数实例能够保存自己的实参状态,就能解决i的共享问题

    for (var i = 1; i <= 5; i++) {
      setTimeout(
        function timer(j) {
          console.log(j)
        },
        i * 1000,
        i
      )
    }
    // 1 2 3 4 5
    

    2.产生作用域来保存状态

    我们知道,前面的问题是状态最终都指向了global上的i,作用域可以保存变量的状态,如果在timer和global作用域之间添加一个新的作用域,并让这个中间作用域持久化(形成闭包),那么也能解决当前的i共享问题

    • 使用IIFE产生闭包
    for (var i = 1; i <= 5; i++) {
      ;(function(j) {
        setTimeout(function timer() {
          console.log(j)
        }, j * 1000)
      })(i)
    }
    // 1 2 3 4 5
    

    分析

    (function(){})()
    

    上述代码称之为IIFE(立即执行表达式),可以在不污染现有作用域情况下创建一个新的作用域(函数内部),在循环过程中,每个IIEF中的setTimeout回调函数都因为引用了j变量而产生了闭包效应,j的即时状态得以保存,问题迎刃而解。

    • 使用bind产生闭包
    for (var i = 1; i <= 5; i++) {
        setTimeout(function timer(i) {
          console.log(i)
        }.bind(null, i), i * 1000)
    }
    
    

    跟上面类似,bind在内部产生了一个闭包,能够在循环中保存当前循环的i的状态,bind的核心实现如下

    Function.prototype.bind = function (context) {
      var fn = this
      return function  ()  {
        return fn.apply(context, args)
      }
    }
    
    • 救世主let
    for (let i = 1; i <= 5; i++) {
      setTimeout(function timer() {
        console.log(i)
      }, i * 1000)
    }
    

    我们使用前面的方法,都需要改动函数本身,而let语法可以说是js帮我们做了产生作用域的操作,而又不需要改动太多,仅仅一个符号改变即可享受,美滋滋

    let的babel转译结果

    var _loop = function _loop(i) {
      setTimeout(function timer() {
        console.log(i);
      }, i * 1000);
    };
    
    for (var i = 1; i <= 5; i++) {
      _loop(i);
    }
    

    babel在这种情况下转译也是利用一个函数产生了一个独立的作用域,且生成了闭包

    小结

    此问题的核心在于利用作用域和闭包保存瞬时状态,利用函数参数列表和产生作用域都是基于此思路,特别是在ES6,函数参数列表被认为是一个实质的作用域,两个方法的实质都是一样的。

  • 相关阅读:
    delphi 数据导出到word
    use vue vuex vue-router, not use webpack
    样式化加载失败的图片
    HTML5 这些你全知道吗?
    移动端touch实现下拉刷新
    参与前端开源项目你应该了解的知识
    JavaScript 深浅拷贝
    精通移动端布局
    JavaScript模块
    两层Fragment嵌套,外层Fragment切换时内层Fragment不显示内容
  • 原文地址:https://www.cnblogs.com/sefaultment/p/11016824.html
Copyright © 2020-2023  润新知