«

笔记-8-JavaScript高级补充(异步编程/ES6+)

codeez 发布于 阅读:3268 笔记


早期处理异步的主要方式就是使用回调函数,那个时候相对复杂的异步处理常常会出现回调地狱。
为了解决回调地狱JS提供了Promise和async/await

Promise
有三个状态,而且只会改变一次。
待定(pending)初始状态,已完成(fulfilled),已拒绝(rejected)

new Promise((resolve, reject)=>{
    // executor
    resolve() // 当调用resolve函数时,会执行then方法传入的回调函数
    // 当调用reject函数时,会执行catch方法传入的回调函数
}).then((res)={}).catch((err)=>{})

resolve()传入不同值的区别:
一、传入普通值或者对象,那么这个值就会作为then回调的参数
二、传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态
三、传入的是一个对象,并且这个对象有then方法,那么会执行该then方法,并根据then方法的结果来决定Promise的状态

// 第三种传参,代码示范
new Promise((resolve, reject) => {
    resolve({
        then: function(resolve, reject) {
            resolve("thenable value")
        }
    })
}).then(res => {
    console.log(res)
})

finally()是ES9新增的,无论成功还是失败都会调用
Promise.all() 将多个Promise包裹在一起形成一个新的Promise,新的Promise的状态有被包裹的所有Promise决定。
当所有Promise都变成fulfilled时,新Promise变成fulfilled,并将所有Promise返回值组成数组
当有一个Promise变成reject时,新的Promise变成reject,并将第一个reject的返回值作为参数

all方法有一个问题:当有其中一个Promise状态变成reject时,新Promise就会立即变成reject状态
那么对于已经完成的以及处于pending状态的Promise,是获取不到对应结果的。
在ES11中,添加了新的API:Promise.allSettled
该方法会在所有的Promise都有结果后(无论成功失败)才会有最终状态
而且这个Promise的结果一定是fulfilled
返回的结果是一个数组,存放着每个Promise的结果,结果是个对象包含status状态和value值

race方法是当任意一个率先有结果,就返回

any方法是ES12新增方法,和race类似
any方法会等到一个fulfilled状态,才会决定新Promise状态,只要有一个成功就会终止。
如果所有结果都是reject,那么也会等到所有Promise都变成rejected,都是reject会报AggregateError错误

类方法:Promise.resolve()、Promise.reject()

Promise.resolve("xxx")
// 等价于
new Promise((resolve) => resolve("xxx"))
Promise.reject() //同理

then方法内返回的内容会被Promise包裹,默认返回成功态

手写Promise:https://github.com/coderwhy/HYPromise

生成器 Generator
是ES6新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。
(平时函数终止的条件:执行完、return、throw错误)
生成器函数在定义时需要在function关键字后面加一个符号 *
通过yield(一般翻译成产出)关键字来控制函数的执行流程
生成器函数的返回值是一个生成器对象,可以反过来操作生成器函数

function* foo(){
    console.log("111")
    yield "可选产出值"
    console.log("222")
    yield
    console.log("333")
    yield
    // 默认return undefined
}
const gen = foo()
// 此时不会有任何打印输出
// 让它执行第一行输出111
const res = gen.next() // 可以拿到可选产出值
//让它输出222
gen.next() // 可以传值,函数yield可以接收到

生成器事实上是一种特殊的迭代器
迭代器可以使用户在容器对象上遍历,其行为像数据库中的光标。

const testArr = ["abc","a","b","c"]
// 给数组创建一个迭代器(一个next方法)
// next方法是一个无参数或者一个参数的函数,返回一个对象拥有done(boolean)属性和value属性。
// 如果迭代器已经将序列迭代完毕done属性需要为true
// 封装一个函数用来迭代数组
function arrIterator(arr) {
let index = 0
return {
        next: function() {
            if (index < arr.length) {
                return { done: false, value: arr[index++] }
            } else {
                return { done: true, value: undefined }
            }
        }
    }
}
arrIterator(testArr).next() // 遍历

