Skip to content

ES新版本的新特性

在新的ECMA代码执行描述中(ES5以及之上),对于代码的执行流程描述改成了另外的一些词汇

词汇环境是一种规范类型,用于定义标识符与特定变量和功能的关联,并基于EcmaScript代码的词汇嵌套结构。词汇环境由环境记录和可能无效的对外词汇环境的参考组成。通常,词汇环境与EcmaScript代码的某些特定句法结构相关联,例如函数代码,块或捕获条款的TryStatement子句,每次加载此类代码时都会创建一个新的词汇环境。

  • 基本思路是相同的,只是对于一些词汇的描述发生了改变;
  • 执行上下文站和执行上下文也是相同的;
  1. 执行上下文栈:Execution Context Stack,用于执行上下文的栈结构;
  2. 执行上下文:Execution Context,代码在执行之前会先创建对应的执行上下文;
  3. 变量对象:Variable Object,上下文关联的VO对象,用于记录函数和变量声明;
  4. 全局对象:Global Object,全局执行上下文关联的VO对象;
  5. 活跃对象:Activation Object,函数执行上下文关联的VO对象;
  6. 作用域链:scope chain,作用域链,用于关联指向上下文的变量查找;

2、词法环境(Lexical Environments)

Section titled “2、词法环境(Lexical Environments)”

词法环境是一种规范类型,基于ECMAScript代码的词汇嵌套结构 ,用于定义标识符与特定变量和功能的关联

