es6笔记

变量声明

  1. 为什么在let声明变量之前访问变量报的不是undefined的错而是未初始化的错
    底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错:“Cannot access 'a' before initialization”。当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。

    在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的循环变量)

    在循环中使用let声明的循环变量,在循环结束后会销毁

  2. const
    const声明变量时必须赋值,且不可更改,但是可以间接更改,如更改const声明对象的内部值

  3. 使用的好处

    1. 声明的变量不会挂载到全局对象,不会污染全局变量
      使用let声明一个a使用window.a是访问不到的,使用var是可以访问到的
    2. 声明的变量,不允许当前作用域范围内重复声明,在块级作用域中用定义的变量,在作用域外不能访问
    3. 不会存在变量提升(实际存在),不会出现闭包等怪异问题

字符串方法

  1. codePointAt()
    返回调用该方法的字串的指定位的码点,用于判断一个字符占用多少位(16/32),因为最后开始计算机中读取文字的长度是按码元算的(16位),而js长期
    没有更新,在字符集扩充后 .length 方法依然是以码元来读取字符串长度的,所以有时会造成读取的长度和实际长度不同的情况,此方法可用来返回字符的
    码点,如此就可以用来判断字符占用多少位了,大于2*16就是占两位的
  2. includes
    判断字符串中是否包含指定的子字符串
  3. startsWith
    判断字符串中是否以指定的字符串开始
  4. endsWith
    判断字符串中是否以指定的字符串结尾
  5. repeat
    将字符串重复指定的次数,然后返回一个新字符串。

