闭包的那些事--闭包+定时器小例子

// //源码 for(var i=0; i<5; i++){ setTimeout(function() { console.log(new Date,i) }, 1000); } console.log(new Date,i); // // 结果: // // Mon Mar 20 2017 10:30:56 GMT+0800 (中国标准时间) 5 // // Mon Mar 20 2017 10:30:57 GMT+0800 (中国标准时间) 5 // // Mon Mar 20 2017 10:30:57 GMT+0800 (中国标准时间) 5 // // Mon Mar 20 2017 10:30:57 GMT+0800 (中国标准时间) 5 // // Mon Mar 20 2017 10:30:57 GMT+0800 (中国标准时间) 5 // // Mon Mar 20 2017 10:30:57 GMT+0800 (中国标准时间) 5

//变形1-1 for(var i=0; i<5; i++){ (function(index){ setTimeout(function(){ console.log(new Date,index); },1000) })(i) } console.log(new Date,i); // //结果: // // Mon Mar 20 2017 10:30:56 GMT+0800 (中国标准时间) 5 // // Mon Mar 20 2017 10:30:57 GMT+0800 (中国标准时间) 0 // // Mon Mar 20 2017 10:30:57 GMT+0800 (中国标准时间) 1 // // Mon Mar 20 2017 10:30:57 GMT+0800 (中国标准时间) 2 // // Mon Mar 20 2017 10:30:57 GMT+0800 (中国标准时间) 3 // // Mon Mar 20 2017 10:30:57 GMT+0800 (中国标准时间) 4//变形1-2 //定义一个帮助函数,利用 JS 中基本类型(Primitive Type)的参数传递是 //按值传递(Pass by Value)的特征; //这样的做法显然是更符合直觉的 var helper=function(index){ setTimeout(function(){ console.log(new Date,index); },1000) }; for(var i=0; i<5; i++){ helper(i); // 这里传过去的 i 值被复制了 } console.log(new Date,i); for(var i=0; i<5; i++){ var helper=function(index){ setTimeout(function(){ console.log(new Date,index); },1000) }; helper(i); // 这里传过去的 i 值被复制了 } console.log(new Date,i); //变形1-3 //这里只有个非常细微的变动,即使用 ES6 块级作用域(Block Scope)中的 let 替代了 var, //但是代码在实际运行时会报错, //因为最后那个输出使用的 i , //在其所在的作用域中并不存在,i 只存在于循环内部。 for(let i=0; i<5; i++){ setTimeout(function(){ console.log(new Date,i); },1000) } console.log(new Date,i); //最后输出使用的 i 不存在,会报错 //结果: //Uncaught ReferenceError: i is not defined at.... //Mon Mar 20 2017 10:48:09 GMT+0800 (中国标准时间) 0 //Mon Mar 20 2017 10:48:09 GMT+0800 (中国标准时间) 1 //Mon Mar 20 2017 10:48:09 GMT+0800 (中国标准时间) 2 //Mon Mar 20 2017 10:48:09 GMT+0800 (中国标准时间) 3 //Mon Mar 20 2017 10:48:09 GMT+0800 (中国标准时间) 4

//变形2-1 //期望代码的输出变成 0 -> 1 -> 2 -> 3 -> 4 -> 5, //并且要求原有的代码块中的循环和两处 console.log 不变, //该怎么改造代码? for(var i=0; i<5; i++){ (function(index){ setTimeout(function(){ console.log(new Date,index); },1000) })(i) } setTimeout(function(){ console.log(new Date,i); },1000*i)//循环结束后在大概第 5 秒的时候输出 ,只能说大概//变形2-2 //重新思考这个问题, //如果把这次的需求抽象为: //在系列异步操作完成(每次循环都产生了 1 个异步操作)之后, //再做其他的事情,代码该怎么组织? //对,就是 Promise。 const tasks = []; for (var i = 0; i < 5; i++) { ((j) => { tasks.push(new Promise((resolve) => { setTimeout(() => { console.log(new Date, j); resolve(); // 这里一定要 resolve,否则代码不会按预期 work }, 1000 * j); // 定时器的超时时间逐步增加 })); })(i); }Promise.all(tasks).then(() => { setTimeout(() => { console.log(new Date, i); }, 1000); // 注意这里只需要把超时设置为 1 秒 }); //变形2-3 //努力使代码阅读时的颗粒度更小,模块化更好 const tasks = []; // 这里存放异步操作的 Promise const output = (i) => new Promise((resolve) => { setTimeout(() => { console.log(new Date, i); resolve(); }, 1000 * i); }); // 生成全部的异步操作 for (var i = 0; i < 5; i++) { tasks.push(output(i)); } // 异步操作完成之后,输出最后的 i Promise.all(tasks).then(() => { setTimeout(() => { console.log(new Date, i); }, 1000); }); //注意: //使用 Promise 处理异步代码比回调机制让代码可读性更高, //但是使用 Promise 的问题也很明显, //即如果没有处理 Promise 的 reject,会导致错误被丢进黑洞, //好在新版的 Chrome 和 Node 7.x 能对未处理的异常 //给出 Unhandled Rejection Warning, //而排查这些错误还需要一些特别的技巧(浏览器、Node.js)。//变形2-4 //既然 Promise 已经被拿下, //如何使用 ES7 中的 async await 特性来让这段代码变的更简洁? // 模拟其他语言中的 sleep,实际上可以是任何异步操作 const sleep = (timeountMS) => new Promise((resolve) => { setTimeout(resolve, timeountMS); }); (async () => {// 声明即执行的 async 函数表达式 for (var i = 0; i < 5; i++) { await sleep(1000); console.log(new Date, i); } await sleep(1000); console.log(new Date, i); })();

    推荐阅读