进程与线程
首先我们先要来了解一下进程的概念,在js中,什么是进程呢?进程就是CPU在运行指令及加载和保存上下文所需要的时间,而线程是进程中更小的单位,描述的是执行一段指令所需要的时间。而一个进程中可以并发多个线程,每条线程并行,且执行不同的任务。今天我们就具体来聊聊单线程。
单线程
大家都熟知java是一种多线程语言,能够在同一时间执行不同的任务,往往可以做到多种任务共同执行。但是我们的JavaScript却是单线程语言,这表明,在同一个时间段内,JavaScript只会执行一个任务,也就是说,如果在同一个时间有多个任务需要执行的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务,而如果前一个任务执行时间很长,后一个任务就不得不继续等待,于是又有了一个任务队列的概念。
而为什么JavaScript是一门单线程语言?引用一下阮一峰老师的话来说,JavaScript的单线程,与它的用途是有很大关系,我们都知道,JavaScript作为浏览器的脚本语言,主要用来实现与用户的交互,利用JavaScript,我们可以实现对DOM的各种各样的操作,如果JavaScript是多线程的话,一个线程在一个DOM节点中增加内容,另一个线程要删除这个DOM节点,那么这个DOM节点究竟是要增加内容还是删除呢?这会带来很复杂的同步问题,因此,JavaScript是单线程的。
而同样,JavaScript作为一门单线程语言,意味它可以节约内存,并且节省了上下文切换的时间,更快地实现与用户的交互。
在javas为单线程语言的基础之上,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务
同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。就比如我们熟知的元素的渲染页面,其实就是一个同步任务。
异步任务
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。比如js最基础的异步setTimeout,setInterval函数,就是异步,再比如打开页面时,图片的加载,其实也是一个异步任务。
我们来看下面的代码 :
function a() {
console.log('a');
}
function b() {
setTimeout(() => {
console.log('b');
},1000)
}
function c() {
console.log('c');
}
a()
b()
c()
看完上述代码,你们会觉得应该打印的结果为a、b、c。实际上,虽然我们执行函数的顺序为a,b,c,但是我们打印的结果顺序却为a、c、b,这是为什么呢?这是因为由于方法b中的setTimeout为异步函数,延迟一秒钟进行,所以先执行了同步函数,也就是a和c,其次才执行b。那么为了正确的打印出a、b、c这个顺序的结果,我们采用后面的promise方法。
ajax
在此之前我们先聊聊别的,一说到异步,可能很多人会想到ajax,也就是用jQuery封装后的.ajax( )
Ajax 在浏览器与 Web 服务器之间使用异步数据传输(HTTP 请求),这样就可使网页从服务器请求少量的信息,而不是整个页面。
但是在使用回调函数时,由于多个回调函数相互嵌套以及不断的调用,可能会出现“回调地狱”的问题。这个时候我们也会选择promise方式
$.ajax({
url: 'https://autumnfish.cn/search?keywords="断桥残雪"', //像此网址发送请求
method: 'get',
success: (res) => {//回调函数
singsong()
function a ({
function b({
function c()
})
}) //可能出现 回调地狱 这个问题
}
})
getMusic()
function singSong() {
console.log('sing....');
}
代码分析
这里我们的ajax( )就是一个异步函数,我们向一个歌曲网站发出请求,里面存有歌单,我们拿到当中的歌单,然后定义唱歌singsong( )函数。按照正常逻辑顺序来说,应该是先拿到歌单,再进行唱歌活动,但是打印的结果却是先唱歌,后拿到歌单。这是为什么呢?我们都知道,向网络端发送请求以及获得数据都是需要时间加载的,也就是说,虽然代码在前面,但singsong( )函数为同步函数,ajax( )为异步函数,需要时间调用,那么根据JavaScript的单线程语言特性,这个结果也就容易理解了。 但是因为singsong( )函数的执行必须要依赖函数ajax( )的执行结果,于是我们就可以在ajax( )内部使用回调函数 success( ),将之后要执行的函数放置到success里面,这样执行结果就正常了。但是由于可能会调用多种函数,导致回调地狱,我们也会用promise方式。
promise
看到这里,你是不是会感觉promise非常强大,那么我们通过下面这个例子来认识一下promise
function child() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('童年');
resolve('okok') //需要此举 then生效
}, 2000)
})
}
function marry() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('结婚了');
resolve('ok') //需要此举 then生效
}, 1000)
})
}
function baby() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('大儿');
resolve('okokok') //需要此举 then生效
}, 500)
})
}
function baby2() {
console.log('小儿');
}
child().then(marry) //promise才拥有
child()
.then(() => {
return marry()
})
.then(() => {
return baby()
})
.then(() => {
baby2()
})
/*
child()
.then(marry)
.then(baby)
*/
在分析代码前,我们先来好好认识一下这位promise
(1)promise的三个状态
Promise有三种状态,分别是:Pending(待定态), Resolved(完成态),Rejected(拒绝态)。Promise从Pending状态开始,如果任务成功就转到成功态,并执行resolve回调函数;如果失败就转到失败状态并执行reject回调函数,一个promise的状态只可能从“待定”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换。而且在promise中,resolve必不可少,拒绝态则可有可无,这个要根据具体要求来看。但是如果没有成功态,后续将不能调用链式then。
(2)promise对象必须实现then方法,promise当中的核心就是then,也是最为重要的一个方法,而且then方法必须返回一个promise对象,同一个promise对象可以注册多个then方法,并且回调的执行顺序和他们注册的顺序一致。
(3)then方法接收两个回调函数,成功回调以及失败回调,成功回调必须要有。
我们利用promise可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,promise对象提供统一的接口,使得控制异步操作更加容易。
代码思考
我们继续回到代码,上述代码中,定义了四个函数,粗略简短地表示人的一生。分别为child()、marry()、baby()、baby2(), 其中前三个函数都为异步函数,内部定义了setTimeout函数,分别延迟了2s、1s、0.5s执行。如果我们不增加promise那么执行结果就为baby2(),baby(),marry(),child(),这真就相当于你的人生逆着走了,但是加了我们的promise,可以让你的人生顺利地走向巅峰。
我们在child()函数里面定义了promise对象,并在promise对象中传入参数resolve与reject来表示它的成功态与拒绝态。实际上这是它本来就具有的,并且在函数中要定义成功态,也就是resolve,只有定义了这个resolve才能使我们的then生效,而且还要返回我们的promise对象。这样才能用 child().then(marry)的方式,使得marry()的执行在child()之后。后续的代码也是一样的方式,最后实现正确的顺序。
我们再看一个例子:
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('aaa');
resolve('yes')
}, 1000)
})
}
function b() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('bbb');
resolve('yes')
}, 800)
})
}
function c() {
setTimeout(() => {
console.log('ccc');
}, 500)
}
// a().then(b).then(c)
Promise.all([a() , b()]).then(c) //a,b执行完后才能执行c
Promise.race([a() , b()]).then(c) // 谁先执行完,马上执行c
代码分析
这里需要注意的是,promise的其他用法:
Promise.all([a() , b()]).then(c)
,a,b函数全部执行完后才能执行c。
Promise.race([a() , b()]).then(c)
,函数a,b当中任何一个先执行完,就马上执行c。
这些都是可能函数调用出现的顺序,而promise也能实现这样的要求。
总结
promise的使用还是比较简单的,这里我们也只是初步了解,还是需要多学习,多总结。
我是小白,如果本篇文章存在错误,请在下方评论指出,谢谢各位!