大前端--面向对象和事件循环(EventLoop)

本文详细解析了JavaScript中的面向对象原理,类与对象的关系,构造函数与普通函数的区别,以及new操作的深入剖析。同时介绍了堆栈与作用域链、原型链的概念,以及Function与Object的角色。核心内容涵盖了异步编程中的EventLoop、EventQueue和Promise机制,通过实例和案例探讨了事件循环的工作原理和执行顺序。
摘要由CSDN通过智能技术生成

一:面向对象

类,对象,实例三者中对象最大。
一切皆对象,在众多的对象中,发现有一些对象有相同的属性和特征,此时就可以做抽象,最终产出了类。
实例:就是new之后的产物。

1.普通函数和构造函数
function Foo(m, n) {
 let ret = m + n
  this.m = m
  this.n = n
  // return ret
  return { name: 'liz'}
}
// 01普通函数调用
let ret = Foo(10, 20)
console.log(ret) // 30
// 02构造函数调用
let res= new Foo(10, 20)
console.log(res) // Foo {m: 10, n: 20}
// 03构造函数调用
let res1= new Foo(10, 20)
console.log(res1) // {name: "liz"}
  1. 不论是当做哪种函数去执行,它终归是一个函数,因此堆栈执行及作用域和作用域链这套内容是存在的。
  2. 不同的点在于使用 new 关键字的时候会多做一些事情
  3. 当执行 new 操作的时候浏览器会自动开辟一个内存空间,用于存放实例对象。然后与 this 进行关联
  4. 如果将一个函数当做构造函数那么默认会返回实例对象,如果说函数内部自己设置了返回值,且这个返回值是引用类型,那么采用自己返回的值。
    在这里插入图片描述
  • new 操作到底做了什么?
    创建对象–>关联this–>返回对象

  • 普通函数

    • 正常调用,不需要new关键字
    • 执行过程还是按着堆栈执行 + 作用域链查找机制
  • 构造函数

    • 使用new 关键字
    • 与普通函数类似,同样会创建私有上下文没然后进栈执行
    • 执行new 操作时,浏览器会创建一个空间表示控对象与this进行关联。
    • 函数体内如果没有return 或者说return 的是基本数据类型,默认返回对象实例
    • 函数体内如果返回引用类型,那么就以自己返回为主
    • 函数此时叫做类,返回的结果叫做对象实
  • new 操作符

    • 正常情况下使用new完成对象实例创建,如果当前类不需要传递参数,则可以不加括号运行
    • new Foo,未加小括号说明Foo不需要传,称之为无参列表
    • new Foonew Foo(),的优先级不同,前者为19,后者为20
    • 每一次new 都会将函数重新执行,生成一个新的执行上下文,创建一个新的实例对象,因此两个实例对象不一样。
2原型及原型链
  1. 名词:prototype proto Object
  2. 当前我们只讨论对象(没有顾及函数也是对象这一点)
  3. 实例对象是对象,所以它的身上会有一个 proto 属性指向当前类的原型( foo1身上的 proto 指向 Foo.prototype)
  4. 原型对象也是对象,所以它的身上也有一个 proto 属性指向Object 类的原型对象
  5. ( Foo.prototype.proto ===》Object.prototype )
  • 原型链查找规则
  1. 首先会找自己的私有属性,如果有则直接使用
  2. 私有属性当中如果不存在,则默认会基于 proto 属性找到它所属类的原型对象
  3. 如果原型对象身上也没有,则继续基于原型对象身上的 proto 往上接着找,直到找到 Object.prototype 为止
  4. 如果没有找到就报错