词法环境是一种保存标识符变量映射的数据结构。 (这里的标识符是指变量/函数的名称,变量是对实际对象的引用[包括函数对象或原始值]。

ECMA文档ECMA-262 2020 8

例如 FunctionDeclaration、BlockStatement 或 TryStatement的Catch子句,并且每次评估此类代码时都会创建一个新的词法环境。

  • 内部词法环境

    • 一个词法环境经常用于 关联 一个函数声明、代码块语句、try-catch语句当它们的代码被执行时,词法环境被创建出来

      当开始执行 函数、 代码块、 或者 try-catch 语句的时候 会创建对应的词法空间

  • 外部词法环境

    一个词汇环境可以作为多个内部词汇环境的外部环境,

    例如,如果一个 FunctionDeclaration 包含两个嵌套的 FunctionDeclaration,那么每个嵌套函数的词法环境都会将当前包围函数的词法环境作为其外部词法环境。

  • 执行上下文会对应一个词法环境,代码运行期间词法环境有两个组件

    执行上下文的LexicalEnvironment和VariableEnvironment组件始终是词汇环境。

    • 词法环境和变量环境是同一个值,要去看通过判断使用那个引用来处理如果此法环境就来处理let,变量环境就来处理var
    1. LexicalEnvironment用于存放let、const声明的标识符:

      let和const声明定义作用域为运行执行上下文的LexicalEnvironment的变量。

      这些变量是在实例化其包含的词法环境时创建的,但在对变量的词法绑定求值之前,不能以任何方式访问这些变量。社区约定俗称一种说法暂时性死区

      初始值的设定要在执行到代码的时候在进行初始化,而不是创建的时候分配的初始化

    2. VariableEnvironment用于存放var和function声明的标识符:

      var语句声明作用域为运行执行上下文的VariableEnvironment的变量。

      Var变量是在实例化的时候就创建了,并且在创建时初始化为未定义。

  • 词法环境由两部分组成

    1. 环境记录(Environment Record)
    2. 外部词法环境(outer Lexical Environment)

上述所说,词法环境和变量环境是两个组件,所以个人理解两个是一体 词法环境 当遇见 var 或者 function声明的变量交给 VE 处理,let const 则交给 LE 处理

  • 理解:词法环境相当于VO(作用域),外部词法环境相当于作用域链

环境记录是一个抽象类,包含三个子类:声明式环境记录、对象环境记录和全局环境记录。函数环境记录和模块环境记录是声明性环境记录的子类

  • 总结:环境记录是用于包含在其范围内定义的标识符集,并将定义的函数、变量try catchjs语法和标识符集进行关联。

声明式环境记录:声明性环境记录用于定义ECMAScript语言语法元素、函数声明、变量声明和直接将标识符绑定与ECMAScript语言值关联起来的Catch子句。

  • 每个声明性环境记录都与一个包含变量、常量、let、类、模块、导入和/或函数声明的 ECMAScript 程序范围相关联。 声明性环境记录绑定由包含在其范围内的声明定义的标识符集。

  • 环境记录的一种会有自己的内存空间,保存let const 模块的导入导出 和类 的声明。

  1. 对象环境记录用于定义ECMAScript元素的效果,例如With Statement,它将标识符绑定与某些对象的属性关联起来。

    每个对象环境记录都与一个称为其绑定对象的对象相关联。对象环境记录绑定直接对应于其绑定对象的属性名称的字符串标识符名称集。

    不是 IdentifierName (标识符-变量名称)形式的字符串的属性键不包含在绑定标识符集中。无论其 [[Enumerable]] 属性的设置如何,自有属性和继承属性都包含在集合中。

    因为可以从对象中动态添加和删除属性,所以由对象环境记录绑定的标识符集可能会作为任何添加或删除属性的操作的副作用而发生变化。

    由于这种副作用而创建的任何绑定都被认为是可变绑定,即使相应属性的 Writable 属性的值为 false。对象环境记录不存在不可变绑定。

    为 with 语句 (13.11) 创建的对象环境记录可以提供它们的绑定对象作为隐式 this 值,以在函数调用中使用。该功能由与每个对象环境记录关联的 withEnvironment 布尔值控制。默认情况下,对于任何对象环境记录,withEnvironment 的值都是 false。

    1. 对象记录绑定了 galbol Object ,在全局对象式环境记录就是windows,包含了一些内置对象 和 函数声明,var 的变量声明

    2. 对象式环境记录是在 全局环境下才有 或者使用with语句时候

      可以说该词法环境有绑定对象的时候,才会有对应得对象式词法环境

    • 总结:对象环境记录一般存在全局环境记录,和 with 代码块内,主要作用是将标识符和变量的属性进行关联。

全局环境记录用于表示由在公共领域中处理的所有 ECMAScript 脚本元素共享的最外层范围。全局环境记录为内置全局变量、全局对象的属性以及脚本中出现的所有顶级声明提供绑定。

全局环境记录在逻辑上是单个记录,但它被指定为封装对象环境记录和声明性环境记录的组合。

对象环境记录具有作为其基础对象的关联领域记录的全局对象。

这个全局对象是全局环境记录的 GetThisBinding (就是一个对应的算法,针对不同的环境全局环境下返回的windows)具体方法返回的值。全局环境记录的对象环境记录组件包含所有内置全局变量(第 18 条)的绑定以及由全局代码中包含的 FunctionDeclaration、GeneratorDeclaration、AsyncFunctionDeclaration、AsyncGeneratorDeclaration 或 VariableStatement 引入的所有绑定。

全局代码中所有其他 ECMAScript 声明的绑定包含在全局环境记录的声明性环境记录组件中。 所以在控制台生命式环境记录为Script

可以直接在全局对象上创建属性。因此,全局环境记录的对象环境记录组件可能包含由 FunctionDeclaration、GeneratorDeclaration、AsyncFunctionDeclaration、AsyncGeneratorDeclaration 或 VariableDeclaration 声明显式创建的绑定和作为全局对象的属性隐式创建的绑定。为了识别使用声明显式创建的绑定,全局环境记录维护使用其 CreateGlobalVarBinding 和 CreateGlobalFunctionBinding 具体方法绑定的名称列表。

  1. 全局使用let、const声明的变量,不会放在object,放在声明式环境记录里
  2. var 会放在全局对象里,全局的对象环境记录,有关联了全局对象,所以声明的函数、var都会在全局对象里
  • 全局环境记录在逻辑上是单个记录,但它被指定包含对象环境记录和声明性环境记录的组合。

  • 对象环境记录会绑定全局对象(windows), 作为其基础对象的关联领域记录。

  • 全局环境记录的对象环境记录组件包含所有内置全局变量

在执行函数时生成的词法环境中的环境记录, 函数环境记录和Module环境记录都是声明式环境记录的子集

函数环境记录是一个声明式环境记录,用于表示函数的顶级范围,如果函数不是 ArrowFunction,则提供 this 绑定。 如果函数不是 ArrowFunction 函数并引用 super,则其函数 Environment Record 还包含用于从函数内部执行 super 方法调用的状态。

  • 函数环境记录是一个声明性环境记录
  • 所以 变量声明都会放在 声明性环境记录当中,没有对象式环境记录

模块环境记录是用于表示 ECMAScript 模块的外部范围的声明性环境记录。 除了正常的可变和不可变绑定之外,模块环境记录还提供不可变导入绑定,这些绑定提供对另一个环境记录中存在的目标绑定的间接访问。

  • 属于声明式环境字子类
  • Module环境记录 就是在使用模块化导入的时候,也会有对应的词法空间

词法环境相当于VO,不过在全局环境下的VO不在完全由windows组成的了 ,而是GO绑定到对象式环境记录

还有一个声明式环境记录,来存放let const声明的变量

在函数的词法环境,就只有声明式环境记录,var 和function、let、const都放在当前的环境记录,剩下的都一样

全局代码执行和函数 trycatch with在执行的时候都会有根据代码对应的词汇嵌套结构,内部的词汇环境会指向外部的词汇环境,就是作用域链。

在ES5中我们声明变量都是使用的var关键字,从ES6开始新增了两个关键字可以声明变量:let、const

  • let关键字:

    从直观的角度来说,let和var是没有太大的区别的,都是用于声明一个变量

    • 在当前代码块引入作用域
  • const关键字( 建议 const作为主要变量声明使用除非当时就能确定该变量是需要修改的 )

    • const关键字是constant的单词的缩写,表示常量、衡量的意思;
    • 但是如果赋值的是引用类型,那么可以通过引用找到对应的对象,修改对象的内容;
    1. 它表示是一个只读的常量,一旦声明,不可修改

      (面试题)基本数据类型执行期间在 ‘栈内存’ 中,值存储在 ‘堆内存’ 中,通过指针来指向 ‘堆内存’ 中对应的 值,所以,const定义的引用数据类型只要不改变指针,是可以修改里面的属性的

    2. 定义是要有初始值

    3. 会在当前代码块内引入作用域

  • 注意

    • letconst 在同一个词法环境下不允许重复声明变量,同时会有暂时性死区
    • 和var分别声明相同的变量也是不允许的
    • let、const 不添加windows 对象

let、const和var的另一个重要区别是作用域提升

  • ECMA规范:let 和 const 声明定义了执行上下文范围内的词汇环境的变量,这些变量是在实例化其包含的词法环境时创建的,但在对变量的词法绑定求值之前,不能以任何方式访问这些变量
  • 理解:在let、const定义的标识符真正执行到声明的代码之前,是不能被访问的

块作用域的顶部一直到变量声明完成之前,这个变量处在暂时性死区(TDZ,temporal dead zone)

  • 使用术语 “temporal” 是因为区域取决于执行顺序(时间),而不是编写代码的位置
  • 暂时性死区和定义的位置没有关系, 和代码执行的顺序有关系

1.2、let const 是否存在作用域提升

Section titled “1.2、let const 是否存在作用域提升”
  • 变量在执行上下文的词法环境创建出来的时候,变量事实上已经被创建了,只是这个变量是不能被访问的。 那么变量已经有了,但是不能被访问,是不是一种作用域的提升呢?
  • 作用域提升理解:在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升;
  • 在这里,它虽然被创建出来了,但是不能被访问,我认为不能称之为作用域提升;
  • 所以我的观点是let、const没有进行作用域提升,但是会在解析阶段被创建出来。

ES6中新增了块级作用域,并且通过let、const、function、class声明的标识符是具备块级作用域的限制的

  • 函数拥有块级作用域,但是外面依然是可以访问的。

    在非严格模式下,引擎会对函数的声明进行特殊的处理,允许像var那样进行提升。所以可以访问

  • 严格模式下还是不允许访问的

  • 存在代码块的情况下,在函数未声明之前也是无法调用的,只能在声明之后调用

{
function foo() {
console.log("foo");
}
}
foo()

描述:就是用let和const声明的变量,在当前代码块内是由自己的作用域和对应的词法环境

  • 当前作用域内不允许做重复的变量声明

作用域提升、window全局对象、没有块级作用域等都是一些历史遗留问题, 是JavaScript在设计之初的一种语言缺陷

  • 在实际工作中,我们可以使用最新的规范来编写,也就是不再使用var来定义变量了
  • 对于let和const来说,是目前开发中推荐使用的;
    1. 我们会优先推荐使用const,这样可以保证数据的安全性不会被随意的篡改;
    2. 只有当我们明确知道一个变量后续会需要被重新赋值时,这个时候再使用let;
    3. 这种在很多其他语言里面也都是一种约定俗成的规范,尽量我们也遵守这种规范;
  1. 保存方式:会保存在不同的环境记录当中,var声明的会保存到global 对象中,let 会保存在声明式环境记录当中

    let、const声明的的变量在当前代码块会有对应的词法环境和作用域的

  2. 引入作用域:let 和const 将当前代码块引入作用域

    var 声明在非函数代码块内,尽管是合法的语句这种语句块也不会引入作用域

  3. 是否重复声明:let 和 const 不可以重复声明

    var 后面声明的变量都会覆盖前边相同标识符变量

  4. 变量提升:let 和 const 会存在暂时性死区,不认为有变量提升

    var 会有作用域提升

  • es3的内存描述

    • 作用域链存在函数对象当中,函数执行的时候会创建VO对象回去上下文的Scopes chain中获取对应的VO对象中的变量
    • 因为立即执行函数,执行一次就会有一个vo,关联到当前的函数对象存在引用
    • 产生了闭包,闭包会保存对应立即执行函数时的VO,所以每一个按钮函数对象都会有自己作用域
  • 用es5的内存描述来说

    • 函数有自己的块级作用域,每执行一次就会关联上下文产生一个对应的词法环境,执行的时候也会形成闭包
    • 当点击按钮函数的时候 按钮函数的外部词法环境指向的是上层作用域,存在引用
    • 所以立即循环多次执行函数的多个词法环境,因为被引用所以不会被销毁
    • 也是同样的每一个按钮函数的外部词法环境都会关联到 i ,这里是由4个不同的词法环境对象,每一个对象保存的i都是不一样的
let btnEls = document.querySelectorAll("button")
for (var i = 0; i< btnEls.length; i++) {
(function (i) {
//这样也会形成闭包var s = i,只要于上层作用域形成引用关系即可
//由于被点击时间引用的原因该函数的词法环境不会被销毁
btnEls[i].addEventListener("click", () => {
//这里引用了上层词法环境的变量形成了闭包
console.log(i);
})
})(i)
}

第三节、字面量的增强(ES6)(三个)

Section titled “第三节、字面量的增强(ES6)(三个)”

ES6中对 对象字面量 进行了增强,称之为 Enhanced object literals(增强对象字面量)。

  • 属性的简写:Property Shorthand

  • 方法的简写:Method Shorthand

  • 计算属性名:Computed Property Names

var name= "zhangsan";
var figure= "http://ssss.img"
var calcArg1 = "feat"
var calcArg2 = "ure"
var obj = {
//属性的增强
name,
figure,
//计算属性写法
[calcArg1 + calcArg2] : "lisi",
["rest" + calcArg1]: "wangwu",
//函数的增强写法
fn() {
console.log("first")
}
}
console.log(obj)

ES6中新增了一个从数组或对象中方便获取数据的方法,称之为解构Destructuring。

  • 解构赋值 是一种特殊的语法,它使我们可以将数组或对象“拆包”至一系列变量中。
  • 解构主要划分,
    1. 数组的解构
    2. 对象的解构
  1. 扩展运算符作为 形参 的时候 为剩余参数 数组类型
  2. 扩展运算符作为 实参和解构参数 的时候,作为展开运算符
  1. 接收到返回值对象或者数组的时候,进行解构获取其中的值,这种情况下一般都是对象解构
    • 异步请求返回值
    • 浅拷贝对象
  2. 比如对函数的参数进行解构;

将对应索引位置的变量,进行赋值

let obj = {
address: "天津市"
}
let arr = ["zhangsan", 123, "lisi", obj]
var [name, price, nickName, objs] = arr;
console.log(name, price, nickName, objs)

终于要还是根据索引位置进行结构,空元素也会占一个索引

//顺序解构
let [name, , nickname] = arr;
console.log(name, nickname);

就是扩展运算符的展开,和剩余参数的使用

  • 将剩余解构的参数,转换到 …剩余参数。

    扩展运算符可以将,该索引位置后面所有的元素保存为一个Array

//数组解构
const arr = [123, 1, 2, 3]
let [name, nickname, ...newArr] = arr;
console.log(name, nickname, newArr);
  • 合并数组
var args = [123, 222, 333]
var rest = ["zhangsan", "lisi"]
var temp = [...args, ...rest]

当数组对应的索引位置,没有赋值,或者 undefined 的情况下 会是由默认值

注意:null,不会触发默认值

let arr = [undefined, 123, "lisi", obj]
var [name = "lisi", num, nameNick, temp] = arr;
console.log(name, num, nameNick, temp);
let obj = {
name: "zhangsan",
age: 56,
address: "天津市",
fn() {
console.log("fn")
}
}
let {name, age, address} = obj

只需要变量名和属性名,相符就可以不用管顺序

let {age, address, fn} = obj

解构的时候可以重命名,可以设置默认值

//3、重命名解构
let {name: nickname, age: num, address: addr, fn: foo} = obj;
console.log(nickname, num, addr, foo);

当没有该属性,或者属性值为 undefined 的时候 会触发默认值

也可以结合重命名来设置默认值

let obj = {
// name: "zhangsan",
age: undefined,
address: "天津市",
fn() {
console.log("fn")
}
}
let {
name = "lisi",
age: num = 333,
address, fn
} = obj;
console.log(name, num);

5、[ES9新增]- 扩展运算符对象解构

Section titled “5、[ES9新增]- 扩展运算符对象解构”
  • 是一种浅拷贝

  • 注意:扩展运算符作为实参的时候 可以展开 数组 但是不能直接解构 对象

    简单说 展开元素符解构只能在对象里面用。

const obj = {
name: "lisi",
age: 17,
}
const info = {...obj, address: "天津市"}
console.log(info)
//错误示范
function foo(...args) {
console.log(args)
}
//这里不能直接做对像解构属性做入参,要为可“迭代对象”才行(类似数组)
foo(...obj)

相当于将传入的参数解构 nameage属性,之后赋予默认值为空对象

funtion foo({name, age} = {}) {}

在ES6之前,如果我们想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的(ugly)。 ES6允许我们使用字符串模板来嵌入JS的变量或者表达式来进行拼接:

  • 使用 “ 符号来编写字符串,称之为模板字符串;
  • 模板字符串中,我们可以通过 ${expression} 来嵌入动态的内容;

模板字符串还有另外一种用法:标签模板字符串(Tagged Template Literals)。

  • 基本使用这里就不举例了

  • 我们可以使用模板字符串来调用函数

    1. 使用模板字符串的调用函数的时候,会将模板字符串中的 字符串表达式 作为参数

    2. 会将模板字符串 以 ${expression}表达式为分割符进行分割,分割之后形成数组作为第一个参数的实参传入

      • 在模板字符串的最后 ${expression} 和 反引号之间也会存在一个空的字符串也会在数组当中,${expression} 不放在最后或者最前面就不会出现空的字符串

      • 如果一定要在开头结尾使用表达式,可以使用filter函数进行过滤字符串 var newArr = arr.filter(item => item)

      • 函数第一个参数值总是包含字符串的数组

    3. 最后在将表达式作为剩下的实参依次传入,一个表达式对应一个参数

    //模板字符串调用函数
    function foo(...rest) {
    //第一个参数值总是包含字符串的数组。
    console.log(rest[0].filter((val) => val))
    console.log(rest[1])
    console.log(rest[2])
    }
    foo`你好张三${name} 年龄 ${age}`

2、使用场景React的styled-components库

Section titled “2、使用场景React的styled-components库”

了解到在使用react 的时候css 是用js写的,用的就是这个语法

在ES6之前,我们编写的函数参数是没有默认值的

  • 而在ES6中,我们允许给函数一个默认值
  1. 默认值的定义,规范要放到最后(在很多语言中,如果不放到最后其实会报错的)

  2. 注意:设置默认值的参数不会计算到函数属性 length 属性内前面arguments 有提到

  3. 默认值,只有在参数没有定义非 undefined 的情况下会生,所以传入空null是不会触发默认值的

// 默认值
function temp(arg = "默认值参数为定义") {
console.log(arg)
}
//这里不会触发默认值
temp(null)

es6语法中参数也可以用于解构,和设置默认值

  1. 默认值 只有在参数没有定义的为undefined 的时候会触发
  2. 默认值剩下的和 解构的默认值是相同的
const arr = [, 12, "天津市"]
const obj = {name: null, age: "13", address: "北京市"}
//数组解构
//顺序解构
function foo([name = "默认值", , address] = arr) {
console.log(name, address)
}
foo()
//对象解构
function bar({name = "默认值", age, address} = obj) {
console.log(name, age, address)
}
bar()
// 别名 + 默认值
function baz({name: nickName = "默认值", age, address} = obj) {
console.log(name, age, address)
}
baz()

Symbol 是基本数据类型,每个从 Symbol() 返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的

  • 不完整的构造函数

    它的静态方法会暴露全局的 symbol 注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:“new Symbol()”。

  • Symbol 是不能通过 for…in来遍历的

ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;

  • 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性。

  • 在是实现apply、call、bind方法的时候想传入对象中添加函数 , 会有一定的几率添加的函数名会重复,之后覆盖原有的方法(尽管这种记录很小)

    • 重构 Function.prototype.call方法

      const obj = {
      name: "testObj"
      }
      //重构call方法
      Function.prototype.ynCall = function(thisArg, ...otherArg) {
      //判空
      thisArg = thisArg ?? window
      //转换为包装类
      thisArg = Object(thisArg)
      const key = Symbol()
      Object.defineProperty(thisArg, key, {
      configurable: true,
      value: this
      })
      thisArg[key](...otherArg)
      delete thisArg[key]
      }
      function baz(...args) {
      console.log(this, ...args)
      }
      baz.ynCall(obj,"zhangsan", 123)
    • 重构Function.prototype.bind 方法

      //重构bind方法
      Function.prototype.ynBind = function(thisArg, ...otherArg) {
      //判空
      thisArg = thisArg ?? window
      //转换为包装类
      thisArg = Object(thisArg)
      const key = Symbol()
      Object.defineProperty(thisArg, key, {
      configurable: true,
      value: this
      })
      //解决 参数即可以是参数列表 也可以是 数组
      // 核心代码
      return function(...args) {
      const temp = otherArg.flat().concat(args)
      thisArg[key](...temp)
      }
      //这里不能删除了
      // delete thisArg[key]
      }
      const bindFn = baz.ynBind(obj, 666)
      bindFn("wangwu")
  • 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉;

Symbol就是为了解决上面的问题,用来生成一个独一无二的值。

  1. Symbol值是通过Symbol函数来生成的,生成后可以作为属性名;
  2. 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值;
  • es5之前只有字符串这一种可以作为属性名,es6之后只能有以上两种方式Symbol 和字符串

  • Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的;

const key = Symbol()
const key2 = Symbol()
console.log(key == key2)
//output false
  • 我们通常会使用Symbol在对象中表示唯一的属性名:
//属性描述符
const key = Symbol()
const key2 = Symbol()
//使用计算属性设置
const foo = {
[key2]: "zhangsan",
[key]: function() {
console.log("descriptor ")
}
}
//获取
foo[key]()
console.log(foo[key2])

Symbol() 函数 在ES10之后 支持 创建Symbol 类型的时候传入一个描述description:这个是ES2019(ES10)新增的特性;

  • 简单使用
let variable1 = Symbol("zhangsan")
let variable2 = Symbol("zhangsan")
console.log(variable1.description === variable2.description)
  • 个人觉得在不使用 for 方法的时候 b 用没有

  • 就是可以获取一下描述符,有啥用啊?

    mdn 文档对于 Symbol 类型的描述用于调试而已。

  • 如果我们现在就是想创建相同的Symbol类型的值就可以使用描述符
  • for和keyFor好像是全局共享的,使用框架的时候试下,查下mdn文档

添加属性描述符的方法就是使用Symbol函数对象中的 for() 方法

const only = Symbol.for("zhangsan")
const only2 = Symbol.for("zhangsan")
console.log(only === only2, only, only2)
//output true

通过 Symbol 函数对象中的 keyFor() 方法来获取对应Symbol 值对应的描述符

//属性描述符
const only = Symbol.for("zhangsan")
const only2 = Symbol.for("zhangsan")
console.log(only === only2, only, only2)
//output true
//获取属性描述符
console.log(Symbol.keyFor(only), Symbol.keyFor(only2))

第八节、ES6新增的数据结构 Set weakSet、Map weakMap

Section titled “第八节、ES6新增的数据结构 Set weakSet、Map weakMap”

在ES6之前,我们存储数据的结构主要有两种:数组、对象。

  • 在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。

新 Set 方法:原生支持集合的数学运算,包括 union()(并集)、intersection()(交集)、difference()(差集)和 isSubsetOf()(子集判断)等。

  1. 无序不可重复:是可迭代的 weakSet是不可迭代的

  2. 创建方式:没有字面量的创建方式只有 add 和 构造出入可迭代对象

    创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式),和add实例方法

  3. 没有索引:因为 Set 无序的集合所以 不能获取指定索引位置的元素 只能通过循环遍历 或者 entries 获取

  4. 不能使用forin:Set 集合不能使用 for...in 遍历出来的是空的

  • 使用场景:目前能想到的就是去重,但是像java 一样如果是对象类型 对比的是地址值, 值相同的话也不一定会去重

