Javascript 异步与单线程

js是单线程语言,只能同时做一件事儿。

原因:js作为浏览器脚本语言,主要是实现与用户交互,以及操作DOM。这决定了它只能是单线程,否则会带来复杂的同步问题。比如,假定js同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?没有办法处理,若是单线程就比较简单了,用户先执行那个操作,线程就进行哪一个,不会出现冲突。

但是作为单线程语言,缺点也很明显,如果前面的某部分代码执行时间过长,那么后面的代码就必须一直等待下去,会拖延整个程序。对此js是怎么解决的呢?

  1. 异步和同步

同步即是从上到下按照顺序一步一步的执行程序,是会阻塞程序运行的代码,前面的未执行完,后面的无法执行。

console.log(1)
console.log(2)
console.log(3) 
按从上到下顺序输出1 2 3

异步则是不会阻塞程序运行的代码,异步的代码没有执行完毕,不会阻塞后面的代码执行。

setTimeout(() => {
  console.log(1)
}, 1000)
setTimeout(() => {
  console.log(2)
}, 100)
setTimeout(() => {
  console.log(3)
}, 10)
按照时间顺序输出3 2 1

那么问题来了,Javascript作为一个单线程语言,怎么实现的异步?

  1. javascript运行环境——浏览器

浏览器是JavaScript语言的摇篮,也是它的栖息地之 一。脱离了环境,JavaScript代码是不能够运行的,所以在解答以上问题前,先来了解下浏览器。

进程的浏览器:

浏览器,是一种多进程的架构设计,主要包含以下4种进程

浏览器内核:

即浏览器底层最核心和最基础的那一部分,它主要负责对网页当中的html、css、JavaScript进行解释然后在浏览器当中进行渲染最终呈现给用户,也就是说内核的工作就是渲染,所以常常把浏览器内核称为渲染引擎。由5种线程组成

与JavaScript单线程语言不同,浏览器内核属于多线程的。

回到前面,Javascript作为一个单线程语言,怎么实现的异步?

JavaScript的异步机制主要是由运行环境提供的,如浏览器举例,分析如下代码:

setTimeout(() => {
  console.log(1)
}, 1000)
console.log(2)
setTimeout(() => {
  console.log(3)
}, 10)
// 2 3 1

JS引擎线程按顺序执行代码,当遇到定时器任务,便将其指派给定时器触发线程,然后,继续执行接下来的代码。

总结:JS本身作为单线程语言,自身无法实现异步,需要依赖于它的运行环境,浏览器内核便是其一,浏览器内核含有的多个线程能够帮助JS达到异步的目的。

解释说明:

进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程。

线程:进程中的一个执行任务,负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

  1. 同步任务与异步任务

同步任务:指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。

异步任务:指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。异步任务又细分为宏任务和微任务。

宏任务:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering

微任务: Promise, Object.observe, MutationObserver

执行顺序:同步任务 > 微任务 > 宏任务(面试题常考,不论是笔试还是面试!!!)

console.log(1);
setTimeout(() => {
  console.log(2);
}, 0);
Promise.resolve().then(() => {
  console.log(3);
});
console.log(4);
// 1 4 3 2
  1. 任务队列

js中有两类任务队列:宏任务队列和微任务队列。宏任务队列可以有多个,微任务队列只有一个,宏任务进宏任务队列,微任务进微任务队列。 当任务进入到任务队列后并不会立即执行,而是会等主线程处理完了自己的事情后,再来执行任务队列中的任务。

  1. 浏览器的事件循环

当了解完所有的小知识点后,来串一下这些知识点。

  • 最开始,只有宏任务队列里有一个 script(整体代码),然后推入执行栈。

  • script(整体代码)在执行栈中开始划分同步任务和异步任务。

  • 同步任务开始执行,执行结束后出栈。中途如遇到异步任务,会开始划分微任务与宏任务,并将其推进微任务队列和宏任务队列。

  • 当执行栈中的同步任务全部执行完,执行栈清空了,开始执行微任务,逐一将微任务从微任务队列中加入执行栈中执行。

  • 当执行栈中的微任务被执行完了,执行栈清空了,再逐一将宏任务从宏任务队列中加入执行栈中执行,直至执行栈再一次清空,结束。

这部分是面试中高频出现的内容,大部分会以代码题形式出现,给一段代码,然后说出执行结果的输出先后顺序。下面来看几道题,学习解题规则。

