这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」。\
JavaScript是一门单线程的语言,单线程也就是说,JavaScript在同一时间只能做一件事,有多个任务时,就排队,一个个来。
JavaScript之所以为单线程,是因为 JavaScript 的主要用途是与用户互动,以及操作 DOM。如果JavaScript是多线程的话,一个线程在一个DOM节点中增加内容,另一个线程要删除这个DOM节点,那么这个DOM节点究竟是要增加内容还是删除呢?这会带来很复杂的同步问题,因此,JavaScript是单线程的。
同步操作与异步操作是代码所要依赖的核心机制。同步操作符合单线程的一步一步执行的特点,那js为什么需要异步?往下看
同步与异步
这里所说的同步、异步与我们平时生活中的所说的同步异步是不一样的。
同步就好比,我刷牙的时候不能洗脸,洗脸的时候不能刷牙一样,必须等刷完牙后才能好好洗脸(不要给我说你能 🌚)
异步就好比,吃饭的时候不耽误刷剧一样。吃饭的时间内我可以刷剧,可以烧着开水等会儿喝,充分利用了时间。
同步操作
同步任务,会严格按照它们的出现顺序来执行。这样的执行流程,也让我们更容易的分析执行代码的状态(比如变量的值)等
var num = 22;
function addSum(){
num = 22 + 1
console.log('函数内部', num)
}
console.log(1, num)
addSum()
console.log(2, num)
打印结果与顺序如下:
// 1 22
// 函数内部 23
// 2 23
第一次打印,函数执行第二次打印,第三次打印。
异步操作
异步行为是为了优化因计算量大而 时间长的操作。
为什么需要异步?
比如, 向远程服务器发送请求并等待响应,这时候可能会出现长时间等待,如果只在这干等着数据响应,明显不合理,也会严重影响用户体验。
JavaScript设计之时,自然也考虑到了这一问题,当向远程发送请求任务时,会挂起等待中的任务,先处理后面的,等到请求数据响应后再执行挂起的任务。
这就有了同步任务与异步任务之分。
js 引擎执行异步代码而不用等待,是因有为有 消息队列和事件循环。
- 消息队列:消息队列是一个先进先出的队列,它里面存放着各种消息。
- 事件循环:事件循环是指主线程重复从消息队列中取消息、执行的过程。
实际上,主线程只会做一件事情,就是从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。、
修改一下上一个例子:
var num = 22;
setTimeout(()=> {
console.log(1, num)
},0)
function addSum(){
num = 22 + 1
console.log('函数内部', num)
}
addSum()
console.log(2, num)
输出结果显示?:
// 函数内部 23
// 2 23
// 1 22
代码执行遇到setTimeout时,执行任务被推入到消息队列中,继续执行主线程同步代码,同步代码执行完成后,消息队列任务依次出队执行。
这时就有一个问题,如果接下来的某些操作需要用到异步操作后的最新数据时,如何能在异步调用完成后,立刻拿到数据进行操作?
接下来一起来看下,像这种异步问题都有哪些方式可以解决。
解决异步问题的方案
回调函数
在早期,JavaScript只支持定义回调函数来表明异步函数完成
function addSum(val, callback){
setTimeout(() => callback(val + 1), 1000)
}
addSum(5, (val)=> {console.log(val)})
// 1s后会输出6;
这样会有一个问题, 比如串联多个异步操作,在平时开发中,向后台发起ajax获取数据,接口B需要等待接口A返回数据,接口C需要等待接口B的响应数据,这个会造成什么? 就只能不断嵌套执行了(俗称回调地狱)
// 伪代码
ajaxA({
success(){
ajaxB({
success(){
ajaxB({
success(){
}
})
}
})
}
})
看了上面的代码解构,不由露出开心的表情。
如果说一个异步返值依赖于另一个异步返回值,就又复杂了些
function addSum(val, callback){
setTimeout(() => callback(val + 1), 1000)
}
addSum(5, (val)=> {
addSum(val, (yy) => {console.log(yy)})
})
// 2s后会输出6;
更加难以维护了,回调地狱果然不同凡响。
期约(Promise)
ECMAScript 6 新增了正式的 Promise(期约)引用类型,支持优雅地定义和组织异步逻辑。
下章节见。