1.1、Set添加元素的方式

目前了解的只有两种

  1. 通过构造函数创建,只不过构造函数只接收可迭代对象
    • 也用于作为 Set 集合转换
  2. add方法添加

1.2、Set的实例方法

  • 下方的都是实例对象的方法

属性只有一个就是size属性

  1. add(value):

添加某个元素,返回Set对象本身

  1. delete(value):

从set中删除和这个值相等的元素,返回boolean类型;

  1. has(value):

判断set中是否存在某个元素,返回boolean类型;

  1. clear():

清空set中所有的元素,没有返回值;

  1. forEach(callback(value, value, array), [, thisArg]):

通过forEach遍历set;

参数一和参数二都是元素,没有索引

  1. entries() 条目的意思

一个新的包含 [value, value] 形式的数组迭代器对象

  • 由于集合对象不像 Map 对象那样拥有 key,然而,为了与 Map 对象的 API 形式保持一致,故使得每一个 entry 的 key 和 value 都拥有相同的值,因而最终返回一个 [value, value] 形式的数组。
  • 属性方法:next()
const setInstance = new Set(arr)
setInstance.add("zhangsan")
setInstance.add("lisi")
setInstance.add("wangwu")
//获取 entries '迭代器对象'
const setEntries = setInstance.entries()
//再通过 next() 方法获取获取下一个 '元素对象'
const nextSet= setEntries.next()
//使用使用value '属性' 获取数组元素
console.log(nextSet.value)

WeakSet 对象允许你将 弱保持对象 存储在一个集合中。GC不会将集合保存的对象算进可达性算法内

  • WeakSet 不能遍历、不能拿 size,就是因为它的内容可能随 GC 随时变化
  • 就是发现除了当前集合外 不存在别的引用的时候 WeakSet 中的对象就会被回收

例子:一个table.for循环列表10个,这个时候需要导出选中的数据,weakSet.add(dataList[i]) , 之后点击分页dataList又重新创建了10个对象,这个是由前dataList[i] 销毁了,weakSet中的dataList[i]也会跟这销毁,如果是 set dataList[i] 不会销毁因为有了一个新的引用

注意weakSet存放弱引用,不是它自己是弱引用,如果weakSet 没有数据也会存在,除非它自己的引用不见了

2.1、WeakSet特点

  1. WeakSet 不能循环遍历获取

    因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。

  2. *WeackSet 不能存放基本数据类型

    可以放包装类型使用Object()

  3. 同样是无序的,不能迭代也不能获取 entries 所以是不能获取元素的,只能判断有没有对应的元素

  4. 也同样是无序不可重复的

2.2、WeakSet 的实例对象方法(3个)

  1. add(value):

添加某个元素,返回WeakSet对象本身

  1. delete(value):

从 WeakSet 中删除和这个值相等的元素,返回boolean类型;

  1. has(value):

判断 WeakSet 中是否存在某个元素,返回boolean类型;

2.3、WeakSet的应用场景

  • 注意事项:weakSet 使用场景一定是在,一次性的业务逻辑当中保存的临时数据,当前逻辑执行完就有可能会被回收

    一般会在几秒钟之后,不会立刻回收的,这个已经使用 FinalizationRegistry 测试过了

  1. 重点可以在集合数据进行遍历的时候,充当临时容器,保存需要条件的判断。

    只有删除,添加,has,应该只是用来进行大型集合循环进行判断的容器

  2. 可以用来做 case 边界判断限制只允许 当前类创建的对象才能调用该方法

//限定函数调用的对象
const collection = new WeakSet()
class Persion {
constructor() {
collection.add(this)
}
runing() {
if(collection.has(this)) {
console.log("成功调用runing")
} else {
console.log("调用对象错误")
}
}
}
let persion = new Persion()
const foo = persion.runing
foo()
console.log(collection)

新增的数据结构是Map,用于存储映射关系。

一个 Map 对象在迭代时会根据对象中元素的插入顺序来进行遍历,一个 for…of 循环在每次迭代后会返回一个形式为 [key,value] 的数组。

3.1、构造函数

只能是两个值的可迭代对象

let myMap = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
])

3.2、对象和Map的区别

  1. 对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key)
    1. 在js中,对象这种数据结构的 key 只能存放两种基本数据类型数据类型,String、Symbol
    2. 如果使用对象来作为 key ,会将对象的数据类型转化成字符串来进行存储
  2. 任何值(对象或者基本类型)都可以作为一个键或一个值。
  3. 对象中的原型对象也会有 key 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
  4. Map 的键值对的 数量 可以直接获取
  5. Map 是可迭代对象,且频繁删除新增的时候性能比较好

3.3、特点 4个重点

  • Map 是通过键值对存储的一种数据结构
  1. 是有序,但是key值不可重复的,可遍历
  2. key 可以设置任意类型,可以是对象会被转换成字符串不需要添加泛型
  3. 可迭代 且频繁删除新增的情况下性能较好

3.4、Map的实例对象方法(6)

  • 属性只有一个size
  1. set(key, value):

在Map中添加key、value,并且返回整个Map对象;

  1. get(key):

根据key获取Map中的value;

  1. has(key):

判断是否包括某一个key,返回Boolean类型;

  1. delete(key):

根据key删除一个键值对,返回Boolean类型;

  1. clear():

清空所有的元素;

  1. forEach(callback(key, val), [, thisArg]):

通过forEach遍历Map;

  1. entries()

同样返回一个迭代器, 在使用 entries.next().value 一个一个获取值,目前还没有想到应用场景

  1. 有 .keys() 和 .values()

和Object.keys()方法效果一样的

3.5、Map 的应用场景

  1. 频繁的进行数据的增删改
for (const item of map) {
//就是想记下这个解构的使用
const [key, value] = item
console.log(key, value)
}

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

4.1、特点

  1. 不可迭代

  2. WeakMap的key不能是基本数据类型,不接受其他的类型作为key,可以使用包装类。包装类对象会自动转换成基本数据类型

    因为原始数据类型是没有引用的,WeakMap 是弱引用的所以key只能是对象类型

  3. WeakMap 的 key 对 对象的引用是弱引用如果没有其他引用引用这个对象,那么GC可以回收该对象;

4.2、WeakMap常见对象实例方法有四个

  1. set(key, value):

    在Map中添加key、value,并且返回整个Map对象;

  2. get(key):

    根据key获取Map中的value;

  3. has(key):

    判断是否包括某一个key,返回Boolean类型;

  4. delete(key):

    根据key删除一个键值对,返回Boolean类型

  5. clear(): 方法

4.3、WeakMap 的应用场景

个人觉得也是进行逻辑判断的

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

proxy在 目标对象的外层搭建了一层拦截,外界对目标对象的某些操作(后文会说明,有哪些操作可以拦截),必须通过这层拦截。

  • 通过构造函数生成 proxy 代理对象,

  • 参数一target参数是要拦截的目标对象,

    要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理

    • 可以监听数组,很重要
  • 参数二handler参数也是一个对象,用来定制拦截行为。

  • 总结(mdn):Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义

在vue2中是通过 Object.defineProperty 方法存取属性描述符 descriptor 来监听属性是否被修改 获取的

  • 个人觉得也避免了,修改自身属性死循环的问题
  • vue3.0发布后,在双向数据绑定这里,使用proxy代替了object.defineProperty,众所周知,obj.defineProperty是对对象属性监听,循环对象,一个个属性监听,proxy是对一整个对象进行监听。而proxy的一大优势就是可以监听数组。
  • Object.defineProperty 设计的初衷,不是为了去监听一个对象中所有的属性的。

    我们在定义某些属性的时候,初衷其实是定义普通的属性,

  • 如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么 Object.defineProperty 是无能为力的

  • 存储数据描述符设计的初衷并不是为了去监听一个完整的对象,强行的监听一个对象会很耗费性能

const obj = {
name: "zhangsan",
age: "age",
runing: function () {
console.log("zhangsan___runing")
}
}
// 可以使用class getter 和 setter 的访问器 但是要逐一属性添加,工作量很大
for (let entries of Object.entries(obj)) {
let [key, value] = entries
let temp = value;
Object.defineProperty(obj, key, {
get() {
console.log(temp,"get 访问器");
return temp;
},
set(value){
console.log(value,"set 访问器");
temp = value
}
})
}
console.log(obj.name);
obj.age = 99
//不能监听新增 和 删除
obj.addr = "天津市"
delete obj.addr

ES6中,新增了一个Proxy类, 可以通过创建代理对象的方式来操作实例对象,这样我们可以在代理类中监听一个对象的相关操作

  • 代理对象可以监听我们想要对原对象进行哪些操作

  • 常见捕获器

  • 注意
  1. 这里的参数 key 是 spring 类型的要使用计算属性获取对应的值

  2. constructapply,它们 主要 应用于函数对象

1、handler.get()

