导读
之前对js的执行机制一知半解,导致使用setTimeout
和setInterval
函数时老是犯一些比较低级的错误。这篇文章就来系统地总结一下,先来看代码
const s = new Date();
const fn = () => {
let i = 0;
while (i < 2000000000) {
i++;
}
console.log(Math.floor(new Date() - s) / 1000, "秒");
}
setInterval(fn, 1000);
思考:上面这些到底会打印啥?
答案:
2.733 “秒”
4.375 “秒”
6.047 “秒”
7.698 “秒”
9.402 “秒”
11.068 “秒”
再看看这个函数的执行时间,大约是1.65s
const fn = () => {
let i = 0;
while (i < 2000000000) {
i++;
}
}
console.time("时间");
fn();
console.timeEnd("时间")
时间: 1656.19091796875 ms
我们发现,只有第一个打印的是
a
1
=
1.65
+
1
a_1 = 1.65 + 1
a1=1.65+1
其他的全是
a
n
=
a
n
−
1
+
1.65
a_n = a_{n-1} + 1.65
an=an−1+1.65,这与我们设置的1s后再执行fn
的的情况不符合
理应是
a
n
=
a
n
−
1
+
1.65
+
1
a_n = a_{n-1} + 1.65 + 1
an=an−1+1.65+1,这是为什么呢?
js的任务类型说起
同步
同步任务很简单,即单线程的执行方式,一条路走到黑。js是基于同步的语言
异步
js的异步任务有很多种,下面简单地分类一下
- 资源请求(如图片加载、请求数据
- 定时器任务,如
setTimeout,setInterval
- 事件。如
onclick, onmousedown
既然js是基于同步的语言,那js是如何实现异步的呢?
js的执行
js会把执行的内容放到以下的两个地方
- 执行栈
- 任务队列(消息队列
就像这样
事件执行的逻辑为
所以执行顺序为
- 先执行执行栈中的任务,如果是异步任务,则交给异步处理函数处理
- 执行栈为空时,则把任务队列中的任务取出。
- 重复1-2的过程,直到执行栈和任务队列都为空时,程序结束
结合开始的代码描述上述过程
const s = new Date();
const fn = () => {
let i = 0;
while (i < 2000000000) {
i++;
}
console.log(Math.floor(new Date() - s) / 1000, "秒");
}
setInterval(fn, 1000);
setInterval的真正含义是,每隔1000ms,就把一个fn函数放进消息队列
过程:
- 主栈中按顺序依次执行,遇到fn函数时,第1000ms把fn放入任务队列
- 把fn取出,执行fn(大约需要执行1.65s,约2.7s fn执行结束打印2.7s,但是在第2000ms时,又把另外一个fn加入了任务队列。但到第2.7s才开始执行,执行完成后打印4.3s
- 依次类推,所以开始是第2.7s是第一个程序执行完成后的打印事件,后面每隔1.65s打印一次
就像这样,其中橙色的线为在队列中的等待时刻,蓝色的线是执行时刻
可以看到,每个fn之间是连续执行的
如果想要每个fn之间相隔了1s才执行,可以这样写
let myInterval = function(fn, delay) {
function inside() {
fn();
setTimeout(inside, delay);
}
setTimeout(inside, delay);
}
myInterval(dosomthing(), 1000);