/**
     * prototype 属性
     * - 每一个函数(除箭头)数据类型,都自带一个 prototype 属性,指向原型对象(Function除外)
     * - 每个原型对象都自带一个 constructor 属性,指向当前构造函数本身 
     * - 函数类数据类型
     *  + 普通函数、箭头函数、生成器函数
     *  + 构造函数( 自定义类 )
     *  + 内置函数(内置构造函数)
     * 
     * __proto__属性
     * - 每个对象数据类型,都自带一个 __proto__ 属性(隐式原型)
     * - 该属性会指向当前实例所属类的原型对象(prototype)
     * - 对象数据类型
     *  - 普通对象、数组对象、正则
     *  - prototype 原型对象
     *  - 实例对象
     *  - 函数也是对象
     * 
     * Object 类
     * - 所有对象如果不知道是 new 谁来的,那么它就是 Object 的一个实例
     * - Object 本身也是一个函数,因此同样具有 prototype 属性,指向它自己的原型对象
     * - Object原型对象本身也是一个对象,所以它身上也具有一个 __proto__ 属性,内部设计它指向 Null
     *
     */
	
	 //  下面的Foo是new function来的额
     // Foo就是一个对象  他的身上就会有__proto__
     // Foo本身是一个函数,他的身上就会有prototype属性
    function Foo() {
      this.m = 10
      this.n = 24
      this.getM = function () {
        console.log(this.m)
      }
    }
    Foo.prototype.getM = function () {
      console.log(this.m)
    }

    Foo.prototype.getN = function () {
      console.log(this.n)
    }

    // 加小括号的执行优先级会低于不加小括号
    // 加不加小括号:如果不需要传递参数,可以不加小括号
    let foo1 = new Foo // === new Foo()
    let foo2 = new Foo // === new Foo()
    // 1.当执行new时,会把Foo函数体内的代码执行一遍
    // 2.foo1,foo2当前的两个实例指向的一定是两个内存地址

    /*
    1.每一个函数都有一个prototype
    2.每一个原型对象都有一个constructor属性
    3.原型对象必然是一个对象,那么它一定会有一个__proto__属性,这个属性一定指向它所属类的原型对象
    */

    console.log(foo1.getM === foo2.getM) // false  foo1,foo2当前的两个实例指向的一定是两个内存地址
    console.log(foo1.getN === foo2.getN) // true  foo1,foo2都不存在getN,去原型对象上找
    console.log(foo1.__proto__.getN === Foo.prototype.getN) // true 实例对象是对象,所以它的身上会有一个 __proto__ 属性指向当前类的原型( foo1身上的 __proto__ 指向 Foo.prototype)
    console.log(foo1.__proto__.getM === foo2.getM) // false  foo1.__proto__.getM找的是原型对象上的getM,foo2.getM是自己的getM
    console.log(foo1.getM === Foo.prototype.getM) // false
    console.log(foo1.constructor) // Foo构造。   函数foo1自身没有constructor,去原型对象上找。指向构造函数本身。
    // ƒ Foo() {
    //   this.m = 10
    //   this.n = 24
    //   this.getM = function () {
    //     console.log(this.m)
    //   }
    // }  
    console.log(Foo.prototype.__proto__.constructor) // Object对象   ƒ Object() { [native code] }

    // 牵扯到this
    foo1.getM()  // 10
    foo1.__proto__.getM() // undefined
    foo2.getN() // 24
    Foo.prototype.getN()  // undefined

在这里插入图片描述
在这里插入图片描述

结论
  1. 堆栈作用域及作用域链(一套机制)
  2. 原型及原型链(一套机制)
3.Function 与 Object
  1. 函数
    1. 普通函数调用(研究:堆栈作用域及作用域链)
    2. 构造函数( 研究:原型及原型链 )
    3. 对象( 研究:键值对 ) 【所谓的高阶应用】
    4. 上述的三种角色之间并没有必然的联系,但是函数是一等公民( 函数就是个函数 )
  2. 语录
    1. Function 是一等公民,虽然有很多种身份,但最重要的还是函数
    2. 每个对象都存在 __proto__ 属性,指向所属类的原型对象
    3. 每个函数存在 prototype 属性(有例外),指向它的原型对象【有的函数没有原型对象】
    4. 所有对象如果不知道谁是爹,那么就都安在 Object 身上 ,且 Object 本身也是一个函数
    5. Function 与 Object 是JS当中二大并行的基类,虽然最终查找的落脚点都是 Object
    6. 所有的函数都是 NEW Function 来的
    7. Function.prototype 原型对象是一个匿名函数,但是它的处理机制和其它的原型地象是一样的(指向Object.prototype)
      在这里插入图片描述
      请添加图片描述

正常来说,我们遇到的原型对象, 都是一个对象
但是对于 Function来说它的 prototype 是一个匿名函数
虽然它是一个函数,但是它的处理机制和之前的原型对象是一样的,它身上是没有 prototype 属性的
一个函数如果具备了prototype 属性有什么好处,或者说一个函数没有 prototype 属性,它又失去了什么?
不能执行 new 操作
哪些函数不具备 prototype 属性:

  1. 箭头函数
  2. Function.prototype 这个函数
  3. 对象当中的函数简写
    Object 类是一个函数,它是 new Function 来的
    Function的原型对象又是 new Object.prototype 来的