handler.get() 方法用于拦截对象的读取属性操作。

  • 参数(3):(target 目标对象key 键receiver Proxy 或者继承 Proxy 的对象

    receiver 一般情况下就是 当前proxy 对象

  • 返回值:可以返回任何值**,默认返回 set 之后 或者 默认值**

2、handler.set() 返回值boolean

handler.set() 方法是设置属性值操作的捕获器。

  • 参数(4):(target 目标对象key 键value 值, receiver
  • 返回值:布尔值
    • 返回 true 代表属性设置成功
    • 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。
    • 默认返回true

3、handler.deleteProperty() 返回值boolean

handler.deleteProperty() 方法用于拦截对对象属性的 delete 操作。

  • 参数(2):(target 目标对象,key 属性名
  • 返回值:必须返回一个 Boolean 类型的值,表示了该属性是否被成功删除。

4、handler.has() 返回值boolean

handler.has() 方法是针对 in 操作符的代理方法。可以看作是针对 in 操作的钩子。

  • 参数(2):(target 目标对象, key,是否存在的属性)
  • 返回值:has 方法返回一个 boolean 属性的值。
class Foo {
_name = undefined;
constructor(name, addr, age) {
this.name = name;
this.addr = addr;
this.age = age;
}
get name() {
this._name = name
return this._name;
}
set name(value) {
this._name = value
}
}
/* const obj = {
name: "lisi",
age: 34,
addr: "北京",
_name: null,
get name() {
this._name = name
return this._name;
},
set name(value) {
this._name = value
}
}
*/
const foo = new Foo("zhangsan", "天津市", 34)
const fooProxy = new Proxy(foo, {
get(target, key, receiver) {
return Reflect.get(target, key)
},
set(target, key, value, receiver) {
console.log(value)
// class 中默认是严格模式,在严格模式下 这里要改变访问器的this指向要有返回值。
const isSuccess = Reflect.set(target, key, value, receiver)
return isSuccess
}
})
fooProxy.name = "wnagwu"
console.log(foo)

handler.apply() 监听函数

handler.apply() 方法用于拦截函数的调用。

  • 参数(3)(target目标对象, thisArg被调用时的上下文对象,就是函数执行时候的this, argumentsList 被调用时的参数数组
  • 返回值:看你监听函数有没有返回值, 需要手动在apply 函数内调用当前函数。
  • 注意:args 是个数组proxy内部传参调用的时候记得使用扩展运算符解构
//监听指定函数的调用
const runFunc = new Proxy(foo.runing, {
/**
* @param {Funcion} 当前监听的函数
* @param {thisArg} 当前函数的this
* @param {Array} 参数列表
*/
apply: (fn, thisArg, args) => {
console.log("函数被调用了")
console.log(target)
//这里需要手动调用下
return fn(...args)
}
})
//使用代理对象调用
runFunc("zhangsa")

handler.construct() 监听构造函数

handler.construct() 方法用于拦截 new 操作符。为了使 new 操作符在生成的 Proxy 对象上生效,用于初始化代理的目标对象自身必须具有 [[Construct]] 内部方法(即 new target 必须是有效的)

  • 参数:(target 闯入的构造函数对象,argumentsList 参数列表数组类型, receiver proxy 实例对象)
  • 返回值:必须要返回一个对象
class Foo{
constructor(name, age, addr) {
this.name = name;
this.age = age;
this.addr = age;
}
}
const objProxy = new Proxy(Foo, {
/**
* @param {Function} 传入的目标构造函数
* @param {Array} 参数列表
* @param {Proxy} Proxy 的实例对象 或者继承 Proxy的对象
*/
construct(fn, argumentsList, receiver) {
console.log(target)
console.log(argumentsList)
console.log(receiver)
//这里一定要返回一个对象,否则报错
return new Number(123)
}
})
const foo = new objProxy("lisi", 45, "田间是")
console.log(foo)
  1. handler.getPrototypeOf()
  2. handler.setPrototypeOf()
  3. handler.isExtensible()
  4. handler.preventExtensions()
  5. handler.getOwnPropertyDescriptor()
  6. handler.defineProperty()
  7. handler.ownKeys()
    • Object.getOwnPropertyNames 方法和
    • Object.getOwnPropertySymbols 方法的捕捉器。
  1. 修改原始对象代理对象会随之改变,但是不会触发捕获器,只有修改代理对象才会触发捕获器。

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers (en-US)的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

  • 简述

    • 一个工具集合

    简述: 就是保存了操作对象的基本方法判删改查构造器函数,还有Object中的操作、获取描述符的方法(但是无论操作,和获取都没有批量操作的方法),和原型操作、获取的方法

  • 特点

    • Reflect的所有属性和方法都是静态的(就像Math对象)。

    • 其中的一些方法与 Object 相同,尽管二者之间存在某些细微上的差别

      1. 静态方法 Reflect.defineProperty() 基本等同于 Object.defineProperty() 方法,唯一不同是返回 Boolean 值。
      2. 还有进行对象的基本操作的时候也会返回相对应方法的boolean值
  • 总结: reflect 是一个工具集合,通常和proxy一起使用,使用反射的方式修改对象。包含Object中的部分方法

1、Reflect.getOwnPropertyDescriptor()

  • 没有Reflect.getOwnPropertyDescriptors() 批量获取的方法

静态方法 Reflect.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor() 方法相似。如果在对象中存在,则返回给定的属性的属性描述符。否则返回 undefined

2、Reflect.apply()

静态方法 Reflect.apply() 该方法与 ES5 中Function.prototype.apply()方法类似:调用一个方法并且显式地指定 this 变量和参数列表 (arguments) ,参数列表可以是数组,或类似数组的对象。

3、Reflect.construct()

Reflect.construct() 方法的行为有点像 new 操作符 构造函数 , 相当于运行 new target(...args).

  1. Reflect.construct(Person, [brand, info], Student)

    理解作用:调用了 Person 的构造函数传入参数 不过返回的对象时 Student 类型的

  • 实现借用构造函数
class Person{
constructor(brand, info){
this.brand = brand;
this.info = info;
}
}
class Student{
constructor(brand, info, price, score){
Object.assign(this, Reflect.construct(Person, [brand, info], Student))
this.price = price;
this.score = score;
// this = stu
}
}

4、Reflect.set()

Reflect.set 方法允许你在对象上设置属性。它的作用是给属性赋值并且就像 property accessor 语法一样,但是它是以函数的方式。

  • 会返回一个boolean值
  • 结合 Proxyreceiver 如果遇到 setterreceiver则为setter调用时的this值。
  • 注意:Proxy 结合使用改变访问器this的时候 (这个是重点,不改变访问器的this就没事), 在严格模式下要在 Proxy 中的 set 捕获器里面返回 Reflect.set 的返回值
    • ES6 的class 中默认使用的就是严格模式,所以要监听class的时候,要返回 Relect.set 的值

5、Reflect.getPrototypeOf()

Object相同 返回一个对象

6、Reflect.setPrototypeOf()

Object 相同 返回boolean 类型

7、Reflect.has()

作用与 in 操作符 相同。

  • target:目标对象
  • key:匹配的键
  • 返回值: boolean

8、Reflect.defineProperty()

  • 注意Reflect 里没有 Object.defineProperties() 方法

静态方法 Reflect.defineProperty() 基本等同于 Object.defineProperty() 方法,唯一不同是返回 Boolean 值。

9、Reflect.deleteProperty()

  • 注意这里参数 key 时 字符串类型的 变量传入的话会返回 true
  1. Reflect.deleteProperty 删除对象的属性的时候

    当使用 delete 操作符 修改一个密封,冻结或者 属性设置了 configurable:false 的时候,在严格模式 ”use strict“ 下会报错,导致后面的代码都执行失败

    Reflect.deleteProperty 不会异常,会返回一个 boolean

三、proxy 和 Reflect 结合使用的时候的好处

Section titled “三、proxy 和 Reflect 结合使用的时候的好处”
  1. Refect 会返回 boolean 值,可以判断本次修改是否成功

  2. Proxy 中的作用

    解析:

    ​ 语法:obj.name 这个是js的语法,有内部控制,使用隐式绑定来决定 this 的指向

    reflect:直接使用语法底层的调用方法来进行调用

    作用:

    • 正常调用没什么区别,但是当 obj 中的属性存在 “访问器” 并且 使用 proxy 监听对象的话,会存在不能监听到 “访问器” 中对使用当前对象的操作
    • 捕获器中使用 js 语法 return target[key] 调用访问器的话 this 指向当前的对象,就会指向代理对象。使用 Relfect 的话可以通过传入 receiver(receiver 就是外层 Proxy 对象) 让 this 重新指向代理对象,这样就可以监听了
    • 有这样需求的话可以使用 Reflect.set(target, key, val, receiver)
    const obj = {
    _name: "why",
    set name(newValue) {
    console.log("this:", this) // 默认是obj
    //这样的话this 会执行proxy,proxy会对访问器中的修改对象数据的话也会进行监听
    this._name = newValue
    },
    get name() {
    return this._name
    }
    }

Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。

  1. 主要用于异步计算
  2. 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
  3. 代码风格,容易理解,便于维护
  1. 统一异步请求的规范

    对于不同的人、不同的框架设计出来的方案是不同的,那么我们必须耐心去看别人的源码或者文档,以便可以理解它 这个异步请求函数到底怎么用;

  2. 解决回调地狱

    多个异步等待合并便于解决

  1. pending:(初始状),既没有被兑现,也没有被拒绝,

    当执行 Executor(执行promise传入的回调参数的时候)的时候处于这个状态

  2. fulfilled: (兑现)意味着操作成功完成;

    执行了resolve时,处于该状态,Promise已经被兑现;

  3. rejected:(已拒绝)意味着操作失败;

    执行了 reject 时,处于该状态,Promise已经被拒绝;

  • 注意:一旦状态被确定下来,Promise的状态会被 锁死,该Promise的状态是不可更改的

    就是不能同时 调用多次 resolvereject, 更不能同时调用

    throw new Error 抛出异常的时候也不能方法确定状态方法的后面

3、resolve参数不同值的区别(重要)

Section titled “3、resolve参数不同值的区别(重要)”

3.1、普通值

  • 情况一:如果 resolve 传入一个普通的值或者对象,那么这个值会作为 then 回调的参数;

    多使用结构 通常情况下 resolve只有一个参数,需要传入多个对象的话,实参就使用对象类型,这时结构就显得非常重要了

promise.then(({data, flag}) => { console.log(data, flag) })

3.2、传入新的 Promise 对象

  • 情况二:如果 resolve 中传入的是另外一个 Promise那么这个新 Promise 会决定原 Promise 的状态:
const promise1 = new Promise((resolve, reject) => {
//如果resolve传入的 是 Promise 对象的话 当前状态会交由传入 Promise 对象处理
resolve(new Promise((resolve, reject) => {
resolve("传入了新的:Promise")
}))
})
promise1.then(res => {
console.log("lisi", res)
})

3.3、传入包含 then 方法的对象

  • 情况三:如果 resolve 中传入的是一个对象,并且这个对象有实现 then 方法,那么会执行该then 方法,并且根据 then 方法的结果来决定Promise的状态:
const promise = new Promise((resolve, reject) => {
resolve({
name: "zhangsan",
//如果对象中存在then函数的话,就会将当前状态交给对象中then处理
then: function(resovle, reject) {
//千万别写错名
resovle("张三对象中的 then")
// reject("张三对象中的 then")
}
})
})
promise.then(data => {
console.log("zhang receiver", data)
})
  1. then 方法

then方法是Promise对象上的一个方法(实例方法)

  • 放在Promise的原型上的 Promise.prototype.then

  • then方法接受两个参数

    1. fulfilled(兑现) 的回调函数:当状态变成 fulfilled 时会回调的函数;
    2. reject 的回调函数:当状态变成 rejected 时会回调的函数;

1.1. then 方法返回值

  1. then 方法返回的是一个新的 promise 对象

  2. 新的 Promise 对象 在没有抛出异常的情况下,默认调用的是 resolve 函数 进入 fulfilled 状态

    所以可以链式调用 then 方法,因为每次返回的都是新的 Promise 实例对象,由于 then 内没有发生异常的时候默认确定的状态是 fulfilled这样会发生所有链式调用的 then 方法在没有发生异常的时候都会被执行

    //then 返回的 是一个新的 Promise 的是实例对象 , 在默认没有出现错误异常的i情况下会默认执行 resolve
    const promise = new Promise((resolve, reject) => {
    resolve("fulfilled~")
    })
    promise.then(data => {
    // console.log("第一个then", data)
    // 返回的值 会被当作新 Promise 实例对象的resolve的参数,then 回调函数的参数类型的规范就等于resolve的参数规范
    return "zhangsan"
    }).then(data => {
    //这里默认会被调用
    console.log("默认执行的 then", "没有返回值的时候data是undefined", data)
    })
  3. then 方法回调函数的返回值,作为 新的Promise 中resolve 方法的参数

  4. 所以 then 回调函数的返回值,和 resolve 的参数规则一致

  • then方法返回的Promise的状态

    1. 当then方法中的回调函数本身在执行的时候那么它处于 pending 状态

    2. 当then方法中的回调函数返回一个结果时,那么它 处于fulfilled状态 ,并且会将结果作为resolve的参数

      ✓ 参数类型一:返回一个普通的值; ✓ 参数类型二:返回一个Promise;

      这里的新的 new Promise() 。一定要添加,构造函数的参数 executer

      const promise = new Promise((resolve, reject) => {
      reject("zhangsan, err")
      })
      promise.catch(err => {
      console.log(err)
      return new Promise((resolve, reject) => {
      //交给新的 Promis 处理
      })
      })

      ✓ 参数类型三:返回一个thenable值;

      promise.then((data) => {
      return {
      //这个就叫 thenable
      then: function(resolve) {
      resolve(data + "My name is lisi")
      }
      }
      })
    3. 当then方法抛出一个异常时,那么它处于reject状态

1.2. then方法 – 多次调用

一个Promise的then方法是可以被多次调用的:

  • 当Promise的状态变成 fulfilled(兑现状态) 的时候,这些回调函数都会被执行;
  1. catch 方法

catch方法也是Promise对象上的一个方法(实例方法),也是放在Promise的原型上的 Promise.prototype.catch

  • catch的返回值:catch方法也是会返回一个Promise对象的所以和 then 的返回值规则相同,catch方法后面我们可以继续调用then方法或者catch方法

  • 还有一个就是当catch后面还有catch方法返回值也会做下一个 then的参数

    const promise = new Promise((resolve, reject) => {
    reject("当前状态 rejected")
    })
    promise.catch(err => {
    return err
    }).then(data => {
    console.log(data)
    throw new Error(data)
    }).catch(err => {
    console.log(err, "第二个 catch")
    return err
    }).catch(err => {
    console.log("最后一个catch", err)
    //上面的返回值会调用整个then
    }).then(data => {
    console.log("最后一个then", data)
    })

    还是同样的区分是否 throw 抛出异常,普通对象的话直接 找下一层的 then 了

    • 参数一:普通值,对象
    • 参数二:thenable 前面有介绍和 then 中的一样
    • 参数三:也能return 一个Promise 对象
  • 如果我们希望后续继续执行catch,那么需要抛出一个异常

  • 触发 Catch

    这里调用 reject 或者抛出一个异常 , 都会触发 catch 方法。

    const promise = new Promise((resolve, reject) => {
    // reject("异常了")
    throw new Error()
    })
    promise.catch(err => {
    // throw new Error(err) 正常情况下这里不抛出异常的话, 不会往下面传异常的
    console.log("first")
    }).catch(err => {
    //err 会将异常对象传过来
    console.log("第二个Catch", err)
    //可以直接抛出
    throw err
    })
  • Promise的 catch 方法也是可以被多次调用的

    当Promise的状态变成reject的时候,这些回调函数都会被执行;

    const promise = new Promise((resolve, reject) => {
    reject("异常了")
    // throw new Error()
    })
    promise.catch(err => {
    // throw new Error(err)
    console.log("1")
    })
    promise.catch(err => {
    // throw new Error(err)
    console.log("2")
    })
    promise.catch(err => {
    // throw new Error(err)
    console.log("3")
    })
  1. finally方法 [ES9新增]

finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行 的代码。

  • 效果,就像java 中的 finally 一样,使用 io流 的时候在 finally 里关闭流
  • finally方法是不接收参数的,因为无论前面是fulfilled状态,还是rejected状态,它都会执行

5.1、Promise.resolve/reject

5.1.1、resolve 方法

Promise.resolve的用法相当于new Promise,并且执行resolve操作

  • 使用场景:当我们已经有一个结果了且想用 promise 给返回出去

  • 参数形态:和 new Promiseresolve 的参数规范一致

    1. 普通值
    2. thenable
    3. Promise 的 实例
    //当我们已经有结果的情况下 想要使用 Promise 可以h使用 Promise 的静态方法 resovle
    arr = ["zhangsan", "lisi", "wnagwu"]
    //这里放在异步方法内
    const promise = Promise.resolve(arr)
    //一般会分开来用
    promise.then(data => {
    setTimeout((value) => {
    console.log(value)
    }, 1000, data);
    })

5.1.2、reject方法

reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态。

  • 用法也能猜到,和 new Promise 中的 reject 方法一致

  • Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的。

  • 使用方法和 resolve 相同,在没有捕获的 catch 方法的时候会直接抛出异常

    //Promise.reject
    const p = Promise.reject(arr)
    p.then(data => {
    console.log(data)
    //这里不捕获的话 抛出uncaught (in promise)
    }).catch(err => {
    console.log(err)
    })

5.2、Promise.all

它的作用是将多个Promise包裹在一起形成一个新的Promise;

作用: Promise.all的作用: 包裹多个Promise 生成一个新的Promise,监听多个 Promise的状态, 当所有的promise 都确定状态之后,如果全部为 fulfilled,就会确定promise 的状态为 fulfilled,如果有一个promise状态为 rejected 就会确定新Promise的状态为rejected,且返回第一个reject

  • 参数:@@iterator 可迭代对象

  • 返回值:promise对象

  • 特点:

    1. 一定会等待所有的 Promise 的进入 fulfilled 状态的时候才会执行新的Promise

    2. 新的Promise状态为fulfilled的话,会将所有Promise的返回值组成一个数组返回到新的 Promise

    3. 如果有一个Promise 的状态是 rejected 的话那么回立刻确定新的Promise 的状态为 rejected 不会等待其他的promise完成 并且会将第一个reject的返回值作为参数;

    // Promise.all的作用: 包裹多个Promise 生成一个新的Promise,监听多个 Promise的状态, 当所有的promise 都确定状态之后,如果全部为 fulfilled,就会确定promise 的状态为 fulfilled,如果有一个promise状态为 rejected 就会确定新Promise的状态为rejected,且返回第一个reject
    const p = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve("第一个 promise")
    }, 1000)
    })
    const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve("第三个 promise")
    }, 2000)
    })
    const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve("第二个 promise")
    }, 3000)
    })
    const promise = Promise.all([p, p2, p3])
    promise.then( data => {
    //这里的data是个数组是所有 promise 的fulfilled 的状态集合
    const [p1, p2, p3] = data
    console.log(p1, p2, p3)
    }).catch(err => {
    //如果 promise 集合中有一个状态为 rejected 的话直接回调用这个
    console.log(err)
    })

