javascript之同步异步简述
前言
最近准备把nodejs啃一啃,毕竟现在CTF的nodejs
题还挺多的,并且nodejs
也是必备知识,所以想学这nodejs
搞个express
或者koa
玩玩,在学习过程中感觉与其他语言区别较大的也就是js
中的同步和异步处理,并且感觉在js
中,回调函数还是十分普遍的,因此要了解同步和异步的模式
正言
单线程
(1)单线程的概念
如果大家熟悉java,应该都知道,java是一门多线程语言,我们常常可以利用java的多线程处理各种各样的事,比如说文件上传,下载等,而JavaScript是否也可以支持多线程呢?
答案是否定的,JavaScript
是一门单线程的语言,因此,JavaScript
在同一个时间只能做一件事,单线程意味着,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务,比如说下面这段代码
function fun1(){
console.log(1);
}
function fun2(){
console.log(2);
}
function fun3(){
console.log(3);
}
fun1();
fun2();
fun3();
//1
//2
//3
会依次从上往下执行,但是如果fun1()
中的代码执行的是读取文件或者ajax操作,文件的读取和数据的获取都需要一定时间,难道我们需要完全等到fun1()
执行完才能继续执行fun2()
?因此才有了异步的概念
(2)同步任务和异步任务
同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务
异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务
function fun1(){
console.log(1);
}
function fun2(){
console.log(2);
}
function fun3(){
console.log(3);
}
fun1();
setTimeout(()=>{
fun2();
},0);
fun3();
//1
//3
//2
在输出中我们可以看到,由于setTimeout()
是典型的异步处理,这里我们待会再讲,因此主线程中显然是会先完成同步任务,即执行完成fun1()和fun2()
后,等到异步任务通知线程后在任务队列中调到了了主线程中,主线程才会最终调用
异步机制
上文提到了任务队列
,因此这里先说明一下什么是任务队列?
在前面也介绍了,异步任务是不会进入主线程,而是会先进入任务队列,任务队列其实是一个先进先出的数据结构,也是一个事件队列,比如说文件读取操作,因为这是一个异步任务,因此该任务会被添加到任务队列中,等到IO完成后,就会在任务队列中添加一个事件
,表示异步任务完成。当主线程处理完同步任务后,从消息队列取出一个事件消息,其对应异步任务(函数)入栈,执行回调函数,如果未绑定回调,这个消息会被丢弃,执行完任务后退栈
该机制实质上也就是事件循环的机制:
- 1.宿主环境为JavaScript 创建线程时,会创建堆 (heap) 和栈 (stack) ,堆内存储 JavaScript 对象,栈内存储执行上下文;
- 2.栈内执行上下文的同步任务按序执行,执行完即退栈,而当异步任务执行时,该异步任务进入等待状态(不入栈),同时通知线程:当触发该事件时(或该异步操作响应返回时),需向消息队列插入一个事件消息;
- 3.当栈内同步任务执行完毕后,线程从消息队列取出一个事件消息,其对应异步任务(函数)入栈,执行回调函数,如果未绑定回调,这个消息会被丢弃,执行完任务后退栈;
- 4.当线程空闲(即执行栈清空)时继续拉取消息队列下一轮消息
这也就是异步处理的大致原理,因为前段实在是用到很多异步,因此回调函数确实出现频率很大,下面提及异步编程的实现:
- 回调函数
这也是为什么回调函数如此频繁的原因。举个具体的例子来说明回调函数的异步体现:
function getData(){
setTimeout(()=>{
var name = 'crispr';
return name;
},1000);
}
console.log(getData());
//undefined
本来是想得到返回的字符串,但是却返回了undefined
,原因就是因为setTimeout()
是异步的,当js
代码执行到这里的时候,不会等待异步请求完毕,就直接return
了,所以值就一直是undefined
,因此如果想实现我们的目的有两种方法,一种是在加入回调函数进行返回
function getData(callback){
setTimeout(()=>{
var name = 'zhangsan';
callback(name);
},1000);
}
getData((name)=>{
console.log(name);
这样callback
就相当于一个console.log
,最终能够输出crsipr
,还有一种方法也是异步编程实现的重要依据
- Promise对象
当我们使用Promise
来达到我们之前的目的时,应该这样使用
function getData(resolve,reject){
setTimeout(()=>{
var name = 'crispr';
resolve(name);
},1000)
}
var p = new Promise(getData);
p.then((name)=>{
console.log('your name is ' + name);
})
//crispr