let obj = {name: 'liz', foo() {}} 
不能new obj.foo()
4.this
  1. this 是什么
    this 就是当前函数的执行主体(谁执行了这个函数),不等同于执行上下文,也不等同于作用域。他只是执行上下文中的一个变量。
  2. 常出现 this 的场景
    1. 事件绑定
    2. 普通函数调用
    3. 构造函数
    4. 箭头函数(不具备this)
    5. 基于 call/bind/apply 强制改变 this 的指向
  3. 规律
    1. 函数执行的时候需要查看函数的前面是否有 . 如果有,则点前面的对象就执行主体,如果没有 . 一般就是window或undefined
    2. 特殊情况
      1. 匿名函数中的 this 是window或者 undefined
      2. 回调函数中的 this 是window或者 undefined
      3. 小括号语法
(
function (){
console.log(this) // window
}
)()

let arr= [1,2,3,4,5]
let obj = {name: '拉钩教育'}
arr.map(function (item ,index) {
	console.log(this) // window
})
arr.map(function (item ,index) {
	console.log(this) // {name: '拉钩教育'}
}, obj)

调用的时候才能确定this指向

let obj = {
fn: function() {console.log(this, 111)}
}

let fn = obj.fn
fn() // 调用的时候才能确定this, window
obj.fn() // obj

// 如果小括号里只有一项,那么和没写小括号是一样的. 
(obj.fn)()  // 等同于: obj.fn()

// 如果有小括号有多项,那么它会返回最后一项.
// 相当于对最后一次做了一次浅拷贝,下面的代码就好像在执行fn() -> window
(10, fn, obj.fn)() // window

var a = 3;
var obj = { a: 5 }
obj.fn = (function () {
  // console.log(this) window,因为是匿名函数和非严格模式。  如果去掉外面的小括号,this就是obj
  this.a *= ++a
  return function (b) {
  // 这里的this是obj
    this.a *= (++a) + b
    console.log(this.a, a)
  }
})()
var fn = obj.fn
obj.fn(6) // this.a = 5 ; a = 13; this.a = this.a * ((++a)* 6) = 95
fn(4) // this.a = 13; a = 13 ; this.a = 13 * (14+4) = 234
console.log(obj.a, a) // 95, 234

/**
 * console.log(obj)
 * {a: 95, fn: ƒ}
 * 
 * 
 * console.log(obj.fn, fn)
 * ƒ (b) {
    this.a *= (++a) + b
  }
  ƒ (b) {
    this.a *= (++a) + b
  }
 */

在这里插入图片描述

  • promise:
    1. 处理多个promise
      1.promise.all等待所有的promise实例都成功,整体状态返回的状态才成功,只要有一个失败,整体失败。
      2. promise.rase看多个实例谁先处理完,先处理完成的状态,不论是成功还是失败,就是最后整体的状态。
    2. promise与微任务
      1. then操作
      1.当前实例的状态如果是明确的,则会创建一个异步微任务
      2.当前实例如果状态是pending,则只是将任务进行保存,并没有创建微任务
      2. resolve | reject执行
      1.此时会创建一个异步微任务,同步结束后基于状态执行then的相关操作。

案例:
可以看到每次的执行步骤
task queue: 宏任务队列
call stack:执行环境栈
micro queue:微任务执行环境栈

https://www.jsv9000.app/

setTimeout(function foo1() {
  console.log('1')
})

Promise.resolve().then(function foo2() {
  console.log('2')
}).then(function foo3() {
  return Promise.resolve('3').then(function foo4(data) {
    setTimeout(function foo5() {
      console.log('4')
    })
    console.log('5')
    return data
  })
}).then(function foo6(ret) {
  console.log(ret)
})

1.foo1会被加入到宏任务.
2.接着执行promise.resolve().then因为promise.resolve()明确知道promise的执行状态是成功,结果是undefined,因此foo2会被立即加入到微任务,形成微任务1。到此,同步代码执行完了一轮。foo3不会被加入到微任务,因为不知道foo2的状态。
3.看微任务队列是否有任务,有一个foo2微任务,拿到执行环境栈中开始执行,输出2,出栈。任务结束。此时foo3的任务状态明确,则foo3加入到微任务队列,同步代码foo6不会被加入到微任务队列,因为foo3的状态不明确。
3.看微任务队列是否有任务,有一个foo3微任务,则开始执行,promise.resolve(‘3’)执行之后状态明确,则.then中的foo4会被加入到微任务队列。接着foo5会被加入到宏任务,形成宏任务2。接着执行console.log('5'),输出5,返回data。同步代码执行完成.