5.3、Promise.allSettled 方法

all方法有一个特点:当有其中一个Promise变成reject状态时,**新Promise就会立即变成对应的reject状态。并且返回第一个 reject **

  • 这样对于其他的 Promise ,依然处于 pending 状态的 Promise,我们是获取不到对应的结果的;
  • 参数:@@iterator 可迭代对象

  • 返回值:promise对象

  • allSettled 方法是可以获取到 所有 Promise 的状态 [ 数组类型 ],最后作为新的 Promise resolve的参数

  • 是[ES11] ES2020中新增的 API

    该方法会等待在所有的Promise 确定状态之后调用(settled),无论是fulfilled,还是rejected时,都有最终的状态; 并且这个Promise的结果一定是fulfilled的

    //会返回每个promise 的状态 和 参数
    {
    0: {status: 'fulfilled', value: '第一个promise'},
    1: {status: 'rejected', reason: '第一个promise'},
    2: {status: 'fulfilled', value: '第一个promise'}
    }
    const p1 = new Promise((resolve, reject) => {
    resolve("第一个promise")
    })
    const p2 = new Promise((resolve, reject) => {
    reject("第一个promise")
    })
    const p3 = new Promise((resolve, reject) => {
    resolve("第一个promise")
    })
    const promise = Promise.allSettled([p1, p2, p3])
    promise.then(data => {
    //返回的状态一定是 fulfilled
    console.log(data)
    }).catch(err => {
    //不会调用catch
    console.log(err)
    })

5.4、race方法(竞赛)

就是多个Promise相互竞争,谁先有结果,那么就使用谁的结果;

  • 参数:@@iterator 可迭代对象

  • 返回值:promise对象

  • race 会返回第一个确定状态的 promise 的结果 不会关注是 reject 还是 fulfilled,都会确定 race Promise 的状态

const p = new Promise((resolve, reject) => {
setTimeout(reject("第1个"), 1000)
})
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve("第2个"), 2000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(resolve("第3个"), 3000)
})
const promise = Promise.race([p, p1, p2])
//race 会返回第一个确定状态的 promise 不会关注是 reject 还是 fulfilled
promise.then(data => {
console.log("fulfilled", data)
}).catch(err => {
//这里会调用catch
console.log("err", err)
})

5.5、any [ES12新增]

会返回第一个状态为 Fulfilled Promise 的结果,来确定,新的 Promise 的状态

  • 参数:@@iterator 可迭代对象

  • 返回值:promise对象

  • 如果所有的 Promise 都是 reject 的,那么也会等到所有的 Promise 都变成 rejected 状态;

  • 如果所有的Promise都是reject的,那么会报一个 AggregateError 的错误

const p = new Promise((resolve, reject) => {
setTimeout(reject("第1个"), 1000)
})
const p1 = new Promise((resolve, reject) => {
setTimeout(reject("第2个"), 2000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(reject("第3个"), 3000)
})
const promise = Promise.any([p, p1, p2])
//any 方法会返回第一个状态确定为 fulfilled 的promise 的结果 最先确定状态 为reject 忽略
//全部为 reject的话 会报异常
promise.then(data => {
console.log("fulfilled", data)
}).catch(err => {
console.log("err的值", err)
})
// ❌ 旧写法:需要定义外部变量来“接住”内部的控制权
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// 然后在某个地方调用
resolve("成功!");
async function startScanning() {
// ✅ 新写法:解构出 promise 本身和它的控制开关
const { promise, resolve, reject } = Promise.withResolvers();
// 假设这是一个弹窗组件或扫码库
const scanner = new BarcodeScanner();
// 监听扫描事件
scanner.onDetected((result) => {
resolve(result); // 扫码成功,让 promise 状态变更为成功
scanner.stop();
});
// 监听取消/报错
scanner.onCancel(() => {
reject(new Error("用户取消了扫码"));
});
// 开启摄像头
scanner.start();
// 等待结果(像同步代码一样直观)
return await promise;
}
  • 什么时候确实“多余” 如果你已经用 async/await + try/catch,并且调用链统一是 Promise 风格,Promise.try 往往没必要。

  • 当你要包一段“可能同步、可能异步、可能抛错”的第三方回调,并且想统一成 Promise 链时,Promise.try 会更顺手。

迭代器(iterator)对象类型,可以使用户在容器对象(container,例如链表或数组)上 遍历 的对象,使用该接口无需关心对象的内部实现细节。

  • 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等
  • 迭代器是帮助我们对某个数据结构进行遍历的对象。

JavaScript中,迭代器也是一个具体的对象,由迭代器函数进行创建,这个对象需要符合迭代器协议(iterator protocol)

  • 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式;
  1. 创建一个函数 返回值为一个对象(将要实现的迭代器对象)

  2. 返回的 iterator 对象 要有 一个特定的 next 方法

  3. 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:

    • done(boolean)

      如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)相反如果没有可迭代的值则为 true, done为true 这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。

    • value

      迭代器返回的值。done 为 true 时可省略

  4. 当一个可迭代对象在迭代的过程中被中断的话,会触发 return 方法(注意:这里是方法)

  • 根据以上规范创建迭代器

    const arr = ["zhangsan", "lisi", "wangwu"]
    function foo(arr) {
    let index = 0;
    if(index < arr.length){
    return {
    next: () => {
    return {done: false, value: arr[index++]}
    }
    }
    } else {
    return {done: true}
    }
    }
    //给根据 迭代器对象的规范 创建迭代器对象
    const iterator = foo(arr)
    console.log(iterator.next())
    console.log(iterator.next())
    console.log(iterator.next())
    console.log(iterator.next())
    console.log(iterator.next())

当一个对象实现了iterable protocol协议时它就是一个可迭代对象

  • 这个对象的要求是必须实现 @@iterator 函数,在代码中我们使用 Symbol.iterator 访问该属性;
  • @@iterator-[Symbol.iterator] 函数要返回一个迭代器对象 (重点)
2.1、可迭代对象的使用场景和优势
Section titled “2.1、可迭代对象的使用场景和优势”
  1. 可以进行 for… of 迭代遍历

    for…of 操作时,其实就会调用它的 @@iterator 方法;

  2. 在许多内置的 API 当中, 传入的参数都要求是可迭代的

    • 对象构造

      new Map([Iterable])

      new WeakMap([iterable])

      new Set([iterable])

      new WeakSet([iterable]);

    • Api

      Promise.all(iterable)Promise.race(iterable)Array.from(iterable);

  • 根据可迭代对象的规范 自定义字面量可迭代对象那个
//创建一个可迭代对象
const info = {
arr: ["zhangsan", "lisi", "wangwu"],
[Symbol.iterator]: function() {
//注意这里的index 不要放在 next 方法里会死循环
let index = 0;
return {
next: () => {
if(index < this.arr.length){
return {done: false, value: this.arr[index++]}
} else {
return {done: true}
}
}
}
}
}
  • 根据 可迭代对象的规范创建 class 可迭代对象的构造器