函数

  1. 参数默认值
    给一个函数使用参数默认值的时候就会默认使用严格模式的arguments模式即arguments中存储的数值和形参的数值不再互相映射,他们将互相独立互不影响,在书写形参时,直接给形参赋值,附的值即为默认值

  2. 剩余参数
    ES6的剩余参数专门用于收集末尾的所有参数,将其放置到一个形参数组中。
    ``` js
    function (...形参名){

         }
     ```
    
    1. 使用arguments的缺点
      1. 如果和形参配合使用,容易导致混乱
      2. 从语义上,使用arguments获取参数,由于形参缺失,无法从函数定义上理解函数的真实意图
  3. 函数的调用方式(解决函数的二义性
    以前的函数既可以普通调用又可以使用new调用,这就造成了问题,所以如果要强制一个函数必须要用new调用则要在函数内部进行判断

    1. 老方法
          if(!(this.instanceof func)) // 判断函数的this是不是继承自函数本体
      
      这么写的原因是正常new一个函数的时候会在函数运行之初创建一个this对象,这个将这个对象的隐式原型指向当前的构造函数,使用this.instanceof func就判断了这个this是通过new调用的还是普通调用
    2. 新方法
          if(new.target === undefind) // new.target 如果函数是以new的方式调用则返回整个函数的函数体,如果不是则返回undefined
      
  4. 箭头函数

    1. 普通函数的this指向
      1. 通过对象调用函数,this指向对象
      2. 直接调用函数,this指向全局对象
      3. 如果通过new调用函数,this指向新创建的对象
      4. 如果通过apply、call、bind调用函数,this指向指定的数据
      5. 如果是DOM事件函数,this指向事件源
    2. 细节
      1. 箭头函数中,不存在this、arguments、new.target,如果使用了,则使用的是函数外层(函数定义位置)的对应的this、arguments、new.target,箭头函数的 this 值在定义时就确定了,继承自外层作用域的 this
        1. 注意
          对象字面量在声明时不创建作用域
      2. 箭头函数没有原型
      3. 箭头函数不能作用构造函数使用

对象

  1. 方法
    1. Object.is
      用于判断两个数据是否相等,基本上跟严格相等(===)是一致的,除了以下两点:
      1. NaN和NaN相等
      2. +0和-0不相等
    2. Object.assign
      用于混合对象
    3. Object.getOwnPropertyNames 的枚举顺序
      Object.getOwnPropertyNames方法之前就存在,只不过,官方没有明确要求,对属性的顺序如何排序,如何排序,完全由浏览器厂商决定。
      ES6规定了该方法返回的数组的排序方式如下:
      1. 先排数字,并按照升序排序
      2. 再排其他,按照书写顺序排序
    4. Object.setPrototypeOf
      该函数用于设置某个对象的隐式原型
      比如: Object.setPrototypeOf(obj1, obj2),
      相当于: obj1.__proto__ = obj2
  2. 面向对象

  1. 传统的构造函数的问题

    1. 属性和原型方法定义分离,降低了可读性
    2. 原型成员可以被枚举
    3. 默认情况下,构造函数仍然可以被当作普通函数使用
  2. 类的特点

    1. 类声明不会被提升,与 let 和 const 一样,存在暂时性死区
    2. 类中的所有代码均在严格模式下执行
    3. 类的所有方法都是不可枚举的
    4. 类的所有方法都无法被当作构造函数使用
    5. 类的构造器必须使用 new 来调用
  3. 类的声明相关

    1. class
      类声明标识符
    2. constructor
      构造器函数,相当于构造函数本体,可传入参数,后跟函数体
    3. 原型方法(实例方法)
      直接写在类里
    4. 使用
      使用方法同构造函数
    5. 存取器属性(get/set)
      需要一个中间变量,相当于object.defineproperty,书写方式如下
          class Ani {
              constructor(name) {
                  this.name = name;
              }
      
              get name() {
                  return "动物:" + this._name;
              }
      
              set name(value) {
                  if ((typeof value) === "number") {
                      this._name = value + "string"
                  } else {
                      this._name = value;
                  }
              }
      
          }
      
    6. 静态成员
      使用static关键字的定义在非constructor中的就是这个类的静态成员
    7. 字段初始化器
      如果这个构造函数的属性不需要传参是已经固定的可以如下写,初始化的表达式会隐式的在构造器中用this加在里边
          class test {
              a = 1;
              b = 2;
              // 相当于
              // constructor(){
              //     this.a = 1
              // }
          }
      
    8. 装饰器(未实装)
      装饰器的本质是一个函数,一个装饰器有三个参数,第一个参数是类本身,第二个是方法名,第三个是方法描述(方法的一些信息,为一个对象)
          function my(target, methodName, des) {
              const old = des.value
              des.value = function(...args) {
                  console.warn(`${methodName}方法已过时`)
                  obl.apply(this.args)
              }
          }
          class Te {
              @my
              print() {
                  console.log(123)
              }
          }
          let x = new Te()
          x.print()
      
    9. 类的继承
      1. 如果两个类A和B,如果可以描述为:B 是 A,则,A和B形成继承关系如果B是A,则:
        1. B继承自A
        2. A派生B
        3. B是A的子类
        4. A是B的父类
          如果A是B的父类,则B会自动拥有A中的所有实例成员。
      2. 相关关键字
        • extends:继承,用于类的定义

        • super

          • 直接当作函数调用,此关键字表示父类构造函数,相当于用call调用父类构造函数并绑定this
          • 如果当作对象使用,则表示父类的原型,即如果需要字类调用父类的原型上的方法既可以将super当作父类的构造函数
                super.xxx()
            

          注意:ES6要求,如果定义了constructor,并且该类是子类,则必须在constructor的第一行手动调用父类的构造函数
          如果子类不写constructor,则会有默认的构造器,该构造器需要的参数和父类一致,并且自动调用父类构造器

              class Animal {
                  constructor(type, name, age, sex) {
                      this.type = type;
                      this.name = name;
                      this.age = age;
                      this.sex = sex;
                  }
          
                  print() {
                      console.log(`【种类】:${this.type}`);
                      console.log(`【名字】:${this.name}`);
                      console.log(`【年龄】:${this.age}`);
                      console.log(`【性别】:${this.sex}`);
                  }
          
                  jiao(){
                      throw new Error("动物怎么叫的?");
                  }
              }
          
              class Dog extends Animal {
                  constructor(name, age, sex) {
                      super("犬类", name, age, sex);
                      // 子类特有的属性
                      this.loves = "吃骨头";
                  }
          
                  print(){
                      //调用父类的print
                      super.print();
                      //自己特有的代码
                      console.log(`【爱好】:${this.loves}`);
                  }
          
          
                  //同名方法,会覆盖父类
                  jiao(){
                      console.log("旺旺!");
                  }
              }
          
              const d = new Dog("旺财", 3, "公");
              d.print();
              console.log(d)
              d.jiao();
          
  4. 类的优点

    1. 属性和原型方法在一起,易阅读
    2. 原型方法不可枚举
    3. 默认不可以直接调用
  5. 类的特点

    1. 类声明不会被提升,与 let 和 const 一样,存在暂时性死区
    2. 类中的所有代码均在严格模式下执行
    3. 类的所有方法都是不可枚举的
    4. 类的所有方法都无法被当作构造函数使用
    5. 类的构造器必须使用 new 来调用

解构

  1. 对象解构
    {同名变量 = 默认值}
  2. 数组解构
    const [n1, n2] = numbers;

符号(symbol)

  1. 什么是符号
    符号是ES6新增的一个数据类型,它通过使用函数 Symbol(符号描述) 来创建,符号设计的初衷,是为了给对象设置私有属性
    私有属性:只能在对象内部使用,外面无法使用
  2. 符号的特点
    • 没有字面量
    • 使用 typeof 得到的类型是 symbol
    • 每次调用 Symbol 函数得到的符号永远不相等,无论符号名是否相同
      • 符号可以作为对象的属性名存在,这种属性称之为符号属性
      • 开发者可以通过精心的设计,让这些属性无法通过常规方式被外界访问
      • 符号属性是不能枚举的,因此在 for-in 循环中无法读取到符号属性,Object.keys 方法也无法读取到符号属性
      • Object.getOwnPropertyNames 尽管可以得到所有无法枚举的属性,但是仍然无法读取到符号属性
      • ES6 新增 Object.getOwnPropertySymbols 方法,可以读取符号
    • 符号无法被隐式转换,因此不能被用于数学运算、字符串拼接或其他隐式转换的场景,但符号可以显式的转换为字符串,通过 String 构造函数进行转换即可, console.log 之所以可以输出符号,是它在内部进行了显式转换
  3. 符号的意义
    1. 符号的作用就是用来创建私有变量的,一个对象有自己的方法,禁止外部使用即可在命名时使用符号
        let obj = (function () {
            const a = Symbol();
            const b = Symbol();
            return {
                [a]: 1,
                [b]() {
                    console.log("dsad");
                }
            }
        }())
    
    如此返回的这个对象中的属性将无法被外界访问
  4. Object.getOwnPropertySymbols
    此方法可以取得一个对象中的所有符号返回数组
  5. 共享符号
    根据某个符号名称(符号描述)能够得到同一个符号
    Symbol.for("符号名/符号描述")  //获取共享符号
    
  6. 知名符号
    暴露js一些方法的内部实现,用以减少魔法,让开发人员可以参与控制内部实现

异步处理

  1. 事件循环

    1. 宏队列
      浏览器多数会将事件队列进行细分,分为宏队列和微队列,宏队列中计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
    2. 微队列
      promise产生的回调进入微队列
      当js执行栈空闲的时候会首先进入微队列查看是否有待执行方法再进入宏队列
  2. 异步处理的通用模型
    即事件模型,已决未决等,整个异步处理通用模型的过程叫做promise

  3. promise API

  4. promise的串联

    1. 一个promise对象经过then处理后会返回一个新的promise对象,这个promise对象的状态取决于then的执行状态,如果then正确执行没有错误那么
      默认返回的promise就会处于resolve状态,并且在then中显示返回的数据将作为resolve的参数注入,如果有错误返回的promise将会处于reject状态
      pending则为pending
    2. 如果当前的Promise是未决的,得到的新的Promise是挂起状态
    3. 如果当前的Promise是已决的,会运行响应的后续处理函数,并将后续处理函数的结果(返回值)作为resolved状态数据,应用到新的Promise中;
      如果后续处理函数发生错误,则把返回值作为rejected状态数据,应用到新的Promise中。
    4. 如果一个then中手动返回一个promise那么本应该将这个返回值作为默认返回的promise的参数,但是这是一个promise,这时就会将手动返回的这个promise
      的数据注入默认返回的promise中
  5. 任务详解(解释第二点)

  6. 如果第一个任务(前边的任务)失败了而后续的处理没有对失败进行处理那么后续的任务也会失败,当然第一个任务如果成功后续任务没有对成功的状况进行处理那么后续的任务也会成功,数据为前任务的数据

  7. 若有后续处理但还未执行,新任务挂起。

  8. 若后续处理执行了,则根据后续处理的情况确定新任务的状态,即第一个任务执行了,有结果了,后续处理程序就会根据第一个事件的状态进行后续的处理,如第一个程序的结果是成功则进入后续处理程序的成功处理函数(resolve)否则进入reject,

  9. 后续的事件发展是根据是否正确处理相关的,如第一个函数进入reject如果后续的then或者catch进行了正确的处理,那么接下来的then将进入resolve状态,若没有进行任何处理那么就依据第一条
    ~~~ js
    // 未处理reject
    Promise.reject("Error occurred") // 初始reject
    .then(() => console.log("1st then")) // 被跳过
    .then(() => console.log("2nd then")) // 被跳过
    .catch(err => console.log("Caught:", err)); // 捕获错误
    // 输出: "Caught: Error occurred"

    // 处理了reject
    Promise.reject("Error occurred")
    .catch(err => {
    console.log("Caught:", err);
    return "Recovered"; // 返回新值
    })
    .then(val => console.log("After recovery:", val)) // 继续执行
    .catch(err => console.log("Won't run"));
    // 输出:
    // "Caught: Error occurred"
    // "After recovery: Recovered"
    ~~~

  10. promise的其他API

    1. pro.resolve
    2. pro.reject
    3. Promise.all
      传入一个promise数组,并返回一个promise,这个promise的resolve状态需要等到数组中的所有promise都变为resolve才会转化,只要有一个是reject
      则此promise立即转化位reject状态这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值【参数】的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息,如果不进行处理(then/catch)就会抛出错误。Promise.all方法常被用于处理多个promise对象的状态集合。
    4. Promise.race
      当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象(传入可迭代对象数组中只要有一个进入已决或未决返回的promise立即进入相应状态,并且将这个promise的参数注入产生的这个promise)
  11. promise源码分析

    1. promise是一个构造函数,需要传入一个推动函数,此函数是同步执行的,这个推动函数的功能是将事件推向已决阶段,并计算出回调函数需要的数据。传入推动函
      的两个函数参数在内部是写好的它的作用是改变整个promise对象所处的状态并为后续then中的回调函数实体要用的参数赋值(这个参数已经定义到了类本体上,this上),在执行事件驱动函数如果出现错误不会报错而是直接进入reject状态并将错误信息传入,所以在执行驱动函数的时候需要进行错误捕获并处理
       const myPromise = (function () {
           return class myPromise {
               // 此方法用以判断对象状态并更改状态和数据
               judge(state, record){
                   if (this.status !== "pending"){
                       return;
                   }else{
                       this.status = state;
                       this.data = record;
                   }
               }
      
               // 传入了事件推动函数
               constructor(spread) {
                   // then需要用到的数据
                   this.data = undefined;
                   // 现在promise所处的状态,初步是挂起
                   this.status ="pending";
                   // 内部定义resolve函数,接受一个参数为推动函数中传入的,用以改变对象储在什么状态和更新数据
                   const resolve = (data) => {
                       // 如果当前状态太不是挂起状态那就是已决状态,事件流程不可逆则直接返回
                       this.judge("resolve", data);
                   };
      
                   const reject = (data) => {
                       this.judge("reject", data);
                   };
      
                   // 执行事件推动函数,此函数传入两个参数函数,此两个函数在内部定义好暴露接口用于获取事件推动函数计算出的参数并改变状态
                   // 原生方法如果推动函数执行出现错误不会报错会捕获错误将错误信息赋值在数据上并直接进入reject状态
                   // spread(resolve,reject)
                   try {
                       spread(resolve,reject)
                   }catch (err) {
                       this.judge("reject", err);
                   }
               }
           }
       })();
      
    2. 完整promise
          const myPromise = (function () {
              return class myPromise {
                  // 此方法用以判断对象状态并更改状态和数据
                  // 由于此处的作用是promise状态变更,而这个变更可能是异步的,所以可能回调队列里可能有待执行的回调了,他们在
                  // 状态变更后执行,所以就可以放在这个状态变更方法里
                  judge(state, record, queue) {
                      // 默认状态如果已发生改变则进入已决状态,已决状态无法改变
                      if (this.status !== "pending") {
                          return;
                      } else {
                          this.status = state;
                          this.data = record;
                          queue.forEach(handle => handle(record));
                      }
                  }
      
                  // 传入可能需要添加的队列名,和需要添加的方法,如果符合状态则立即执行如果状态不符则将之添加进队列
                  needAddQueue(queue, handler, data) {
                      if ((typeof handler) !== "function") {
                          return;
                      }
                      if (this.status !== "pending") {
                          setTimeout(() => {
                              handler(data);
                          }, 0)
                      } else {
                          queue.push(handler)
                      }
                  }
      
                  // 根据then的执行情况返回一个相应情况的promise
                  linkPromise(thenable, catchable) {
                      return new myPromise((resolve, reject) => {
                          this.needAddQueue(this.resolveQueue, data => {
                              try {
                                  const result = thenable(data);
                                  resolve(result);
                              } catch (e) {
                                  reject(e)
                              }
                          }, this.data);
      
                          this.needAddQueue(this.rejectQueue, data => {
                              try {
                                  const result = catchable(data);
                                  resolve(result);
                              } catch (e) {
                                  reject(e);
                              }
                          }, this.data)
      
                      })
                  }
      
                  // 传入了事件推动函数
                  constructor(spread) {
                      // then需要用到的数据
                      this.data = undefined;
                      // 现在promise所处的状态,初步是挂起
                      this.status = "pending";
                      // resolve队列
                      this.resolveQueue = [];
                      // reject队列
                      this.rejectQueue = [];
                      // 内部定义resolve函数,接受一个参数为推动函数中传入的,用以改变对象储在什么状态和更新数据
                      const resolve = (data) => {
                          // 如果当前状态太不是挂起状态那就是已决状态,事件流程不可逆则直接返回
                          this.judge("resolve", data, this.resolveQueue);
                      };
      
                      const reject = (data) => {
                          this.judge("reject", data, this.rejectQueue);
                      };
      
                      // 执行事件推动函数,此函数传入两个参数函数,此两个函数在内部定义好暴露接口用于获取事件推动函数计算出的参数并改变状态
                      // 原生方法如果推动函数执行出现错误不会报错会捕获错误将错误信息赋值在数据上并直接进入reject状态
                      // spread(resolve,reject)
                      try {
                          spread(resolve, reject)
                      } catch (err) {
                          this.judge("reject", err);
                      }
                  }
      
                  // then方法的作用是用来注册已决状态的事件队列,事件队列有两个,一个是resolve一个是reject状态,如果在then的时侯promise已处于
                  // 已决状态则直接执行传入的方法,如果没有到达已决状态则加入事件队列,由于then方法可能传入两个回调函数或一个,所以在添加事件的时候
                  // 要进行判断
                  then(thenable, catchable) {
                      // if ((typeof thenable) !== "function"){
                      //     return;
                      // }
                      // 已决状态立即执行
                      return this.linkPromise(thenable, catchable);
                  }
      
      
                  catch(catchable) {
                      return this.linkPromise(undefined, catchable);
                  }
              }
          })();
      
  12. async和await

    1. 什么是async和await
      async 和 await 是 ES2016 新增两个关键字,它们借鉴了 ES2015 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。
    2. async
      目的是简化在函数的返回值中对Promise的创建,async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。
      有async的函数其内部的返回值即为新创建的promise对象的resolve或reject的传入的数据
    3. await
      await关键字必须出现在async函数中!!!!await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable中的状态数据。

    4. async就是一个promise的语法糖,他将函数中的内容封装在一个promise中执行,并将返回的结果作为返回的promise的响应状态的参数,而await的作用
      是等待一个async返回的promise进入已决并拿到其返回的参数,相当于把then摊开了没有回调了,await解析的是已决状态的then中对应回调函数的参数
      在for循环中用到await则下一次循环必须等这一次的await拿到结果后才会继续进行,省去了连点then
      如果await的表达式不是Promise,则会将其使用Promise.resolve包装后按照规则运行

fetch API

  1. 使用
    1. fetch()
      第一个参数为请求地址,第二个参数为配置对象,请求方式默认get,返回一个promise
    2. 方法
      • ok:boolean,当响应消息码在200~299之间时为true,其他为false
      • status:number,响应的状态码
      • text():用于处理文本格式的 Ajax 响应。它从响应中获取文本流,将其读完,然后返回一个被解决为 string 对象的 Promise。
      • blob():用于处理二进制文件格式(比如图片或者电子表格)的 Ajax 响应。它读取文件的原始数据,一旦读取完整个文件,就返回一个被解决为 blob 对象的
        Promise。
      • json():用于处理 JSON 格式的 Ajax 的响应。它将 JSON 数据流转换为一个被解决为 JavaScript 对象的promise。
      • redirect():可以用于重定向到另一个 URL。它会创建一个新的 Promise,以解决来自重定向的 URL 的响应。
    3. Request对象
      • 除了使用基本的fetch方法,还可以通过创建一个Request对象来完成请求(实际上,fetch的内部会帮你创建一个Request对象),此方法用来进行全局配置简
        化代码
      1. 用法
            let req = new Request(url地址, 配置)
            fetch(req);
        
        可以用一个函数用来根据参数返回一个request对象就可以实现整体的基本配置
        尽量保证每次请求都是一个新的Request对象

迭代器和生成器

  1. 迭代器

    1. 迭代器
      所谓迭代器就相当于一个仓库管理员,正常情况下要在数组中拿数据需要进入这个数组仓库根据下标去找数据(for循环),如果使用迭代器则不再进入仓库,
      只需要告诉迭代器要拿哪一个数据即可,比如如果是一个稀松数组则可以让迭代器操作过后再返回数据
    2. 什么是迭代器
      一个对象它具有next属性,该属性是一个方法,此方法返回一个对象,此对象有两个属性,一个value,一个done,value属性是获取的数据,done是一个
      布尔值,它表示可迭代对象是否迭代完毕,此对象即为一个迭代器
          {
              next() {
                  return {
                      value: num,
                      done: boolean,
                  }
              }
          }
      
    3. 可迭代协议
      ES6规定,如果一个对象具有知名符号属性Symbol.iterator,并且属性值是一个迭代器创建函数,则该对象是可迭代的(iterable)
      要调用这个可迭代对象的迭代器可以利用 Symbol.iterator
          num[Symbol.iterator]() // 这就调用了迭代器生成函数
      
    4. for/of循环
      此循环是用来遍历一个可迭代对象的迭代器的,比如拿到一个数组的迭代器,如果使用此循环则会遍历数组(实际是不断调用这个迭代器的next方法并监听
      返回的对象的done属性,如果返回true则跳出),for/of循环即为下边的语法糖
          let arr = [1,2,3,5,2,43];
          let iter = arr[Symbol.iterator]();
          let result = iter.next();
          while (!result.done){
              console.log(result.value); // 取出数据
              result = iter.next();
          }
          // 同下
          for (const item of arr){
              console.log(item)
          }
      
      for/of循环就是用来迭代可迭代对象的,只要一个对象满足可迭代协议那么他就可以被for/of循环迭代
    5. 迭代器创建函数
      其为一个函数,返回一个迭代器
  2. 生成器

    1. 补充

      1. 生成器结构
        生成器的定义是它既是一个迭代器又是一个可迭代对象,既是生成器那么它是一个具有next方法的对象,它是一个可迭代对象那么他就具有一个[Symbol.iterator] = fun()属性
            {
                next(){
                    return {
        
                    }
                },
                [Symbol.iterator]: function(){
                    return {
                        next(){
                            return {
        
                            }
                        }
                    }
                }
            }
        
        此为一个通过一个生成器函数创建的生成器(实际上不是的,这是伪代码),这两个next其实是同一个
      2. 生成器用法
            function* iteratorArr(arr){
                for(let item in arr){
                    yield arr[item];
                }
            }
        
            let b = iteratorArr([1,2,3,2,1,4,2])
        
        1. yield传参
          1. 生成器第一次调用next运行到yield传入的参数无意义
            原因在于第一次执行的时候是执行到yield就停止,它并没有执行yield的赋值,在下一次执行的时候才会执行这个yield的赋值并执行下一个yield之前的代码
        2. yield传参原理
              function *test(){
                  let i = yield 1;
                  console.log(i);
                  let j = yield 2 + i;
                  console.log(j)
              }
              let x = test();
              x.next();
              x.next(5);
              x.next(3);
          
          以上代码输出结果为5,3,原因在于其流程,第一次next运行卡在了yield,并没有执行赋值并且第一次传参是没有意义的,相当于舍弃了,第一次next其意义就在于启动了迭代器,而第二次yield需要执行的输出语句依赖i这个时候运行next实际上是在第一个赋值表达式开始运行的到第二个yield截至,这个时候传入参数实际上是在给第一个i赋值,这个时候第二个yield返回的对象的value值为4,输出传入的5,第三次实际上是在给第二个赋值表达式赋值,所以输出3
          归根结底,第一次传入的参数相当于舍弃,后续传入的参数相当于在给前一个赋值表达式赋值
          1. 复习细节
            复习阶段了解到yield的作用为迭代器执行节点,并且yield后边跟的数据将会被赋值给返回迭代器对象的value值
            每次执行next之后将会返回一个迭代器对象,包含value和done两个项,yield后边的值将会作为value返回,严重注意,next的第一次无效不需要看,从第二次开始看赋值才有作用
    2. 什么是生成器?
      生成器是一个通过构造函数Generator创建的对象,生成器既是一个迭代器,同时又是一个可迭代对象

    3. 如何创建一个生成器
      由于Generator构造函数是js内部调用的,不允许外部调用此方法,所以要得到一个生成器对象用如下方法,生成器对象既是一个迭代器

          function *method (){ // 此方法一定返回一个生成器对象
      
          }
      
    4. 生成器函数内部是如何执行的?
      生成器函数内部是为了给生成器的每次迭代提供的数据
      每次调用生成器的next方法,将导致生成器函数运行到下一个yield关键字位置
      yield是一个关键字,该关键字只能在生成器函数内部使用,表达“产生”一个迭代数据

    5. 理解

      1. 生成器函数执行返回一个迭代器,这个函数内部的代码是不执行的,只有等调用返回的这个生成器生成器对象(迭代器)的next方法时
        才开始执行生成器函数的内部代码,并且内部代码不是一次性执行到底,而是每调用一次next执行到下一个 yield 标记,这个标记后边
        跟next返回的数据对象,并且执行会预先判断下次执行会不会执行到yield,如果可以那么返回的next数据对象的done就会时false,如果
        下一次调用next不会有yield了那么返回的done就会是true
    6. 注意事项

      1. 生成器函数可以有返回值,返回值出现在第一次done为true时的value属性中
      2. 调用生成器的next方法时,可以传递参数,传递的参数会交给yield表达式的返回值
      3. 第一次调用next方法时,传参没有任何意义
      4. 在生成器函数内部,可以调用其他生成器函数,但是要注意加上*号
    7. 生成器处理异步场景

          function* task() {
              const d = yield 1;
              console.log(d)
              // d: undefined
              const resp = yield fetch("http://101.132.72.36:5100/api/local")
              const result = yield resp.json();
              console.log(result);
          }
      
          run(task)
      
          function run(generatorFunc) {
              const generator = generatorFunc();
              let result = generator.next(); //启动任务(开始迭代), 得到迭代数据
              handleResult();
              //对result进行处理
              function handleResult() {
                  if (result.done) {
                      return; //迭代完成,不处理
                  }
                  //迭代没有完成,分为两种情况
                  //1. 迭代的数据是一个Promise
                  //2. 迭代的数据是其他数据
                  if (typeof result.value.then === "function") {
                      //1. 迭代的数据是一个Promise
                      //等待Promise完成后,再进行下一次迭代
                      result.value.then(data => {
                          result = generator.next(data)
                          handleResult();
                      })
                  } else {
                      //2. 迭代的数据是其他数据,直接进行下一次迭代
                      result = generator.next(result.value)
                      handleResult();
                  }
              }
          }
      
    8. 生成器api

      1. return方法
        提前让整个迭代流程结束
      2. throw方法
        可以在生成器函数中产生一个错误
      3. 生成器调用生成器
        调用别的生成器的时候需要在yield后边加*,否则没有什么太大意义,加了星号后迭代的时候会深入被调用的生成器内部去迭代,即可以使用被调用生成器的全部功能

更多的集合类型

  1. set
    1. 什么是set
      set集合就是用来存放不重复的数据的
    2. 如何创建set集合
      new Set(); //创建一个没有任何内容的set集合
      
      new Set(iterable); //创建一个具有初始内容的set集合,内容来自于可迭代对象每一次迭代的结果
      
      
    3. 如何对set集合进行后续操作
      • add(数据): 添加一个数据到set集合末尾,如果数据已存在,则不进行任何操作
        • set使用Object.is的方式判断两个数据是否相同,但是,针对+0和-0,set认为是相等
      • has(数据): 判断set中是否存在对应的数据
      • delete(数据):删除匹配的数据,返回是否删除成功
      • clear():清空整个set集合
      • size: 获取set集合中的元素数量,只读属性,无法重新赋值
  2. map
    1. 什么是map
      用于存储多个键值对数据
    2. 如何创建map
      new Map(); //创建一个空的map
      new Map(iterable); //创建一个具有初始内容的map,初始内容来自于可迭代对象每一次迭代的结果,但是,它要求每一次迭代的结果必须是一个长度为2的数组,数组第一项表示键,数组的第二项表示值
      
    3. 如何进行后续操作
      • size:只读属性,获取当前map中键的数量
      • set(键, 值):设置一个键值对,键和值可以是任何类型
        • 如果键不存在,则添加一项
        • 如果键已存在,则修改它的值
        • 比较键的方式和set相同
      • get(键): 根据一个键得到对应的值
      • has(键):判断某个键是否存在
      • delete(键):删除指定的键
      • clear(): 清空map
    4. 和数组互相转换
      和set一样
    5. 遍历
      • for-of,每次迭代得到的是一个长度为2的数组
      • forEach,通过回调函数遍历
        • 参数1:每一项的值
        • 参数2:每一项的键
        • 参数3:map本身
    6. map的意义
      一个对象在概念上应该是用来描述某个事物的属性键值对集合,缺一不可,而很多情况下我们需要的是一个存储键值对类型数据的容器,这个容器只用来存储
      没有其他含义,故有map
  3. weakset
    它内部存储的对象不影响外部的垃圾回收,如将一个对象加入一个set对象中并在外部将这个对象赋值为null那么由于set还拿着这个对象的引用所以这个对象实际不会被
    垃圾回收器回收,如果用的是weakset外部的obj被赋值为null那么垃圾回收器就会无视weakset中拿到的引用直接将之回收,再次访问这个weakset就拿不到这个对象了
    所以这个集合就适合用来监控应该被回收的对象有没有被回收
  4. weakmap
    它的内部存储的键值对的键不影响外部的垃圾回收,且此集合的键只能被赋值为对象,即如果在外部这个键对象被赋值为null那么再利用这个键对象来访问相应的值
    就无法访问了,因为这个对象已被回收。这个集合可以用来监控连锁关系,比如一个dom的li对象关联一个存储姓名和年龄的对象,如果这个li对象被删除那么这个数据
    对象就应该被回收,即可利用weakmap

代理和反射

  1. 属性描述符(Object.defineProperty)
    给一个对象的属性添加了get或set属性那么这个属性就变成了一个存取器属性,只要这个属性变成存取器属性那么每次访问和改写此属性的时候就会调用对应的存取器函数
    那么就可以通过这个存取器函数控制属性的访问和更改,vue中的数据动态更新就用到此原理,监听属性变化
        let tar = {
            a: 10,
        };
    
        let obj = {
        };
    
        Object.defineProperty(obj, "a", {
            set(v) {
                console.log("运行get");
                if (tar["a"] !== v) {
                    console.log("同步tar");
                    tar["a"] = v;
                }
                obj._a = v;
            },
            get() {
                console.log("运行set");
                return obj._a;
            }
        })
    
  2. 代理和反射
    1. Reflect
      1. Reflect是什么?
        Reflect是一个内置的JS对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些JS底层功能
        由于它类似于其他语言的反射,因此取名为Reflect
      2. 它可以做什么?
        使用Reflect可以实现诸如 属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在与对象中 等等功能
      3. 它的意义
        reflect对象中有一系列的底层函数式方法,他们用来进行js的基础操作,如赋值等,这样做的意义在于减少魔法,让作者可以更多的参与到js的底层实现
    2. Proxy 代理
      1. 什么是代理
        代理:提供了修改底层实现的方式
        //代理一个目标对象
        //target:目标对象
        //handler:是一个普通对象,其中可以重写底层实现
        //返回一个代理对象
        new Proxy(target, handler)
        
        代理代理了需要操作的对象,是我们修改待修改对象的底层实现的中间层
        handler对象中可以重写所有的反射对象中的底层方法
      2. 使用
        vue热更新如下,后续更改被代理的对象需要通过代理者才可实现代理功能(观察者模式代理实现)
        let tar = {
        };
        let obj = {};
        const proxy = new Proxy(obj, {
            // target:被代理的对象 p:被操作的key value:被赋予的值 receiver:代理本身
            set(target, p, value, receiver) {
                console.log("动态更新tar");
                tar[p] = value;
                console.log(tar);
            }
        })
        proxy.a = 1;
        

es6笔记
http://localhost:8090/archives/es6bi-ji
作者
Administrator
发布于
2026年01月30日
许可协议