二:事件循环(EventLoop)

2.1EventLoop(事件循环)与EventQueue(事件队列)

Event Loop (事件循环)是一个很重要的概念,指的是计算机系统的一种运行机制。

1.执行顺序
  1. 浏览器加载界面之后会分配一个线程来执行 JS代码,称之叫 JS引擎(主线程)
  2. JS引擎会自下而下执行 JS 代码,此过程会遇到(定时器、网络请求、事件绑定、promise)
  3. 遇到上述的代码之后,浏览器会开启一个 Event Queue(任务|事件)队列 优先级队列结构
  4. 在队列中存在二个任务队列:微任务micro task | 宏任务 macro task
  5. 最终会将遇到的异步任务放到 Event Queue队列当中(未执行)
  6. 主线程会继续向下执行同步代码,直到所有同步代码执行完就会处理异步任务
  7. 进入 Event Queue 当中查找异步任务,找到之后放入主线程里执行(此时主线程又被步用)
  8. 执行完一个异步任务之后,主线程再次空闲,此时再进入 Event Queue 查找余下的异步任务

2. 异步编程

2.2 名词说明

JS 当中是否存在异步编程 。是的
JS 是否能同时处理多件事情(JS是多线程的?)。不能同时处理多件事强,因为是单线程。但是js可以实现异步编程。看着好像可以实现同时处理多件事情。
事件循环是为了解决什么问题或者达到了什么效果 。 为了解决阻塞问题,达到了异步编程的效果。
事件循环中的循环是如何体现的。

js是一种机制,是js内部在代码执行的时候,遇到了异步代码之后,他的一种处理方式/手段。为了解决代码的阻塞问题。

2.1.1 进程与线程
  1. 进程:可以看做是一个应用程序(例如打开浏览器或者浏览器打开一个tab页面,就是来了一个进程)浏览器是多进程的。一个微信就是一个进程。
  2. 线程:是进程当中具体做事情的人,每个线程同一时刻只能做一件事。
  3. 一个进程当中可以包含多个线程。
2.1.2 同步编程和异步编程
  1. 同步编程:一件事一件事的去做,上一件没有完成,下一件不会被处理(单线程)。按代码的顺序去执行。一件事情做完之后才会去另外一件事讲。
  2. 异步编程:上一件事没有处理完,下一件事可以可以继续去处理(多线程)。并行,串行去执行。
  3. js代码在浏览器中执行,而浏览器是多进程的,当我们打开一个界面的时候就相当于开启了一个进程。在这个进程中会存在多个线程(js引擎,GUI渲染,Http网络请求, DOM事件监听器,定时器监听线程…)。其中js引擎就来执行js代码,很明显它是一个单线程,因为它是单线程所以默认情况下只能同步完成执行代码,但是我们发现js里也可以实现异步,这是因为它内部存在事件循环和事件队列机制。
  4. JS是单线程,基于 EventLoop 机制来实现异步编程的。
2.1.3 JS 中的异步操作
  1. promise(then)
  2. async/await(generator)
  3. requestAnimationFrame
  4. 定时器操作
  5. ajax(网络请求)
  6. 事件线绑定
2.1.4 JS 单线程
  1. 浏览器是多线程的,GUI 渲染线程、JS引擎线程、事件触发线程、异步HTTP请求线程
  2. 浏览器平台下的 JS 代码是由 JS引擎执行的,所以它是单线程
  3. JS 中大部分代码都是同步编程,可以基于单线程的 EventLoop 实现异步效果
2.1.5 JS事件循环模型图

模拟事件循环执行顺序

在这里插入图片描述
EventLoop:是一直轮训的状态。

