JavaScript事件循环机制

来源:互联网 发布:淘宝小北名表靠谱吗 编辑:程序博客网 时间:2024/06/05 15:15
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);
})();

0 0
原创粉丝点击