打工人!打工魂!前端才是人上人!此系列总结于大前端进击之路过程中的学习,如果文章中有不对的地方,希望大家能进行批评改正,互相进步。
我们先来看一道经典的面试题,让我们的小脑袋瓜子思考起来~如果你对这道题有清晰的思路并且了解背后的原因,那么请直接点赞评论加关注!!!!!
//请写出输出内容
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
答案!你答对了吗?没对的不要跑,睁大你的小眼睛仔细看以下的内容
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/
为了回答这个问题我们首先需要知道JS的执行环境是单线程的,是因为JS语言最早是运行在浏览器端的语言,目的是为了实现页面上的动态交互。实现动态交互的核心就是DOM操作,因此决定了JS必须是单线程模式工作。我们来假设一下如果JS是多线程一起工作的,其中一个线程修改了一个DOM元素,另外的一个线程同时又要删除这个DOM元素,那么此时浏览器就懵逼了,无法明确以哪个工作线程为准。所以为了避免线程同步的问题,JS就被设计成了单线程的工作模式。
注意,我们这里说的单线程是JS的执行环境是单线程,浏览器中是多线程的。
采用单线程的工作模式可以节省内存,节约上下文切换时间,没有锁的问题。但弊端也很明显,如果中间有一个任务需要花费大量的时间,那么后面的任务就需要等待这个任务完成后才能执行,就会出现假死的情况,对用户很不友好。为了解决这个问题JS给出了两种执行模式:同步模式(Synchronous)和异步模式(Asynchronous)。
同步模式其实很好理解,举个栗子:
我们如果按照同步模式煮面的话,首先先将锅里装上水,打开火开始烧水,等待水烧开,再将面、鸡蛋、火腿肠等材料拿出,材料准备好后放入锅中进行煮,煮好后开始干饭。
在这里其实我们已经能够看出来问题,我们必须等到水烧开后才去准备要煮的材料。回到概念里就是在同步模式下我们的代码是依次执行,后一个任务必须等待前一个任务结束才能开始执行。程序执行的顺序和代码编写的顺序是完全一致的。在单线程模式下,大多数任务都是以同步模式执行。
上个例子中我们在等待水烧开的过程中什么都没干,很浪费时间,我们可以在烧水的过程中将食材都准备好,等到水烧开后直接放入。
我们在烧水的过程中去干了别的事情,就属于异步模式,异步模式中不会等待异步任务的结束才开始执行下一个同步的任务,都是开启过后就立即执行下一个任务。
异步模式对于JS很重要,没有异步模式的话我们就无法同时处理大量的耗时任务,就会给用户带来卡顿和假死的体验。对于我们开发者来说,会给我们打开代码执行的顺序混乱的问题。
宏任务可以理解为每次执行栈执行的代码就是一个宏任务
浏览器为了让JS内部宏任务与DOM操作能够有序的执行,会在一个宏任务执行结束后,下一个宏任务执行开始前,对页面进行重新渲染。
宏任务包括:script整体代码、setTimeout、setInterval、I/O、UI交互事件、MessageChannel等。
微任务可以理解为每个宏任务执行结束后立即执行的任务,发生在宏任务后,渲染之前,执行微任务。
所以微任务的响应速度相比宏任务会更快,因为无需等待UI渲染
微任务包括:Promise.then、MutaionObserver、process.nextTick(Node.js环境下)等。
图片取自掘金,侵即删
回调函数:由调用者定制,交给执行者执行的函数。
我们通过 callback 回调函数、事件发布/订阅、Promise 等来组织代码,本质都是通过回调函数来实现异步代码的存放与执行。
// callback就是回调函数
// 就是把函数作为参数传递,缺点是不利于阅读,执行顺序混乱。
function foo(callback) {
setTimeout(function(){
callback()
}, 3000)
}
foo(function() {
console.log('这是回调函数')
console.log('调用者定义这个函数,执行者执行这个函数')
console.log('其实就是调用者告诉执行者异步任务结束后应该做什么')
})
Promise概念MDN传送门
关于Promise概念性内容就不在赘述了,可直接点击传送门前往MDN查看。简单来说如果我们是用传统的回调函数方式来完成复杂的异步流程,就会无法避免大量的回调函数嵌套,产生回调地狱的问题。为了避免回调地狱让我们开始愉快的Promise的学习时光吧!
// 我们想要执行完第一个再执行第二个再执行第三个
// 虽然我们使用同步的方式将异步的代码学出来了,但是这样的回调是不是让我们的小脑袋瓜子嗡嗡的?
setTimeout(() => {
console.log('执行第一个');
setTimeout(() => {
console.log('执行第二个');
setTimeout(() => {
console.log('执行第三个');
setTimeout(() => {
console.log('执行第四个');
setTimeout(() => {
console.log('执行第五个');
}, 2000);
}, 2000);
}, 2000);
}, 2000);
}, 2000);
回调地狱图示,取自网络,侵即删。
// Promise 基本示例
// promise的英文意思是承诺
// 在JS中Promise是一个对象,接收一个函数作为参数
const promise = new Promise(function (resolve, reject) {
// 这里用于“兑现”承诺
// resolve(100) // 承诺达成
reject(new Error('promise rejected')) // 承诺失败
})
promise.then(function (value) {
// 即便没有异步操作,then 方法中传入的回调仍然会被放入队列,等待下一轮执行
console.log('resolved', value)
}, function (error) {
console.log('rejected', error)
})
我们用Promise来封装一个AJax
function ajax (url) {
return new Promise((resolve, rejects) => {
// 创建一个XMLHttpRequest对象去发送一个请求
const xhr = new XMLHttpRequest()
// 先设置一下xhr对象的请求方式是GET,请求的地址就是参数传递的url
xhr.open('GET', url)
// 设置返回的类型是json,是HTML5的新特性
// 我们在请求之后拿到的是json对象,而不是字符串
xhr.responseType = 'json'
// html5中提供的新事件,请求完成之后(readyState为4)才会执行
xhr.onload = () => {
if(this.status === 200) {
// 请求成功将请求结果返回
resolve(this.response)
} else {
// 请求失败,创建一个错误对象,返回错误文本
rejects(new Error(this.statusText))
}
}
// 开始执行异步请求
xhr.send()
})
}
ajax('/api/user.json').then((res) => {
console.log(res)
}, (error) => {
console.log(error)
})
// 嵌套使用 Promise 是最常见的误区
ajax('/api/urls.json').then(function (urls) {
ajax(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
})
})
})
})
})
ajax('/api/user.json')
.then(function onFulfilled(res) {
console.log('onFulfilled', res)
})
.catch(function onRejected(error) {
console.log('onRejected', error)
})
// 相当于
ajax('/api/user.json')
.then(function onFulfilled(res) {
console.log('onFulfilled', res)
})
.then(undefined, function onRejected(error) {
console.log('onRejected', error)
})
简单来说.catch是给整个promise链条注册的一个失败回调,推荐使用。
拉勾大前端训练营