new Promise(function (resolve) {
  console.log(1);
  resolve();
}).then(function () {
  console.log(2)
})
console.log(3)
分析:Promise本身是同步的,但是then() ,catch()方法是异步的
答案:1 3 2

同步

微任务

宏任务

console.log(1)

console.log(2)

console.log(3)

setTimeout(() => console.log(1), 0)
new Promise(res => {
  console.log(2)
  setTimeout(() => console.log(3), 0)
  res()
}).then(() => console.log(4))
console.log(5)
答案:2 5 4 1 3

同步

微任务

宏任务

console.log(2)

console.log(4)

console.log(1)

console.log(5)

console.log(3)

async function fn1() {
  console.log(1)
  await fn2()
  show()
  console.log(2)
}
async function fn2() {
  console.log(3)
}
function show() {
  console.log(4)
}
console.log(5)
fn1()
console.log(6)   
分析:async函数,当函数被调用时,程序会正常立即执行,但是当碰到await关键词时,await下面的语句会作为微任务加入到微任务队列中,await后面跟着的部分也是会立即执行的
5 1 3 6 4 2

同步

微任务

宏任务

console.log(5)

console.log(4)

console.log(1)

console.log(2)

console.log(3)

console.log(6)

let p;
async function f1() {
  console.log(1)
  p = await 3;
  console.log(p)
}
f1();
console.log(2);
console.log(p);  
答案:1 2 undefined 3
分析:await 3会立即执行,后面的进入微任务,包括把3赋值给p

同步

微任务

宏任务

console.log(1)

console.log(3)

console.log(2)

console.log(undefined)

setTimeout(() => {
  Promise.resolve().then(() => {
    console.log(3)
  })
  console.log(2)
})
new Promise((resolve) => {
  console.log(4)
  setTimeout(() => {
    console.log(5)
    resolve();
  }, 2);
}).then(
  console.log(1)
);
分析:当发现宏任务包含微任务,给他拆分,即所谓事件“循环”
答案:4 1 2 3 5

同步

微任务

宏任务

console.log(4)

console.log(1)

Promise.resolve().then(() => {

console.log(3)

})

console.log(2)

同步

微任务

宏任务

console.log(2)

console.log(3)

console.log(5)

//美团一面题目(属于上一道题进阶版)
setTimeout(() => {
  Promise.resolve().then(() => {
    console.log(3)
  })
  console.log(2)
})
new Promise((resolve) => {
  console.log(4)
  setTimeout(() => {
    console.log(5)
    resolve();
  }, 2);
}).then(res => {
  console.log(res)
});
分析 注意行12写在了setTimeout中,那么自动的行15得在resolve()之后执行,由于resolve()未传参,res = undefined
//4 2 3 5 undefined

同步

微任务

宏任务

console.log(4)

Promise.resolve().then(() => {

console.log(3)

})

console.log(2)

同步

微任务

宏任务

console.log(2)

console.log(3)

console.log(5)

console.log(undefined)

setTimeout(() =>
  new Promise(res => {
    console.log(6)
    setTimeout(() => console.log(7), 0)
    res()
  }).then(() => console.log(8))
  , 0)
new Promise(res => {
  console.log(2)
  setTimeout(() => console.log(3), 0)
  res()
}).then(() => console.log(4))
分析:回顾事件循环流程,当执行栈中的同步任务全部执行完,宏、微任务早已经全部进入了任务队列中。
当执行到表格中紫色部分,表示它已经出队列了,但是由于发现它内部还有微任务,会再次让它‘循环一遍’,那么相应的console.log(7)重新进入宏任务队列,排在了console.log(3)之后
答案:2 4 6 8 3 7

同步

微任务

宏任务

console.log(2)

console.log(4)

new Promise(res => {

console.log(6)

setTimeout(() => console.log(7), 0)

res()

}).then(() => console.log(8))

同步

微任务

宏任务

console.log(6)

console.log(8)

console.log(3)

console.log(7)

分析图解(一个粗糙的图解):

最后自测一下学习成果吧,答案在最后

async function fn1() {
  console.log(1)
  await fn2()
  console.log(2)
}
async function fn2() {
  console.log(3)
}
console.log(4)
setTimeout(function () {  
  console.log(5)
}, 0)
fn1()
new Promise(function (resolve) {              
  console.log(6);
  resolve();
}).then(function () {
  console.log(7)
})
console.log(8)

答案:

4 1 3 6 8 2 7 5

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值