目录
同步和异步的理解
同步
- 指在 主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
- 也就是调用一旦开始,必须这个调用 返回结果(划重点——)才能继续往后执行。程序的执行顺序和任务排列顺序是一致的。
异步
- 异步任务是指不进入主线程,而进入 任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
- 每一个任务有一个或多个 回调函数。前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行。
- 程序的执行顺序和任务的排列顺序是不一致的,异步的。
- 我们常用的setTimeout和setInterval函数,Ajax都是异步操作。
为什么js是单线程
这主要和js的用途有关,js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。 举个例子:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变
<扩展>
- 进程:是cpu分配资源的最小单位;(是能拥有资源和独立运行的最小单位)
- 线程:是cpu调度的最小单位;(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
- 浏览器是多进程的:放在浏览器中,每打开一个tab页面,其实就是新开了一个进程,在这个进程中,还有ui渲染线程,js引擎线程,http请求线程等。 所以,浏览器是一个多进程的。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
Promise和 async await 区别
1、定义:
Promise 是异步编程的一种解决方案,比传统的解决方案:回调函数和事件,更合理和更强大,简单地说,Promise好比容器,里面存放着一些未来才会执行完毕(异步)的事件的结果,而这些结果一旦生成是无法改变的
async await也是异步编程的一种解决方案,他遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。
2、区别:
- Promise的出现解决了传统callback函数导致的“地域回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。而async await代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
- async await与Promise一样,是非阻塞的。
- async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。
defer和async区别
区别主要在于一个执行时间,defer会在文档解析完之后执行,并且多个defer会按照顺序执行,而async则是在js加载好之后就会执行,并且多个async,哪个加载好就执行哪个
<解析>
在没有defer或者async的情况下:会立即执行脚本,所以通常建议把script放在body最后
<script src="script.js"></script>
有async的话,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。但是多个js文件的加载顺序不会按照书写顺序进行
<script async src="script.js"></script>
有derer的话,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成,并且多个defer会按照顺序进行加载。
<script defer src="script.js"></script>
实现异步的方法
回调函数(Callback)、事件监听、发布订阅、Promise/A+、生成器Generators/ yield、async/await
1、回调函数(Callback)
回调函数是异步操作最基本的方法。以下代码就是一个回调函数的例子:
但是回调函数有一个致命的弱点,就是容易写出回调地狱(Callback hell,回调函数中嵌套回调函数的情况就叫做回调地狱,回调地狱就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护,解决回调地狱的方法:1.Promise,2.async/await)。假设多个请求存在依赖性,你可能就会写出如下代码:
回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误,不能直接 return。
2、事件监听
3、发布订阅
4、Promise/A+
5、生成器Generators/ yield
6、async/await
怎么解决callback多层嵌套(回调地狱)
回调地狱有两种解决方案:
- Promises
- Async/await
promise相关知识点
1、语法
executor
函数在Promise
构造函数执行时同步执行,被传递 resolve
和 reject
函数(executor
函数在Promise
构造函数返回新建对象前被调用)。executor
内部通常会执行一些异步操作,一旦完成,可以调用resolve
函数来将promise
状态改成fulfilled
(完成),或者将promise
的状态改为rejected
(失败)。
如果在executor
函数中抛出一个错误,那么该promise
状态为rejected
。executor
函数的返回值被忽略。
例子:
2、原型方法
Promise.resolve(value)
Promise.reject(reason)
Promise.prototype.then(onFulfilled, onRejected)
Promise.prototype.catch(onRejected)
Promise.all(iterable)
Promise.race(iterable)
Promse.race与Promise.all相反
Promise实现文件读取
用Promise实现sleep
宏微队列
JS 中用来存储待执行回调函数的队列包含 2 个不同特定的列队
- 宏列队:用来保存待执行的宏任务(回调),比如:定时器回调、DOM 事件回调、ajax 回调
- 微列队:用来保存待执行的微任务(回调),比如:promise的回调、MutationObserver 的回调
JS 执行时会区别这 2 个队列
- JS 引擎首先必须先执行所有的初始化同步任务代码
- 每次准备取出第一个宏任务执行前, 都要将所有的微任务一个一个取出来执行,也就是优先级比宏任务高,且与微任务所处的代码位置无关