可迭代对象
默认对象是不可迭代的,但如果将迭代器方法内置到对象里面,那么这个对象就变成了可迭代对象
添加 [Symbol.iterator]函数作为属性,返回迭代器对象(内有next方法)用于迭代当前对象。
可迭代对象可进行for...of操作

const info = {
  name: "red",
  age: 18,
  sex: "male",
  [Symbol.iterator]: function () {
      const values = Object.values(this);
      let index = 0;
      const iterator = {
        next: function () {
          if (index < values.length) {
            return { done: false, value: values[index++] };
          } else {
            return { done: true };
          }
      },
  };
  return iterator;
 },
};
    console.log(...info); // red 18 male

从生成器到async/await
async函数中如果有返回值,返回一个Promise.resolve包裹的值
如果返回值是Promise,那么Promise.resolve的状态由Promise决定【它的微任务会被延迟两次(跳过两个个微任务)】
如果返回值是一个对象并实现了thenable(接口对象),那么由then方法决定【它的微任务会被延迟一次(仅跳过一个微任务)】
await会等到Promise的状态变成fulfilled状态
比Promise编写更加方便,更好的可读性和可维护性,在Promise中错误处理通过.catch()方法进行,在async/await中使用try..catch来捕获并处理

监听对象的属性

// 遍历所有属性,对每一个属性使用defineProperty
const keys = Object.keys(obj)
for (const key of keys) {
    let value = obj[key]
    Object.defineProperty(obj, key, {
        set: function(newValue) {
            console.log("监听设置新值")
            value = newValue
        },
        get: function() {
            console.log("监听获取")
            return value
        }
    })
}

但这样做有什么缺点?
首先Object.defineProperty设计的初衷并不是为了去监听一个对象中所有属性的。然后,它只能监听属性的修改和获取,无法监听新增属性和删除属性操作。

在ES6中新增了一个Proxy类,用于帮助我们创建一个代理
也就是说,当我们想监听一个对象时,我们可以先创建一个代理对象(Proxy对象),之后对该对象的所有操作都通过代理对象完成。

const objProxy = new Proxy(obj, {
    set: function(target, key, newValue) {
        console.log(`监听${key}的设置值${newValue}`)
        target[key] = newValue
    },
    get: function(target, key) {
        console.log(`监听${key}的获取`)
        return target[key]
    }
})
// 对obj的所有操作,应该去操作代理对象 objProxy

新增属性也会被set拦截
Proxy除了set、get外还有许多捕获器,可以在网上查

Proxy也可以监听函数对象

Reflect 是ES6新增API,它是一个对象,字面意思是反射
它主要是提供了很多操作JS对象的方法,有点像Object中操作对象的方法
比如:Reflect.getPrototypeOf(target)类似于Object.getPrototypeOf()
Reflect.defineProperty(target,propertyKey,attributes)类似于Object.defineProperty()
为什么需要Reflect?
早期ECMA规范中没有考虑到这种对 “对象本身” 的操作如何设计更规范,所以将这些API都放到了Object上
但Object作为一个构造函数,这些操作放到它身上是不合适的
另外还有一些类似于in、delete的操作符,让JS看起来有些奇怪
使用Proxy时,可以做到不操作原对象

// 原本操作
delete obj.name
if (obj.name) {
    console.log("没有删除成功")
}
// Reflect
if (Reflect.deleteProperty(obj, "name")) {
    console.log("删除成功")
}

Reflect的常见方法可以在MDN上查看
使用Reflect.set方法去替换Proxy中set对原对象赋值的操作

set: function(target, key, newValue, receiver) {
    // 老方法 target[key] = newValue
    // 新方法的好处:不再操作原对象,而且有返回值可以判断是否操作成功
    if (Reflect.set(target, key, newValue)){
        console.log("设置成功")
    }
}

Proxy中set/get方法最后一个参数:receiver 就是代理对象自己。可决定对象访问器setter/getter的指向。便于代理拦截

ES6中使用了class关键字定义类
当我们通过new关键字调用一个Person类时,默认调用class中的constructor方法
实例方法和constructor方法平级直接写就可以,eat(){}
类方法也叫静态方法用static关键字定义
高内聚 低耦合
继承关键字 extends
父类的属性需要在子类中调用super(参数),super关键字也可在子类的实例方法、静态方法中使用
可继承内置类,使用一些内置类的方法
类的混入mixin,JS的类只支持单继承

