JavaScript事件循环机制

JavaScript事件循环机制
1)、JavaScript的一大特点就是单线程 ,这个 线程中拥有唯一的一个事件循环
2)、JavaScript代码的执行过程中,除了依靠函数调用栈来处理函数的执行顺序外,还依靠任务队列来处理另外一些代码的执行。
3)、 一个线程中,事件循环是唯一的,但是任务队列可以拥有多个
4)、setTimeout,setInterval,setImmediate,process.nextTick,Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。 来自不同任务源的任务会进入到不同的任务队列 。其中setTimeout与setInterval是同源的。


实例:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);

约定,用箭头表示其前后的两次输出之间有 1 秒的时间间隔,而逗号表示其前后的两次输出之间的时间间隔可以忽略。
输出结果为:5 -> 5,5,5,5,5,即第 1 个 5 直接输出,1 秒之后,输出 5 个 5
解释:执行到for循环时,遇到setTimeout,就将任务分发到对应的宏任务队列中。每循环一次就将任务放到任务队列中一次,for循环执行结束,执行到最后一句console.log(new Date, i);整个script代码执行结束,接下来执行任务队列,因为var定义的i的作用域在for上一层,所以此时i都是5

但是如果期望代码的输出变成:5 -> 0,1,2,3,4,该怎么改造代码
使用闭包:对循环体稍做改变,让负责输出的那段代码能拿到每次循环的 i 值即可。
for (var i = 0; i < 5; i++) {
(function(j) { // j = i
setTimeout(function() {
console.log(new Date, j);
}, 1000);
})(i);
}
console.log(new Date, i);

利用 JS 中基本类型的参数传递是 按值传递 的特征,改造代码:
var output = function (i) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
};
for (var i = 0; i < 5; i++) {
output(i); // 这里传过去的 i 值被复制了
}
console.log(new Date, i);

因为造成上述的原因是var不是块级作用域,var定义的i存在于上层作用域中,而不是for循环中,意味着在对于它的引用被全部解除之前,它都不会被垃圾收集器所标记并清除,已支保持着循环结束后的值。let定义的是块状作用域,让在循环体内所定义的变量或者常量能留在循环体内。
运用ES6的 块级作用域 ,改造代码:
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);

但是如果期望代码的输出变成:0 -> 1 -> 2 -> 3 -> 4 -> 5,并且要求原有的代码块中的循环和两处 console.log 不变该怎么改造代码
粗暴方案:
for (var i = 0; i < 5; i++) { (
function(j) { setTimeout(function() {
console.log(new Date, j);
}, 1000 * j); // 这里修改 0~4 的定时器时间 })(i);
}
setTimeout(function() { // 这里增加定时器,超时设置为 5 秒
console.log(new Date, i);
}, 1000 * i);

如果把需求抽象为:在系列异步操作完成(每次循环都产生了1个异步操作)之后,再做其他的事情,想到使用Promise。改造代码:
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 )。
接下来,使用 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);
})();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值