setTimeout与循环闭包的示例分析

发布时间:2021-09-17 09:10:45 作者:柒染
来源:亿速云 阅读:134
# setTimeout与循环闭包的示例分析

## 引言

在JavaScript异步编程中,`setTimeout`与循环闭包的组合常会引发令人困惑的现象。本文将通过多个代码示例,深入分析这种组合产生的问题及其解决方案。

## 一、基础概念回顾

### 1.1 setTimeout的工作原理
```javascript
setTimeout(callback, delay)

1.2 闭包的核心特征

function outer() {
  let count = 0;
  return function inner() {
    return ++count;
  }
}

二、经典问题场景重现

2.1 问题代码示例

for (var i = 1; i <= 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, i * 1000);
}
// 输出:6 6 6 6 6(每秒一个)

2.2 现象解析

  1. 同步循环立即执行完毕,i最终值为6
  2. 所有回调函数共享同一个i的引用
  3. 当回调执行时,访问的都是最终的i

三、作用域深度分析

3.1 变量提升与作用域链

// 实际执行等价于
var i;
for (i = 1; i <= 5; i++) {
  // ...
}

3.2 执行上下文变化

时间点 i值 内存状态
t=0ms 6 5个定时器已注册
t=1000ms 6 第一个回调执行

四、解决方案对比

4.1 IIFE(立即执行函数)

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

4.2 let块级作用域

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

4.3 setTimeout第三参数

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

五、进阶场景分析

5.1 多层嵌套循环

for (var i = 1; i <= 3; i++) {
  for (var j = 1; j <= 3; j++) {
    setTimeout(function() {
      console.log(i, j);
    }, (i * 3 + j) * 100);
  }
}
// 输出:4 4(共9次)

解决方案:

for (let i = 1; i <= 3; i++) {
  for (let j = 1; j <= 3; j++) {
    setTimeout(function() {
      console.log(i, j);
    }, (i * 3 + j) * 100);
  }
}

5.2 结合异步操作

const tasks = [];
for (var i = 0; i < 5; i++) {
  tasks.push(() => {
    return new Promise(res => {
      setTimeout(() => res(i), 1000);
    });
  });
}
// 所有Promise都resolve(5)

优化方案:

const tasks = Array(5).fill().map((_, i) => 
  () => new Promise(res => {
    setTimeout(() => res(i + 1), 1000 * (i + 1));
  })
);

六、性能与内存考量

6.1 内存占用对比

方案 每次迭代内存消耗 总内存消耗
var 1个闭包
IIFE n个闭包
let n个块作用域

6.2 执行效率测试

// 测试代码示例
console.time('let');
for (let i = 0; i < 10000; i++) {
  setTimeout(() => {}, 0);
}
console.timeEnd('let');

典型结果: - let方案:~120ms - IIFE方案:~150ms - var方案:~80ms(但结果错误)

七、最佳实践建议

  1. 现代项目:优先使用let+块级作用域
  2. 传统项目:采用IIFE方案
  3. 参数传递:合理利用第三个参数
  4. 代码审查:特别注意循环中的异步操作

八、延伸思考

8.1 事件循环的影响

for (let i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
    while(true) {} // 阻塞测试
  }, 1000);
}

8.2 与Promise的结合

async function process() {
  for (let i = 1; i <= 5; i++) {
    await new Promise(res => {
      setTimeout(() => {
        console.log(i);
        res();
      }, 1000);
    });
  }
}

结论

通过本文分析可见,理解闭包与异步执行的交互机制至关重要。选择适合的解决方案需要权衡: - 代码可读性 - 运行环境要求 - 性能需求 - 团队协作约定

正确运用这些知识,可以避免常见的陷阱,编写出更健壮的异步代码。


附录:完整测试代码

// 所有方案的完整实现对比
const implementations = [
  {
    name: 'Problematic var',
    code: () => {
      for (var i = 1; i <= 5; i++) {
        setTimeout(() => console.log('var:', i), i * 300);
      }
    }
  },
  {
    name: 'IIFE Solution',
    code: () => {
      for (var i = 1; i <= 5; i++) {
        (j => {
          setTimeout(() => console.log('IIFE:', j), j * 300);
        })(i);
      }
    }
  },
  {
    name: 'let Solution',
    code: () => {
      for (let i = 1; i <= 5; i++) {
        setTimeout(() => console.log('let:', i), i * 300);
      }
    }
  }
];

implementations.forEach(impl => {
  console.log(`\nRunning ${impl.name}:`);
  impl.code();
});

”`

推荐阅读:
  1. 循环中的闭包问题
  2. JS中闭包与定时器的示例分析

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

settimeout

上一篇:PostgreSQL中的数组简单介绍

下一篇:.NET中MyMVC框架处理返回值的示例分析

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》