class Person {
constructor(name, addr, age, arr) {
this.name = name;
this.addr = addr;
this.age = age
this.arr = arr
}
//放在对象的原型上面这样的话创建的对象都是可迭代对象
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if(index < this.arr.length) {
return {done: false, value: this.arr[index++]}
} else {
return {done: true, value: this.arr[index++]}
}
}
}
}
}
//继承下来的也是可迭代对象
class Student extends Person {
constructor(name, addr, age, arr) {
super(name, addr, age, arr)
}
}
const arr = [
{bland: "weilai"},
{bland: "mazida"},
{bland: "changan"},
]
const foo = new Student("张三", "天津市", 13, arr)
for (const item of foo) {
console.log(item)
}

迭代器在某些情况下会在没有完全迭代完的情况下中断

  • 迭代器中断的情况

    1. 遍历的过程中通breakreturnthrow中断了循环操作

      通常中断都是使用 break,return 在浏览器运行的js中for循环中会报异常,但是在node 中则不会异常

    2. 解构的时候,没有解构所有的值

  • for...of 内部用的就是迭代器

  • 当一个对象在迭代的时候被上面的情况被中断了的话,会默认执行迭代器对象中的 return 方法

    const obj = {
    name: "zhangsan",
    addr: "天津",
    friend: ["lisi", "wangwu", "yanliu"],
    [Symbol.iterator]: function() {
    let index = 0
    return {
    next: () => {
    //
    if(index < this.friend.length) {
    return {done: false, value: this.friend[index++]}
    } else {
    return {done: true}
    }
    },
    return:()=> {
    //当对象迭代的时候被中断就会触发这个函数
    console.log("已经被中断")
    //这里要有返回值,否则会报错
    return {done: true}
    }
    }
    }
    }
    for (const item of obj) {
    console.log(item)
    if(item == "wangwu") {
    //通常都是使用break 来中断迭代器的
    break
    }
    }
  1. 当我们,想要遍历对象当中存在的,某些特殊属性的时候或者将某些属性作为可迭代对象的参数,这样就可以在对象的内部生成迭代器

    //迭代器的使用场景
    class Person {
    arr = ["zhangsan", "lisi", "wangwu"]
    obj = {nick: "Heimao县", years: "1998"}
    constructor(bland, price, score, info) {
    this.bland = bland
    this.price = price
    this.score = score
    this.info = info
    }
    *[Symbol.iterator]() {
    // yield* Object.keys(this)
    // yield* Object.values(this)
    // yield* Object.entries(this)
    //或者遍历 特殊的属性
    // yield* Object.entries(this.obj)
    yield* this.arr
    }
    }
    const person = new Person("奔驰", 344444, 5, "4H")
    for (const item of person) {
    console.log(item)
    }

第十三节、generator 生成器迭代器的语法糖(异步函数前提知识)

Section titled “第十三节、generator 生成器迭代器的语法糖(异步函数前提知识)”

生成器是ES6中新增的一种 函数控制 、使用的方案,它可以让我们更加灵活的控制函数什么时候 继续执行、暂停执行 等。

用生成器把步骤拆成可恢复流程(偏框架层)

  • 平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。

  • 而生成器函数也可以做到

  • 生成器函数的概念:是一个特殊的函数,可以通过 yield 流动点控制代码的流程。

  • 生成器概念:生成器是一个特殊的迭代器,通过生成器函数创建,通过next,来恢复代码的执行

生成器函数也是一个函数,但是和普通的函数有一些区别

  1. 首先,生成器函数需要在function的后面加一个符号:*

    **在 class 中,的函数语法糖增强写法 中定义 生成器函数 要在函数的前面加 **

  2. 其次,生成器函数可以通过yield关键字来控制函数的执行流程:

  3. 最后,生成器函数的返回值是一个Generator(生成器)

    • 生成器事实上是一种特殊的迭代器(MDN文档介绍);
    • MDN:Instead, they return a special type of iterator, called a Generator.

使用生成函数,默认返回一个生成器对象,用来控制生成器函数 内部的执行时机

yield 关键字用来暂停和恢复一个生成器函数((function*遗留的生成器函数)。

  1. 在迭代器函数当中使用 yield 关键字会暂停当前函数的执行,可是使用生成器对象中的 next() 方法来恢复代码的执行

  2. 而 yield 关键字 可以给每个分段的代码 传入参数,也可以具有返回值

    由于生成器是一个特殊的迭代器,迭代器的next 会有标准的返回值格式,

    //1.这里的参数 num 是在执行第一段代码的参数
    //2.这里声明的生成器函数,要在 function 的后面添加 *
    function* foo(num) {
    console.log("第1段代码", num)
    //3.这里的num1 会被当成下一段代码执行前通过 next() 方法传入的参数,
    //4.返回值的格式 为迭代器返回的标准格式{done: false, value: yield 的返回的值}
    const num1 = yield "第一段代码返回值";
    console.log("第2段代码", num1)
    const num2 = yield "第二段代码返回值";
    console.log("第3段代码", num2)
    //5.最后没有return 默认返回undefined
    }
    const generator = foo(1)
    console.log(generator.next())
    console.log(generator.next(2))
    //这里因为第三段代码没有 yield 声明返回值 所以返回的是 {done: true, value: undefined}
    console.log(generator.next(3))
    //没有第四段代码 所以也是 {done: true, value: undefined}
    console.log(generator.next(4))

如果需要生成器执行的话,需要调用 next 方法

//声明一个生成器函数 要加* 否则使用 yield 声明返回值的时候会报错
function* foo(value) {
console.log("zhangsan", value)
const val1 = yield "return zhangsan"
//这里也能读取第一传入的参数 正常现象
console.log("lisi", val1, value)
const val2 = yield "return lisi"
console.log("wangwu", val2)
yield "return wangwu"
}
//创建生成器,传入第一段代码要使用的参数
const genterator = foo(1)
genterator.next()
//这里传入的参数是给执行下段代码传入的
genterator.next(2)
genterator.next(3)
genterator.next(3)

用来中断函数,接收下一次需要执行代码的参数,但是执行完下一次代码之后就会中断

  • 中断之后 调用 next() 方法返回的值 都是 {value: undefined, done: true}
  • 当执行到*return(value) 方法的时候会立即终止函数*,后面的都不会执行相当于在函数的内部插入了一个 return 语句, 返回值就是return 的参数
//生成器的中断
function* foo(num) {
console.log("第1段代码", num)
const num1 = yield "第一段代码返回值";
console.log("第2段代码", num1)
const num2 = yield "第二段代码返回值";
console.log("第3段代码", num2)
}
const generator = foo("开始执行")
console.log(generator.next())
//output {value: '第一段代码返回值', done: false}
console.log(generator.return("第二段开始"))
//output {value: '第二段开始', done: true} done 的值会变为true 代表的迭代完成,
//之后在调用next 就是 undefined 了
console.log(generator.next("第三段开始"))
//output {value: undefined, done: true}

用来在函数内部,抛出一个异常,来中断函数的执行

  • 抛出异常之后,下一段代码就不会执行 并且抛出异常
function* bar(num) {
console.log("第1段代码", num)
const num1 = yield "第一段代码返回值";
//是在第一段代码执行完,抛出异常,所以在本行代码的后面所有的代码段都不会执行
console.log("第2段代码", num1)
const num2 = yield "第二段代码返回值";
console.log("第3段代码", num2)
}
const generator2 = bar("开始执行")
console.log(generator2.next())
console.log(generator2.throw("第二段开始"))

我们发现生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:

//生成器代替迭代器对象
const obj = {
arr: ["zhagnsan", "lisi", "wnagwu"],
[Symbol.iterator]: function* foo() {
for(let i = 0; i < this.arr.length; i++) {
// console.log(this.arr[i])
yield this.arr[i]
}
}
}
for (const temp of obj) {
console.log(temp)
}

yield* 来生产一个可迭代对象,的迭代器的

  • (重要)相当于是一种yield的语法糖,当yield* 后面的返回值如果是一个可迭代对象的话就会依次迭代这个可迭代对象,每次迭代其中的一个值;

  • 也可以说 yield* 的后面只能放 可迭代对象 否则会报错

    Uncaught TypeError: yield* (intermediate value) is not iterabl

class Person {
constructor(name, age, addr, arr) {
this.name = name;
this.age = age;
this.addr = addr;
this.arr = arr
}
//是用es6 类中函数语法糖增强写法的话要在前面加 *
*[Symbol.iterator]() {
//yield 的语法糖写法
yield* this.arr
}
}
const person = new Person("zhagnsan", "lisi", "wangwu", ["天津","上海", "广州"])
for (const item of person) {
console.log(item)
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<button class="btn">trigger</button>
</body>
<script>
const steps = [
{ id: "login", title: "登录", },
{ id: "fetch", title: "拉取数据", },
{ id: "upload", title: "上传素材", },
{ id: "publish", title: "发布", },
];
const handles = function* () {
yield* steps;
}();
document.querySelector(".btn").addEventListener("click", function () {
console.log(handles.next());
})
</script>
</html>
  • 解决回调地狱问题
  • 要点:返回 promise 实例对象
//定义异步请求函数
function request(url) {
return new Promise((resolve, reject) => {
setTimeout((data) => {
resolve(data)
}, 1000, url);
})
}
request("zhangsan").then(data => {
console.log(data)
return request(data + "lisi")
}).then(data => {
console.log(data)
return request(data + "wangwu")
}).then(data => {
console.log(data)
})

5、generator 和 promise 的异步处理方案

Section titled “5、generator 和 promise 的异步处理方案”

案例需求:我们需要向服务器发送网络请求获取数据,

一共需要发送三次请求; 第二次的请求url依赖于第一次的结果; 第三次的请求url依赖于第二次的结果; 依次类推;

  • 要点:理解 generator函数,和 自动化执行函数
//第三种方案 使用generator
function* foo(url) {
const data = yield request(url);
const data1 = yield request(data + "lisi");
const data2 = yield request(data1 + "wangwu")
}
const generator = foo("张三")
//自定义自动调用函数(这个还是有点用的)
function autoExe(generator) {
function exe(res) {
let {done, value} = generator.next(res)
if(done) return
value.then(data => {
console.log(data)
exe(data)
})
}
exe()
}
autoExe(generator)
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Generator 线性流程示例</title>
<style>
body { font-family: Arial, sans-serif; padding: 16px; line-height: 1.6; }
.row { margin: 8px 0; }
button { margin-right: 8px; margin-bottom: 8px; }
input { margin-right: 8px; padding: 4px 6px; }
.box { border: 1px solid #ddd; border-radius: 8px; padding: 12px; margin-top: 12px; }
.ok { color: #0a7; }
.err { color: #d33; }
pre { background: #f7f7f7; padding: 10px; border-radius: 6px; max-height: 220px; overflow: auto; }
</style>
</head>
<body>
<h2>生成器流程:向导 + 重试 + 暂停恢复</h2>
<div class="row">
<button id="btnStart">开始</button>
<button id="btnPrev">上一步</button>
<button id="btnNext">下一步</button>
<button id="btnRun">自动运行到结束</button>
<button id="btnPause">暂停</button>
<button id="btnResume">恢复</button>
<button id="btnRetry">重试当前失败步骤</button>
<button id="btnReset">重置</button>
</div>
<div class="row">
<input id="jumpIndex" type="number" placeholder="按 index 跳转" />
<button id="btnJumpIndex">跳转 index</button>
<input id="jumpId" type="text" placeholder="按 id 跳转,如 upload" />
<button id="btnJumpId">跳转 id</button>
</div>
<div class="box">
<div>当前步骤:<b id="currentStep">-</b></div>
<div>状态:<b id="status">idle</b></div>
<div>最后结果:<span id="lastResult">-</span></div>
</div>
<div class="box">
<div><b>操作历史</b></div>
<pre id="history"></pre>
</div>
<script>
// ===== 1) 步骤定义(业务流程) =====
const steps = [
{ id: "login", title: "登录", fn: mockTask("登录", 0.15) },
{ id: "fetch", title: "拉取数据", fn: mockTask("拉取数据", 0.30) },
{ id: "upload", title: "上传素材", fn: mockTask("上传素材", 0.40) },
{ id: "publish", title: "发布", fn: mockTask("发布", 0.20) },
];
function mockTask(name, failRate) {
return function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < failRate) {
reject(new Error(name + " 失败(模拟)"));
} else {
resolve(name + " 成功");
}
}, 300);
});
};
}
// ===== 2) 生成器:只负责“线性吐出步骤” =====
function* stepGenerator(stepList) {
for (let i = 0; i < stepList.length; i++) {
yield { index: i, step: stepList[i] };
}
}
// ===== 3) Runner:负责状态、跳转、历史、重试、暂停恢复 =====
class FlowRunner {
constructor(stepList) {
this.steps = stepList;
this.history = [];
this.reset();
}
reset() {
this.currentIndex = -1;
this.current = null;
this.lastError = null;
this.paused = false;
this.running = false;
this.done = false;
this.iter = stepGenerator(this.steps);
this.pointerMap = new Map(this.steps.map((s, i) => [s.id, i]));
this.pushHistory("reset", { msg: "流程已重置" });
this.render();
}
pushHistory(type, payload) {
this.history.push({
at: new Date().toISOString(),
type,
...payload,
});
this.renderHistory();
}
getState() {
return {
currentIndex: this.currentIndex,
currentId: this.current ? this.current.step.id : null,
paused: this.paused,
running: this.running,
done: this.done,
lastError: this.lastError ? this.lastError.message : null,
history: this.history.slice(),
};
}
restore(state) {
this.currentIndex = typeof state.currentIndex === "number" ? state.currentIndex : -1;
this.paused = !!state.paused;
this.running = false;
this.done = false;
this.lastError = state.lastError ? new Error(state.lastError) : null;
this.iter = stepGenerator(this.steps);
for (let i = 0; i <= this.currentIndex; i++) {
this.iter.next(); // 让生成器游标前进到当前步骤
}
if (this.currentIndex >= 0 && this.currentIndex < this.steps.length) {
this.current = { index: this.currentIndex, step: this.steps[this.currentIndex] };
} else {
this.current = null;
}
this.pushHistory("restore", { msg: "从快照恢复" });
this.render();
}
async next() {
if (this.done) return;
if (this.paused) {
this.pushHistory("blocked", { msg: "已暂停,next 被阻止" });
return;
}
const ret = this.iter.next();
if (ret.done) {
this.done = true;
this.current = null;
this.pushHistory("done", { msg: "流程结束" });
this.render();
return;
}
this.current = ret.value;
this.currentIndex = ret.value.index;
await this.runCurrent("next");
}
async prev() {
// 生成器天生不支持回退,这里通过重建迭代器+重放到上一位实现
const target = this.currentIndex - 1;
if (target < 0) {
this.pushHistory("prev", { msg: "已在第一步,无法后退" });
return;
}
this.jumpToIndex(target);
this.pushHistory("prev", { index: this.currentIndex, id: this.current.step.id });
this.render();
}
jumpToIndex(index) {
if (index < 0 || index >= this.steps.length) {
this.pushHistory("jump-fail", { msg: "index 越界", index });
return;
}
this.iter = stepGenerator(this.steps);
let node = null;
for (let i = 0; i <= index; i++) {
node = this.iter.next().value;
}
this.current = node;
this.currentIndex = index;
this.done = false;
this.lastError = null;
this.pushHistory("jump-index", { index, id: node.step.id });
this.render();
}
jumpToId(id) {
if (!this.pointerMap.has(id)) {
this.pushHistory("jump-fail", { msg: "id 不存在", id });
return;
}
this.jumpToIndex(this.pointerMap.get(id));
this.pushHistory("jump-id", { id });
}
pause() {
this.paused = true;
this.pushHistory("pause", { msg: "流程已暂停" });
this.render();
}
resume() {
this.paused = false;
this.pushHistory("resume", { msg: "流程已恢复" });
this.render();
}
async retryCurrent() {
if (!this.current) {
this.pushHistory("retry-fail", { msg: "当前无可重试步骤" });
return;
}
await this.runCurrent("retry");
}
async runCurrent(trigger) {
if (!this.current) return;
this.running = true;
this.lastError = null;
this.render();
const { index, step } = this.current;
this.pushHistory("run", { trigger, index, id: step.id, title: step.title });
try {
const result = await step.fn();
this.pushHistory("success", { index, id: step.id, result });
setLastResult(result, false);
} catch (err) {
this.lastError = err;
this.pushHistory("error", { index, id: step.id, error: err.message });
setLastResult(err.message, true);
} finally {
this.running = false;
this.render();
}
}
async runToEnd() {
this.pushHistory("run-to-end", { msg: "开始自动运行" });
while (!this.done && !this.paused) {
await this.next();
if (this.lastError) break; // 出错停住,等待人工 retry
}
}
render() {
const stepEl = document.getElementById("currentStep");
const statusEl = document.getElementById("status");
stepEl.textContent = this.current
? `${this.current.index} - ${this.current.step.id} (${this.current.step.title})`
: "-";
let st = "idle";
if (this.done) st = "done";
else if (this.running) st = "running";
else if (this.paused) st = "paused";
else if (this.lastError) st = "error";
else if (this.current) st = "ready";
statusEl.textContent = st;
}
renderHistory() {
document.getElementById("history").textContent =
this.history.map((h) => JSON.stringify(h)).join("\n");
}
}
// ===== 4) UI 绑定 =====
const runner = new FlowRunner(steps);
function setLastResult(msg, isErr) {
const el = document.getElementById("lastResult");
el.textContent = msg;
el.className = isErr ? "err" : "ok";
}
document.getElementById("btnStart").onclick = () => runner.next();
document.getElementById("btnNext").onclick = () => runner.next();
document.getElementById("btnPrev").onclick = () => runner.prev();
document.getElementById("btnRun").onclick = () => runner.runToEnd();
document.getElementById("btnPause").onclick = () => runner.pause();
document.getElementById("btnResume").onclick = () => runner.resume();
document.getElementById("btnRetry").onclick = () => runner.retryCurrent();
document.getElementById("btnReset").onclick = () => runner.reset();
document.getElementById("btnJumpIndex").onclick = () => {
const v = Number(document.getElementById("jumpIndex").value);
runner.jumpToIndex(v);
};
document.getElementById("btnJumpId").onclick = () => {
const id = document.getElementById("jumpId").value.trim();
runner.jumpToId(id);
};
// 示例:你可以把快照存起来,稍后恢复(演示“暂停恢复”)
// const snapshot = runner.getState();
// runner.restore(snapshot);
</script>
</body>
</html>

