转载来自:https://hxzy.me/2020/07/30/js-Synchronous-Asynchronous/

​ 在CSDN上看到一篇文章: JavaScript同步、异步、回调执行顺序分析 ,本文后面部分正是基于这篇文章所总结的。

​ 文章中作者提到一个口令的:

同步优先、异步靠边、回调垫底

​ 对于以下代码:

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
console.log(i);

​ 而实际上,输出结果为:5 5 5 5 5 5​ 没有接触js之前,我一直都以为输出的结果会是:0 1 2 3 4 5

​ 正如上面那篇文章中的作者所描述的一样,f or循环和最后的那个console是同步执行的 ,所以for循环执行完毕之后在执行最后的console语句。

​ setTimeout的回调函数因为是垫底,所以会是最后执行。而事实上也是如此,执行最后那个console语句的时候,回调函数一次都没有执行,如下图所示:

​ 上图中可以看到,首先会输出一个5,过了一会后,几乎是同时输出了另外的5个5。

JavaScript 同步的代码是在堆栈中顺序执行的 而setTimeout回调会先放到消息队列 for循环每执行一次,就会放一个setTimeout到消息队列排队等候,当同步的代码执行完 了,再去调用消息队列的回调方法。

​ 在这个经典例子中,也就是说, 先执行for循环,按顺序放了5个setTimeout回调到消息队列,然后for循环结束,下面还有一个同步的console,执行完console之后,堆栈中已经没有同步的代码了,就去消息队列找,发现找到了5个setTimeout。

//同步的代码执行完成了...没有了,就去消息队列里面找

​ 作者提到了两种方式达到输出0 1 2 3 4 5的效果:

​ 一是使用let:

使用 let 语句声明一个变量 ,该变量的范围限于声明它的块中。 可以在声明变量时为变量赋值,也可以稍后在脚本中给变量赋值

let是ES6语法,ES5中的变量作用域是函数,而 let语法的作用域是当前块,在这里就是for循环体。在这里,let本质上就是形成了一个闭包

for(let i = 0; i < 5; ++i) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
console.log(i)
var loop = function (_i) {
    setTimeout(function() {
        console.log('2:', _i);
    }, 1000);
for (var _i = 0; _i < 5; _i++) {
    loop(_i);


img2
使用let与下面这种是一样的意思:

​ 报错的原因是最后那个console所输出的i在整段代码中未定义,let声明的变量旨在所在代码块{}有效。

二是使用闭包的方式:

阮一峰的讲解

闭包的概念:

闭包就是能够读取其他函数内部变量的函数。

​ 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。

​ 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

​ 闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

​ 关于闭包:学习Javascript闭包(Closure)

​ 然后是:

​ 在控制台中首先输出了 script start。

​ 接着代码继续向下执行,遇到setTimeout,因为回调函数垫底执行,其回调函数被放入任务队列中,等待同步执行完毕后在进行执行。

​ 执行async1函数:

async1()

​ 由于async1函数有async标记,当调用async函数的时候会返回一个Promise对象,Promise对象是立即执行的,然后输出async1 start。

​ 而后到了await async2(),在async里遇到await它会使async函数暂停执行执行完async里的await内容后将后续的内容扔入到浏览器的任务队列里面去。所以控制台会先输出async2,然后再回到async1中,将async1没有执行的部分扔到了任务队列里面去。(现在任务队列里面有一个setTimeout和一个async1的后续内容)。

我有点不太理解这个, 但是好像是 async遇到await后....会把后面的东西扔到消息队列里面

async函数定义了一个返回AsyncFunction的异步函数,它会隐式的返回一个Promise作为其返回结果。它的代码书语法和结构更像是同步函数。 async函数体内可以包含多个await表达式(await关键字只能配合async使用),await指令会暂停异步函数的执行,等待Promise的返回结果,并返回结果。 async
function foo() { await 1 console.log('async') 上面的函数实际上等价于: function foo() { return Promise.resolve(1).then(() => console.log('async'))
在await表达式之后的代码可以被认为是存在在链式调用的then回调方法中
 基于上面的结论。因此,当执行await async2时,会将其后面的函数放入Promise.then的回调中(任务队列中),所以会继续执行下方的new Promise, 其then方法的回调也会进入任务队列中。
在await表达式之后的代码可以被认为是存在在链式调用的then回调方法中

​ Promise是立即执行的,输出promise1,然后执行resolve:

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。

​ resolve执行完毕后,马上应该执行.then中的内容,但其内容是个回调函数,所以被放入任务队列中。

​ 同步执行的最后执行console.log(‘script end’),输出script end之后,开始执行异步的任务队列中内容。

setTimeout会在最后执行,就好比css权重的优先级,setTimeout的优先级没有async和promise级别高其实async和promise是一样的,因为调用async方法时就是返回一个promise对象

​ 队列是先进先出的顺序,所以接下来的输出是:

async1 end
promise2
setTimeout
参考文章:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

https://blog.csdn.net/weixin_43606158/article/details/91360230

https://blog.csdn.net/u010297791/article/details/71158212