babel将ES6转ES5,有时间可以再好好看看经babel转换后的源码
ES6对象字面量增强:计算属性名[key]:value、属性增强、方法增强
解构:数组按位置解构,对象按key。
手写个apply:

Function.prototype.apy = function(targetThis){
    Object.definProperty(targetThis,"fn",{
        configurable:true,
        value:this
    })
    targetThis.fn()
    delete targetThis.fn
}

|| 和 ?? 的区别:A ?? B 只有A = undefined或null时 才会返回B
箭头函数无显示原型
严格模式下0开头的八进制数字不合法,所以在ES6中,2进制用0b开头,8进制用0o开头,16进制用0x开头
Symbol 生成一个独一无二的值,常用来做对象的key。对象key只能是字符串或者Symbol 如果是其他值,也会转成字符串。使用Object.keys获取不到symbol的键,需要使用专用方法:Object.getOwnPropertySymbols
Symbol.for("描述") 相同描述在for方法中传,才会生成相同的值。值.description 拿到传入的描述。keyFor获取for方法中的描述

Set/Map、WeakSet/WeakMap
Set元素不可重复
Set/Map中的NaN自等(会被去重)

// 数组去重
const oldArr = [1,22,33,44,22,1]
const newArr = []
for ( const i of oldArr ) {
    if(!newArr.includes(i)){
        newArr.push(i)
    }
}
// 方法二
const oldArr = [1,22,33,44,22,1]
const newArr = Array.from(new Set(oldArr))

.add(元素) 添加元素、.size 获取长度、.delete(元素) 删除某个元素、.has(元素) 是否包含某个元素、clear 清空、forEach遍历,也可用for...of
WeakSet中只能存放对象类型,不能放基本类型,并且对元素是弱引用,如果元素没有其他引用则会呗GC回收。不可遍历

map 映射 相较对象,map可以用复杂类型做键
.set(key, value) 添加、 get()获取 、delete() 删除、has()判断、clear()清空、forEach遍历,for...of遍历时须结构数组

ES8
Object.keys/Object.values/Object.entries 返回由对象的键/值/[键,值]组成的数组
padStart(一共几位,用什么填充)/padEnd 填充,应用场景 时间 日期 填充0
async/await

ES10
flat和flatMap
flat 将一个数组,按照指定深度进行遍历,将遍历到的元素和子数组中的元素组成一个新数组返回(将数组扁平化),也可用递归实现


flatMap 首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。(先进行map操作在进行flat,它的深度是1)

Object.fromEntries(entries) 将Object.entries(对象)返回的值转回对象


str.trimStart/trimEnd 去除首/尾空格 str.trim()首尾都去

ES11
空值合并运算符 ?? 只有undefined和null时会认定false

可选链 obj?.属性

ES12
FinalizationRegistry 对象可以让你在对象被垃圾回收时请求一个回调

WeakRefs 弱引用
当普通的赋值方法将对象赋值给一个变量时,就形成了强引用,导致我们必须手动将变量指向null才可以让对象被垃圾回收。

const infoRef = obj.deref() // 拿到通过弱引用获取的对象
两秒后被释放

逻辑赋值运算符

msg ??= "默认值" // 更严谨
str = obj && obj.name // && 一般的应用场景

字符串replaceAll 方法,一次替换所有匹配项。以前的replace('查找项','替换项')方法只能替换第一个匹配项

ES13
method.at() 在数组中或者字符串中,arr.at(1)就等于arr[1]用来取值

Object.hasOwn(对象,属性) 替代 obj.hasOwnProperty(属性),老方法是实例调用的方法,它放在Object的原型上。
1.防止被重写;2.如果用Object.create(null)创建出来的对象,它隐式原型是null根本就没有hasOwnProperty方法

class中的新成员
私有的对象属性和类属性
类中的静态代码块
static{} // 可以做类的初始化操作,它会是类中最先执行的代码

前端

请先 登录 再评论