前面有记

  • 在ES7之前,计算数字的乘方需要通过 Math.pow 方法来完成。
  • 求幂运算符(**)返回将第一个操作数加到第二个操作数的幂的结果。它等效于Math.pow,不同之处在于它也接受 BigInts 作为操作数。

方法返回一个给定对象自身 可枚举属性键值对数组

  • for...in 的区别在于 in 操作符不仅会遍历对象中的属性也会便利 原型对象中可枚举的属性

  • 可以针对对象、数组、字符串进行操作

    for(let temp of Object.entries(obj)) {
    let [key, value] = temp
    console.log(key, value)
    }

    返回的 entries 是数组类型 key 和value 都是 元素值

    返回的 entries 是数组类型 key ** 是 字符串对应的索引位置**,而value,对应的是 字符

padStart 和 ..End 是 ES8中 新添加的 String 内置类中的实例方法,用来填充指定的字符的

  • 参数一:指定字符串填充之后的长度,不满足该长度会进行填充

  • 参数二:指定要填充的字符

    const str = "4201"
    const padStr = str.padStart(11, "*")
    console.log(padStr)

就是允许在定义函数的时候允许,最有一个形参的后面允许添加 逗号

//就是这个意思
function foo(arg1, arg2, arg3, ){}

这个静态方法也是es8新增的 获取对象属性的描述符

  • 返回值是对象所有属性的描述符对象
const descriptro = Object.getOwnPropertyDescriptors(obj)
  • 之前的只能获取单个属性的描述对象 (属性要是字符串类型的)

    const descriptro = Object.getOwnPropertyDescriptors(obj, "name")

async关键字用于声明一个异步函数,简单说,async 是通过 Promise包装异步任务。

  • 个人理解

    • async 可以理解为 声明一个 Primise 成功执行完默认是调用 resolve异常了调用 reject
    • async 声明的函数 就像是 Promise 的构造回调里面代码是同步的
  • 总结async 函数里在执行 await 之前的代码是同步的,想当于 Promisethen 方法要等待 fulfilled 状态才有结果,并且执行下面的代码,如果没有await 的话和普通的函数执行没有区别,真正异步的是里面的 awaitawait 返回值下面的代码加入微任务队列。可以说是当 async 里面存在 await 才是异步的

    在一起使用的时候,可以理解为,async 是异步函数,await 会等待 promise 的状态为 fulfilled 的时候才会执行下面代码。

  • 举例说明

async function foo() {
console.log("start");
//包括await这行代码之前的代码都是同步的,
const sss =await new Promise((resolve, reject) => resolve("sss"))
//"返回值" sss 和 "下面的代码"是异步的.
console.log(sss)
}
foo()
console.log("end");
/* 以上代码打印
start
await
end
sss
*/
  1. 异步函数的执行执行的时候和正常函数一致, Promise 的构造回调函数里面就是同步的

  2. 异步函数的返回值async 异步函数会默认返回一个promise 的实例对象

    就算没有返回值的时候也会调用 then 方法,但是 callback 回调函数的参数是undefined

    • return 的 值会被当作 resolve 的参数

      所以异步函数的返回值规范,Promise.resolve 的参数规范一致,有 普通值,thenablepromise实例对象

    • 如果在 async 函数中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为Promise的reject来传递

  1. 正常返回数据情况相当于调用 resolve
  2. 函数内出现异常或者抛出异常的话,相当与调用 reject

async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的。

  • await 关键字只能async 异步函数
  • 或者 顶层的 module 中使用 (听说的不确定)

await 的使用场景通常后面会跟一个表达式,该表达式会返回一个 promise 实例对象

  1. await 后面的表达式返回的是一个 promise 实例对象的时候,await会等到Promise的状态变成fulfilled状态之后继续执行异步函数;

    await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。

  2. await 后面的 Promise 表达式 状态为(rejected,或者抛出异常),await 表达式会把 Promise 的异常原因抛出。之后会有catch进行接收

    promise 中 throw 抛出异常 也算是执行 reject方法,所以会被catch()函数进行接收

    • 注意:异常之后后面的代码也不会执行。
  3. await 后面的表达式不是一个 Promiseawait 会把该值转换为已正常处理的 Promise,然后处理结果。

    个人理解:转化为以正常处理的Promise,“正常处理” 理解为调用了 resolve函数将该值传入

    因为 await 的值 和 resolve 的规范一致 普通值,thenable, promise

  • (重点理解): await 可以看作是后面 promise 的then方法等待确定状态之后执行。属于异步的,await后面的代码相当于 then 回调函数中的代码

    就是说 await 不关注后面 promise 里面执行的是异步代码 还是同步代码(因为Promise 的构造回调函数是同步的 里面有异步就执行异步就好了,正常返回 promiseawiat 只需要等待 它 resolve 或者 reject 确定状态就好) 只有promise 确定状态了 await 才会执行后面的代码,promise 里面要是异步,就等异步完事了确定状态之后,在调用 await 后面的代码

  • (重点) 总结:await 虽然会等待promisefulfilled状态 才会执行下面的代码,但实际上是异步的,像 then 方法一样 await 后面的代码会放到 微任务队列里,之后会等待执行

3、async 、await 使用场景和注意事项(主要)

Section titled “3、async 、await 使用场景和注意事项(主要)”
  1. 异步函数可以当作Promise 使用

  2. async 函数执行是同步的,真正异步的是 await

    重点:

    1、async 函数要放的异步函数的话一定要放在 await的后面的如果后面不是 Promise 的话要放在 thenable 里面

    2、就是直接返回一个 new Promise ,这样的话普通函数也可以,就不如直接创建一个Pormise 的实例对象

  3. 如果要放异步函数(不是promise)使用,且不是回调地狱的话。直接使用promise 比较方便

  • 主要的使用场景
    1. 个人觉的还是,在进行网络请求的时候,一定要等待有结果才能执行下面代码的时候
    2. 回调地狱