2.1.6 执行顺序:
  • 整体顺序
  1. 同步任务
  2. 微任务
  3. 宏任务
  • 异步任务执行顺序
  1. 先去执行微任务()【如果微任务队列中存在两个微任务,那么先执行哪一个?】先放的先执行(99%都是这样的)。【微任务是否一定先于宏任务执行?不是的。】
  2. 宏任务(如果有多个宏任务,谁先执行)
  3. 总结:不论是微任务还是宏任务,统一定一下,哪个先到执行时机,谁就先执行 (执行时机,从代码中找感觉)
  • 定时器
  1. setTimeout(function foo1(){}, 1000) 。
    问题:
    1. 整体是异步任务,还是部分是异步任务? foo1的回调函数是异步的。
    2. foo1是什么时候放到异步任务队列中的 ,观点1:1s之后才放入到队列,观点2:setTimeout 是同步操作,立即放入队列?以下围绕:【setTimeout 是同步操作,立即放入队列】展开。

  2. 定时器执行之后返回一个数值,表示当前系统内的第几个定时器 。

  3. 将等待时间设置为 0 ,它也不是立即就执行。因为2个方面:1. 定时器做动画是不靠谱的,因为有些时候设置的等待时间已经到了,但是异步操作还没有到执行的时机(微任务)2.每个浏览器都存在一个最小的反应时间( 4~6ms )。

案例1:基本执行顺序


setTimeout(() => {
  console.log(1)
}, 30)

console.log(2)

// 宏任务1,20ms之后执行。
setTimeout(() => {
  console.log(3)
}, 20)
console.log(4)

// console.time('AA')
// 下面的循环执行完成需要消耗95ms
for (let i = 0; i < 88888888; i++) { }
// console.timeEnd('AA')
// 这个循环执行之后,宏任务1肯定到时间1。

console.log(5)

// 宏任务2,18ms之后执行。
setTimeout(() => {
  console.log(6)
}, 18)

console.log(7)

// 宏任务3,25ms之后执行。
setTimeout(() => {
  console.log(8)
}, 25)

console.log(9)// 1.先执行同步任务,9输出之后说明同步任务执行完了。
// 2.同步任务执行之后,去任务队列,此时没有微任务,所以直接去找宏任务。
// 3.对于宏任务队列来说,谁先到的,谁先执行。但对于当前的代码来说,一定是宏任务1 先到,因为中间有一个95ms的耗时操作(宏任务1,最先放入到任务队列中,放入队列中之后,执行下面的循环花费了95ms,)
// 4.将宏任务1当中的代码拉到主线程中去执行(不考虑执行环境栈),此时主线程是被占用的,没法做其他的事情。
// 5.等到宏任务1执行完之后,会再去找事件队列仍然没有微任务,接着看宏任务,这次发现宏任务2到了
// 。。。。。。


// 执行结果
// 2
// 4
// 5
// 7
// 9
// 3  3比1先到执行时机,所以3先输出。1肯定是先放进去的,但是3是20ms,3是30ms,3比1先到执行时机,因此3先输出
// 1
// 6
// 8

在这里插入图片描述

案例 2: 主线程被占用:

// 宏任务1:
setTimeout(() => {
  console.log(1)
}, 0)

console.log(2)

// while (true) { } // 这里有一个死循环,意味着主线程一直被占用( JS单线程,分不出来身去做其它的事情了 )
// throw new Error('手动抛出异常') // 代码执行时如果有异常,它不会影响异常出现之前所存放的异步操作,但是异常之后不能执行了
// console.log(a) // 代码执行时如果有异常,它不会影响异常出现之前所存放的异步操作,但是异常之后不能执行了

console.log(3)

// 宏任务2:
setTimeout(() => {
  console.log(4)
}, 10)

console.log(5)

/*
1.执行结果 2, 3, 5, 1, 4
2.开始while (true) { }
      执行结果:2 。因为:这里有一个死循环,意味着主线程一直被占用( JS单线程,分不出来身去做其它的事情了 )
3.关闭:while (true) { },开启: throw new Error('手动抛出异常')。
      执行结果: 2 , 报错:‘手动抛出异常’, 1。
      因为:代码执行时如果有异常,它不会影响异常出现之前所存放的异步操作,但是异常之后不能执行了。
4. 关闭:while (true) { }, throw new Error('手动抛出异常')。开启:console.log(a)
      执行结果:2, a is not defined, 1 .   可以发现手动抛出异常,和代码异常执行结果一样,只是报错信息不一样。
*/
案例3: promise语法分析:

在这里插入图片描述


// 1.只有一行代码:执行结果:报错。
const p1 = new Promise() 

// 2.下面的执行结果:报错
const p1 = new Promise({ obj: '123' })

// 3. 执行下面的p1:会有结果输出么?有:输出结果:111。
// executor 里虽然能写同步的代码但是,我们一般用它来管理异步代码。
const p1 = new Promise((resolve, reject) => {
  console.log('111')
})

