JS是单线程的
JavaScript最大的特点就是他是单线程的,即同一时间只能做一件事。
为什么js被设计成单线程的语言呢?
js作为一门脚本语言,主要的用途是完成用户交互以及DOM操作,而假如js同时拥有多个线程,其中一个线程在修改DOM节点,而另外一个线程也在修改这个DOM节点,那么这时候就发生了冲突。单线程的特点,避免了js的复杂性,也是js的核心特征。
补充 进程与线程
进程:是CPU资源分配的最小单位,即能够拥有资源和独立运行的最小单位
线程:CPU调度的最小单位,线程建立在进程的基础上的一次程序运行单位,一个进程可以有多个线程。
浏览器是多进程的
同步与异步
程序的执行模式分为同步执行和异步执行。
同步执行
即顺序的执行代码, 从上到下,依次执行语句,如下:
let a = 1
console.log(a)
let b = 2
console.log(b)
输出结果为:1 2
异步执行
不会等待任务结束才开始下一个任务,对于耗时操作,在任务开启后立即执行下一个任务,耗时任务的后续逻辑会通过回调函数的方式定义,如下:
console.log(‘start’)
setTimeout(() => {
console.log('hello')
}, 100)
console.log('end')
输出结果为:start end hello
在这里setTimeout开启了一个异步任务,在同步代码执行完毕后,100ms后执行了setTimeout的回调函数。
执行过程
单线程意味着,所有的任务需要排队执行,前一个结束,才会执行下一个,如果任务耗时很长,后一个任务就不得不一直等着。 而这种排队等待并不是因为计算量大,而是因为一些IO设备很慢(如ajax读取数据),不得不等到结果出来,在往下执行,所以js语言设计者意识到,这时的主线程可以挂起等待中的任务,先执行排在后面的任务,等到挂起的任务有结果了,在把它执行下去,因而js中的任务也分成了两类:同步任务和异步任务。
任务运行机制
- 1、所有同步任务都在主线程上执行,形成一个执行栈。
- 2、主线程之外,还存在一个“任务队列”,只要异步任务有了结果,就在“任务队列”里注册一个事件。
- 3、当执行栈中所有的任务都执行完毕(执行栈清空),系统会读取“任务队列”中的事件,对应事件的异步任务,进入结束等待状态,然后进入执行栈,开始执行。
- 4、主线程不断的重复第三步。
这个主线程循环读取事件的运行机制,也被称作事件循环。
浏览器中js代码的执行过程
- js代码执行时,创建内存堆和执行栈
- 顺次将js脚本中的代码压入执行栈,执行后弹出
- 当遇到异步任务时,调用相应的webAPI,并开启对应的线程去执行异步任务
- 当异步任务执行完毕,则在任务队列中注册事件
- 执行栈清空时,读取任务队列中的事件,并将回调函数压入执行栈执行,重复读取执行,直到任务队列中没有等待的任务。
宏任务 微任务
宏任务 参与了事件循环的任务,需要排队的执行的任务,如任务队列中的任务。 创建宏任务的操作:I/O、setTimeout、setInterval、setImmediate(node), requestAnimationFram(浏览器)
微任务 不参与事件循环,能够跟在宏任务后面执行的任务,不需要重新排队。 创建微任务的操作:process.nextTick(node)、mutationObserver(浏览器)、Promise.then catch finally
宏任务在程序执行过程中创建,微任务可以在宏任务中创建也可以在微任务中创建,微任务会被单独注册到微任务队列中,在当前宏任务执行完毕后,会立即执行当前微任务列表中的微任务。
异步解决方案
//当前有一个ajax调用封装方法
function sendRequest(url, callback) {
let xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.onload = callback
}
回调函数
sendRequest('/api/user', (response) => {
//处理response
})
问题:容易引发回到地狱,使得代码难以维护,如下
sendRequest('/api/user', (response1) => {
sendRequest('/api/user', (response2) => {
sendRequest('/api/user', (response3) => {
//处理response
})
})
})
pormise
处理多个异步
let p1 = new Promise((resolve, reject) => {
sendRequest('/api/user', (response) => {
//resolve or reject
})
})
let p2 = new Promise((resolve, reject) => {
sendRequest('/api/user', (response) => {
//resolve or reject
})
})
Promise.all([p1, p2]).then(([res1, res2]) => {
//处理response
})
promise的then catch finally 都是微任务
promise特性
- promise有三种状态,pending,fulfilled,rejected,成功状态和失败状态不可互相转变
- promise.then会返回一个新的promise,then的链式调用中,后面的then方法就是在为上一个then方法返回的promise注册回调,前面的then方法中回调的返回值,会作为后面then方法回调的参数,如果回调中返回了一个promise,则后面的then方法的回调会等待它的结果。
- then的第二个参数,是捕获promise异常的回调,它不能捕获then中第一个回调函数中的错误,catch方法可以捕获promise的异常,也可以捕获then中回调函数的异常。
- 在promise的then和catch方法中,参数期望是函数,当传入的参数不是函数的时候会发生值透传,并将该值传给下一个then或catch方法
generator
function *gen () {
let res = yield sendRequest('/api/user', (response) => response)
//处理res
}
let runGen = gen()
runGen.next()
生成器函数特性
- 函数名前有一个*
- 通过调用函数生成一个控制器,如runGen
- 调用next()方法开始执行函数
- 遇到yield 函数执行暂停
- 再次遇到next()继续执行函数
async/await
ES7中的新特性
async callUser() {
const result = await sendMessage('/api/user', (response) => response)
//处理 result
}
特性
- async定义一个异步函数,返回一个隐式的promise
- await必须在async定义的函数中使用
- await指令会暂停函数的执行,并等待Promise执行,然后继续执行异步函数,返回结果
- async/await是genterator的语法糖
——————————————————————个人杂记—————————————————————