4 定时器
定时器
function fn1(){
console.log('test1');
}
function fn2(){
console.log('test2');
}
function fn3(){
console.log('test3');
}
function fn4(a){
console.log(a);
}
setTimeout(fn1, 2000); // 无括号 定时执行
setTimeout(fn2(), 2000); // 空参数 立即执行
setTimeout('fn3()', 2000); // 空参数作为字符串 定时执行
setTimeout(fn4('test4'), 2000); // 带参数 立即执行
setTimeout("fn4('test5')", 2000); // 带参数作为字符串 定时执行
/* --------最后执行结果------
(立即)
test2
test4
(2s后)
test1
test3
test5
--------------------------*/
//代码1
console.log('1');
setTimeout(() => {
console.log('2')
},0);
// 1
// 2
//代码2
console.log('1');
setTimeout(() => {
console.log('2')
},3000);
// 1
// (... 3s later)
// 2
事件循环
js是一门单线程语言,任一时刻,同时只能做一件事,但是这并不代表js里不可以进行异步操作。
js引擎(浏览器)将任务分为同步任务和异步任务。同步任务就是按顺序执行,上一个任务不完成,下一个任务就无法进行,同步任务在主线程上排队运行,是线程阻塞的,而异步任务则处于“任务队列”(task queue)中,不会阻塞线程。
js运行机制的简单理解
程序开始执行之后,主程序则开始执行同步任务,一个接着一个,碰到异步任务就把它放到任务队列中,如事件处理程序,然后继续执行下面的代码,等到同步任务全部执行完毕之后,js引擎便去查看任务队列有没有可以执行的异步任务,比如看看有没有事件被触发,网络请求有没有结束,有的话,将异步任务转为同步任务,开始执行,执行完同步任务之后继续查看任务队列,这个过程是一直循环的,因此这个过程就是所谓的事件循环(event loop),其中任务队列也被称为事件队列。通过一个任务队列,单线程的js实现了异步任务的执行,给人感觉js好像是多线程的。
setTimeout工作机制
以前我对setTimeout的理解就是,先把其他代码全部执行完之后,等待一定延长时间,然后执行setTimeout的回调函数。所以当setTimeout延迟时间设置为0就意外着执行到setTimeout的时候,立刻执行其回调函数,然后再执行setTimeout后面的代码。 但setTimeout是异步的,不管你延迟时间是多少,执行到setTimeout的时候,它便会将其回调函数放入任务队列队尾,也就是说即使延迟时间为0,它也会继续执行setTimeout后面的代码,而非立即执行回调函数。
console.log('1');
setTimeout(function(){
console.log('2');
},0);
console.log('3');
// 1 3 2
setTimeout将其回调函数放入任务队列,所以只有同步任务执行完成,才有机会去执行该任务。它的延迟时间从什么时候开始算呢?从所有的同步任务执行完成之后,再处理完队列中处于该任务前面的任务后(队列先进先出)。延迟设置为0表示,同步任务执行完成之后,并且队列中它前面没有要处理的任务了,立刻执行该异步任务,设置为1000表示同步任务结束1秒后,并且队列中它前面没有要处理的任务了,再执行该异步任务。所以此代码到底多长时间之后执行,并不是完全确定的,还取决于同步任务的执行时间和任务队列中前面任务的执行时间。
如果将setTimeout()的第二个参数设为0,就表示当前代码执行完(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函数。
- 宏任务:
script,setTimeout,setInterval、I/O、UI render- 微任务:
Promise、Object.observe、MutationObserver
setTimeout(() => {
console.log(0);
}, 0);
const promise = new Promise((resolve) => {
console.log(1);
resolve();
console.log(2);
})
promise.then(()=> {
console.log(3);
})
console.log(4);
// 1 2 4 3 0
- 遇到
setTimeout,将其回调函数注册后分发到宏任务的Event Queue;- 遇到了
Promise,new Promise构造函数是同步执行的,then函数是异步执行,所以分发到微任务的Event Queue。内部遇到console.log(1)/(2),立即执行。resolve()的位置不会影响输出。console.log(4)是主线程任务,直接输出;- 此时script整体代码作为第一个宏任务执行结束,接下来去微任务Event Queue里面执行微任务。
then函数在微任务里,console.log(3)被触发执行;- 此时第一轮事件循环结束,开始第二轮循环,就要从宏任务Event Queue开始。发现了之前
setTimeout对应的回调函数存在宏任务Event Queue中,console.log(0)立即执行。- 结束。