// 4.下面的执行结果:ok。 下面的ok会不会输出?会输出的, 1ms之后会输出的。
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('ok')
  }, 1000)
})


// 4.promise 的状态一旦切换了,就不会再改变。
// 下面的代码执行结果:成功了:,ok。 
const p1 = new Promise((resolve, reject) => {
  resolve('ok')
  reject('no')
}) 
console.log(p1)
p1.then((ret) => {
  console.log('成功了:', ret)
}, (reason) => {
  console.log('失败了', reason)
})
/*
promise:
1:executor 函数
  1. 我们在执行 new Promise 操作的时候必须传入一个参数,且这个参数只能是函数, 这个函数我们称之为 executor 函数。executor是同步行为,立即执行。
  2. executor 函数能接收二个函数做为参数, 且 executor 函数是立即执行了。
  3. executor 里虽然能写同步的代码但是,我们一般用它来管理异步代码。

2. Promise实例
   1. new 操作执行之后肯定会返回一个值,这个值就是 Promise 实例 
   2. [[PromiseState]]: Promise 状态, pending(默认状态)   fulfilled(成功)  rejected(失败)
   3. [[PromiseResult]]: Promise 值, undefined             成功的值          失败的原因 
   4. __proto__ 属性可以找到原型对象,身上有 then catch finally 

3. 状态切换
   1. 执行 resolve 的时候将实例状态改变为成功态,此时值就是当前 resolve 调用时接收的值。
   2. 执行 reject 的时候将实例状态改变失败态,此时的值就是..... 
   3. 如果executor 函数执行报错,状态非常明确:是失败态。则状态也会切换至失败态,此时promise 的值就是当前报错的原因。
   4. promise 的状态一旦切换了,就不会再改变( )   
   
   5. 关注promise的状态:决定了:要不要立即把这个微任务放到任务队列当中。 
   6. 关注promise的值:会帮助你分析代码的最终输出结果是什么。

4. 状态和值 
    获取:获取实例对象 的2种方法:
   1. new Promise 获取实例对象。
      1. 调用 resolve reject
      2. executor 函数执行中是否存在报错
   2. 调用 then 方法返回实例对象。
      1. then 注入的两个主法不论哪个执行,只要执行不报错, 则新实例的状态就是 fulfilled , 反之就是 rejected ,值就是返回值
      2. 手动返回一个 promise ,此时新 promise 的值和状态取决当前返回的 promise 
*/



案例分析4:执行顺序总结:

let p1 = new Promise((resolve, reject) => {
  console.log(1)
  // resolve('ok') 
  // console.log('2')

  // 下面的setTimeout函数里面的代码:按照同步的逻辑执行
  setTimeout(() => {
    resolve('ok')
    console.log('2')
  }, 1000)
})

// then何时加入到微任务队列中,取决于p1的状态,只有p1的状态明确(成功 / 失败),才会把then放入到微任务队列中。
// 当setTimeout中的resolve('ok')执行完之后,才会知道p1的状态,是成功的。因此此时,p1才会加入微任务队列中。
// 此时setTimeout函数中的代码在主线程中执行着,因此输出2。输出之后,主线程的代码执行完毕。
// 然后去微任务队列中,看是否有任务,有一个then任务,然后拉到主线程当中执行,输出了 success
p1.then((ret) => {
  console.log('success--->', ret)
}, (reason) => {
  console.log('failed--->', reason)
})
console.log(3)

// 执行结果:1 -> 3 -> 2 -> success:ok