一、扩展运算符 …rest (Object spread operators)

Section titled “一、扩展运算符 …rest (Object spread operators)”

二、Promise finally:后续讲Promise讲解

Section titled “二、Promise finally:后续讲Promise讲解”

一、Array.prototype.flat(平坦的意思)

Section titled “一、Array.prototype.flat(平坦的意思)”

前面Array 内置类,有记具体使用方法

  • flat() 方法会按照一个 可指定的深度 递归遍历数组,并将遍历所有元素包括子数组中的元素合并为一个新数组返回

    就是将嵌套的数组结构,指定深度进行合并,最后返回合并后的新数组

  • 默认深度为 1

  • 内置内中有介绍这里就不在举例了

其实就是 经历一次map 之后返回 flat 的数组

  • 参数返回值,和内置类中高阶函数的规范一致
  • 详细可以看内置类, 这里只作为回顾

在ES8 中Object,可以将对象和数组、字符串转换为 entries 而ES10 新添加的 Object.fromEntries() 则会将 entries 转换成对象类型

  • 一般只用于对象 和 entries 之间的转化

  • 数组类型的 entries 转换完之后 索引会作为 key

    const arr = ["wangwu", "yanxiaoliu"]
    //转化entries
    console.log(Object.entries(arr))
    /*
    0: (2) ['0', 'wangwu']
    1: (2) ['1', 'yanxiaoliu'
    */
    //entries 转回对象
    console.log(Object.fromEntries(Object.entries(arr))
    //{0: 'wangwu', 1: 'yanxiaoliu'}
  • 字符串类型的 entries 转化

    因为字符串转换完之后,就变成了以单个字符分割的 长数组 所以转换为对象也是 索引会作为 key 单个字符作为value

字符串实例方法:去除开头的空格 或者 去除尾部的空格

const str = " zhangsan "
console.log(str.trimStart())

ES是新增的基本(原始) 数据类型

六、Optional catch binding:可以省略error

Section titled “六、Optional catch binding:可以省略error”

在早期的JavaScript中,我们不能正确的表示过大的数字

  • 也就是超过安全数字的话,表示的可能是不正确的。

    就是不准确的,不值的去摸索超过安全数字之后的显示规则。

    const num = Number.MAX_SAFE_INTEGER;
    console.log(num)
    //9007199254740991
    console.log(num + 100)
    //9007199254741092
    console.log(num + 10)
    //9007199254741000
  • 那么ES11中,引入了新的数据类型 BigInt ,用于表示大的整数

  • BigInt 的表示方法就是在字面量数值的后面加上n

  • 变量的话就是用 BigInt() 进行显示转换

    用来表示超过安全数字之后,正确的值

    console.log(BigInt(num) + 100n)

二、?? 空值合并操作符(Nullish Coalescing Operator)

Section titled “二、?? 空值合并操作符(Nullish Coalescing Operator)”

是一个逻辑运算符,当其 左侧操作数null or undefined时返回其右侧操作数,否则返回其左侧操作数。

注意:如果左侧操作符也是null或者undefined的话,会直接返回,左右两边的值都不确定话还时要使用三元运算符

  • 要确保右侧是一定有正确的值

会判断获取的元素或者属性,不同之处在于,在引用为空 (nullish ) (null 或者 undefined) 的情况下不会引起错误,也就是 nullundefined 都会返回的

  • 理解:简单来说就是,在获取或者调用指定的方法、属性。会进行判断,如果是无效的直接返回 undefined,采用短路运算

  • 这也是个短路运算符 ?.

    const obj = {
    name: "zhangsan",
    runing() {
    console.log("first")
    }
    }
    //这里调用的时候 要在runing 后面在加一层 因为?.runing 这一步是获取runging属性 否则不会调用返回 undefined
    obj?.runing?.()
  • 使用场景

    1. 一般用来调用对象中的函数,因为如果没有该函数还进行调用的话,会报错,这种 使用可选链会少一层是否有该函数的判断

      • 使用可选链不会抛出异常,会使用短路运算发现无效会返回undefined
    2. 当一个对象当中存在嵌套引用对象的时候,如果是一层 获取会返回 undefined 但是多一层就是 undefinded.second. 会报错使用可选链会减少代码的冗余

      • 使用可选链不会抛出异常,会使用短路运算发现无效会返回undefined
      //如果是一层 获取会返回undefined 但是多一层就是 undefinded.second 会报错,使用可选链会减少代码的冗余
      let nestedProp = obj.first && obj.first.second;

在ES11 之前JavaScript环境的全局对象,不同的环境获取的方式是不一样的

  • 比如在浏览器中可以通过this、window来获取;
  • 比如在Node中我们需要通过global来获取;

在ES11中对获取全局对象进行了统一的规范:globalThis

  • 在node 和 浏览器中都使用 globalThis 来获取全局对象

在ES11之前,虽然很多浏览器支持for…in来遍历对象类型,但是并没有被ECMA标准化。

  • 在ES11中,对其进行了标准化,for…in是用于遍历对象的key的

六、Dynamic Import:(就是动态import函数)

Section titled “六、Dynamic Import:(就是动态import函数)”

动态导入模块对象,返回一个promise对象。resolve状态的值为模块对象。

会等待所有的 promise 有结果,返回所有promise的状态和值,该方法是被 Promise.resolve() 包裹的。

八、import meta:后续ES Module模块化中讲解。

Section titled “八、import meta:后续ES Module模块化中讲解。”

可以通过 import.meta 对象获取这个模块的元数据信息。

  • 它的属性都是可写,可配置和可枚举的。
  • 刚打了一下是个空的对象(后续遇见了在看)

FinalizationRegistry 对象监听对象被垃圾回收时请求一个回调。

  • 当一个在 FinalizationRegistry 注册表中注册的对象被回收时,会触发回调函数

1、FinalizationRegistry的使用、注册表方法 register

Section titled “1、FinalizationRegistry的使用、注册表方法 register”
  1. 创建 FinalizationRegistry 获取实例,通过实例方法 register 进行注册需要监听的对象
  2. 每个监听对象在销毁的时候都会触发回调函数
let info = {name: "lisi", age: 18}
let obj = {name: "zhangsan", age: 34}
let weakInfo = new WeakRef(info)
let weakObj = new WeakRef(obj)
console.log(weakInfo, weakObj)
const finaliRegister = new FinalizationRegistry(val => {
console.log(`${val} 对象销毁`)
})
finaliRegister.register(info, "info")
finaliRegister.register(obj, "obj")
globalThis.setTimeout(() => {info = null},1000)
globalThis.setTimeout(() => {obj = null},8000)

WeakRef 会帮我们返回一个弱引用的对象变量

  1. 弱引用对象 获取 引用对象的 属性的话,需要调用 deref() 方法来获取
    • 但是注意,如果使用 deref() 获取属性的话就会变成强引用的。
  2. 强引用:对象之间的赋值 绑定都是强引用,特点会计算在GC标记清除算法内。
  3. 弱引用:GC 标记清除不会计算弱引用,当除了本身没有其他强引用链接的时候,就会别GC 回收
  • 特点:就是需要调用 deref() 方法获取对象的值,调用 deref() 方法之后就是强引用了

    应用场景应该只是存储下对象

let obj = {
name: "zhangsan",
age: 17
}
const weakMap = new WeakMap();
const weakObj = new WeakRef(obj)
weakMap.set(obj, "天津市")
//注意 weakObj.derfe() 这样其实就算是强引用了
// 这样的情况就不会销毁
console.log("%O", weakObj.deref())
// 这样就会正常销毁
console.log(weakObj)
//这样的情况下就不会销毁了
const temp = weakObj.deref()
//终结注册表, 监听对象销毁
const finaliRegister = new FinalizationRegistry((val) => {
console.log(weakMap, val)
console.log(weakObj.deref(), val)
})
//注册器
finaliRegister.register(obj, "obj")
//obj 销毁之后 weakMap 也就没有引用了
obj = null

三、logical assignment operators

相似于原地修改( Modify-in-place ) 因为都是逻辑运算符,直接举例,之后得这种就叫做 逻辑赋值操作符

  1. ||=

    function foo(arg) {
    //arg = arg || "前面得值为假得话,都会返回后面得值"
    arg ||= "zhangsan"
    console.log(arg)
    }
    foo(false)
  2. ??=

    function bar(arg) {
    //arg = arg ?? 默认值
    arg ??= "默认值"
    console.log(arg);
    }
    bar(null)
  3. &&=

    function runing() {
    return "runing"
    }
    let obj = {
    name: "zhangsan",
    age: 67,
    action: "action"
    }
    obj.action &&= runing()
    console.log(obj);

ES12 新增数组分割符 number类型可以使用下划线 _ ,提高代码得可读性

  1. 下划线 _ 不能放在数字类型得最前面,这样js会将 _ 当作变量类处理
  2. 也不能将下划线 _ 数字类型得最后面,会报错
  3. 只用于分割数字类型
//可以提高可读性
const num = 1000_000_000

方法返回一个新字符串,新字符串所有满足 pattern 的部分都已被replacement 替换。pattern可以是一个字符串或一个 RegExpreplacement可以是一个字符串一个在每次匹配被调用的函数

  • const newStr = str.replaceAll(regexp|substr, newSubstr|function)
  • replace 仅替换第一个匹配项。
let str = "zhangsan,lisi,wangwu,yanxiaoliu,lisi"
//替换当前字符串中所有符合规则得字符串
let replaceAllStr = str.replaceAll("lisi","")
//只会替换第一次出现符合规则的字符串
let replaceStr = str.replace("lisi","")
console.log(str);
console.log(replaceAllStr);
console.log(replaceStr);

Object中新增了一个静态方法(类方法): hasOwn(obj, propKey) 该方法用于判断一个对象中是否有某个自己的属性;

  1. Object.prototype.hasOwnProperty 的区别就是 前者是实例方法,后者是静态方法

  2. mdn 提示 Object.hasOwn()是一个替代Object.hasOwnProperty ()

  3. 应该是防止 Object.prototype.hasOwnProperty() 方法被重写

  • 提示:Object.hasOwn 方法比较新有可能存在兼容性的问题
  • 只会判断当前对象,不会去原型对象当中去查找

二、新的类成员(New members of classes)(4个字段,一个代码块)

Section titled “二、新的类成员(New members of classes)(4个字段,一个代码块)”

在ES13中,新增了定义class类中成员字段(field)的其他方式

  • 要node 16 版本以上才能使用,写后台的话要用最新的
  • 注意:所有使用 static 修饰的代码,都是类级别的当类进行加载的时候相关的静态代码就会进行加载

静态公有字段和实例公有字段都是可编辑的,可遍历的,可配置的。它们本身不同于私有对应值(private counterparts)的是,它们参与原型的继承。

  • 特点:内部、外部、子类、都可以通过实例进行访问
  • 特点:内部、外部、子类、都可以通过类或者静态方法、代码块进行访问

私有化成员属性

  • 只有类的内部进行访问,子类不能继承

私有静态方法

  • 只有类的内部静态方法和静态代码块可以进行访问,子类和外部类不能直接访问

静态代码块

  • 类级别的,当类进行初始化加载的时候就会执行
class Person{
// public 公共属性
score = 78 //应该等同于this.score = 78
//private 私有属性 只允许内部访问 不能继承
#price = 100
//静态属性
static num = "20220908"
//静态私有属性 只允许内部静态代码块访问 不能继承
static #prefix = "1_"
//所有静态修饰符修饰的静态代码块,函数,属性,都是类级别的 当类初始化的时候静态类型的相关代码都会加载
static {
console.log("静态代码块正在进行加载")
}
constructor(name, age, addr) {
this.name = name;
this.age = age;
this.addr = addr
}
runing() {
console.log(this.name, this.age, this.addr, this.score, this.#price);
}
static fn() {
console.log(this.#prefix, this.num);
}
}
const person = new Person("zhangsan", 34, "天津市");
/* person.runing()
console.log(person.score);
console.log(Person.num);
Person.fn()*/
//静态私有属性 不允许外部访问
// console.log(Person.#prefix);
//私有属性 不允许外部访问
// console.log(person.#price);