/**
 * 分析:
 * 01 executor 函数是立即执行的,所以 1 输出了。
 * 02 在 executor 函数当中存在 setTimeout ,这时就会在事件列队中会加一个宏任务1,此任务在 1000ms 后执行
 * 03 p1.then 开始执行(注册了二个任务)pending:不是一个明确的状态。
 *  此时p1的状态是不明确的,那么 then 后面的微任务还没有添加到的队列当中)
 *  如果你此处认为将微任务添加到队列当中了,那么请记着,这个微任务的执行时机一定是等到 p1 状态明确之后才会执行
 *  p1的状态明确就需要等到 setTimeout 执行完,请思考 setTimeout 是一个什么任务,then 又是一个什么任务。setTimeout是宏任务,then是微任务。先执行setTimeout,再执行then。
 *  所以我们发现不是说 所有的微任务都一定先于宏任务执行。
 * 04 输入了 3 ,当这行代码执行完成之后,就相当于所有的同步代码执行完了。
 * 05 找异步任务,首先换微任务, 没有的时候就找宏任务, 所以此时会调用 resolve('ok')
 * 06 当我们去调用 Ok 的时候就会明确 p1 的状态,那么就会将 then 的回调添加到微任务队列当中,但是 setTimeout 里的代码
 * 现在是在主线程当中运行着,因此 2 肯定先输出,当2 输出之后就意味着这次的同步执行完了,那么再继续去队列当中查找
 * 07 这次查找就看到了微任务,然后拉到主线程当中执行,输出了 success
 * 1 3 2 success
 *
 * 微任务先执行,然后再找宏任务( 第一轮,第二轮.......在一次循环当中,微先于宏 )
 *
 *
 */

案例:

const p1 = new Promise((resolve, reject) => {
  resolve('ok')
})
let p2 = p1.then((result )=>{
  console.log('success:', result)
  setTimeout(()=>{
    console.log('okk')
  }, 1000)
}, reason => {
  console.log('faild:', reason)
  return 20
})

p2.then((result)=>{
  console.log('success:', result)
}, reason => {
  console.log('faild:', reason)
})

// 执行结果: 
// success: ok
// success: undefined
// promise实例
// okk
  • 分析async的特点和 await特点:
// 1.下面的函数输出结果:返回promise实例,状态是:成功;值:100。
// 因为:async 用于修饰函数,默认让返回返回一个成功的 promise 实例
async function foo() {
  return 100
}
console.log(foo())


// 2.立即执行foo函数,如果foo的返回结果是promise实例,则直接返回,如果不是promise实例,处理成promise实例再返回。
// 返回之后,不管后面的代码有多少,把后面的所有代码整体看成是一个微任务。
await foo()
console.log(111)

/**
 * 1. async 特点
      1. async 用于修饰函数,默认让返回返回一个成功的 promise 实例
      2. 如果函数执行报错,则promise 状态为 rejected ,值就是报错原因
      3.如果函数执行正常则实例对象的状态为 fulfilled, 值为函数的返回值,如果没有return 则 undefined 

   2. await 语法 (基于async)
     1. await 后面放置一般都是 promise 实例,如果不是,它会给你处理成 Promise.resolve() 
     2. await foo(), 此语法就相当于是立即执行 foo函数, 接收到 foo 函数的返回值之后处理为 Promise
     3. 假如说 await foo() 后面还有代码,它就会将后面的代码整体看做是一个异步的微任务,等到它前面的 promise 状态明确之后再按规则执行。
 */

案例:


// 案例:
function foo(){
  console.log(1)
  return new Promise((resolve, reject)=>{
    // 宏任务1,1000ms之后执行
    setTimeout(()=>{
      resolve(2)
    }, 1000)
  })
}

// 第一步1.同步任务,输出1
console.log(3)

async function fn() {
  console.log(4)
  let result = await foo() 
  
  //微任务1: await 后面的不管多少行代码被整体看成微任务:他的代码执行时机:一定是等到上面的await 后面的函数状态明确之后才会执行。await后面的foo返回promise实例。
  console.log(result)
  console.log(5)
}

// 第2步: 调用fn函数
fn()
// 第3步:同步输出6。输出6之后,同步代码执行完成。
console.log(6)

/*
分析:
  1.console.log(3) 同步任务,输出1.
  2.调用fn函数,输出4, 输出之后遇到await,立即执行,则输出1,接着执行foo函数中的return new Promise,添加了一个宏任务,1000ms之后执行,执行之后返回的状态不明确。
  console.log(result) console.log(5)整体被看成是一个:微任务,这个微任务的执行时机,一定是等到上面的promise状态明确之后再执行。
  3.继续执行同步任务,输出6。
  4.输出6之后,发现同步任务执行完成。(同步任务执行完之后,开始找微任务,但是发现微任务的执行时机取决于上面的promise状态明确,因此宏任务1开始执行。)
  5.然后开始到任务队列中找宏任务1,宏任务1调用之后就明确了状态(resolve成功状态),且将2向后传递。
  6.开始执行微任务1,console.log(result) console.log(5)。认别输出:  2, 5
*/
// 执行结果:
// 3
// 4 
// 1
// 6
// 2
// 5
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值