Hello ECMAScript .

ECMAScript - 学习笔记 🎬

ECMAScript 6.jpeg

🧩nvm node.js 包管理工具

nvm github

🧩nrm npm 镜像管理工具

nrm ls
nrm test [name]         // 测试延迟
nrm use [name]          // 使用
nrm add [alias] [url]   // 添加自己的源
nrm del [name]          // 删除源

🧩新的声明方式 let 定义变量

  • let 特性
  1. 不属于顶层对象的 window
    • 全局变量和var 都可以用 window 去指向,这就会导致 污染了全局变量
    • 虽然 webpack 打包会规避掉这个问题。
  2. 不允许重复声明
  3. 不存在变量提升
  4. 暂时性死区
Demo1:
a = 2
let a

Demo2: 比较隐蔽的
function foo(a = b, b = 2) {
  // 上面的第一个默认参数用b时,b还没有被赋值 所以会报错
}
foo()
  1. 块级作用域
// 因为 var 没法在 {} 中形成一个块,所以最后 i++ 后,循环外可以使用i它是3
for (var i = 0; i < 3; i++) {
  console.log('循环内:' + i) // 0 1 2
}
console.log('外:' + i) // 3

注意:块级作用域必须要有 {} 大括号

// 这样就会报错
if (true) let a = 5

知识点 + 面试题:

for (var i = 0; i < 3; i++) {
  // 异步
  setTimeout(function () {
    console.log(i); // 直接输出了i的最后结果:③ 3
  })
}

// 解决方案1:
for (var i = 0; i < 3; i++) {
  (function(j){
    setTimeout(function () {
      console.log(j);
    })
  })
  (i) // 调用方法
}

// 解决方案2:(可以通过 babel 查看编译成ES5的语法)
for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  })
}

扩展知识:

  1. webpack 打包目录注意 static 不会被打包进去
  2. webpack 打包时会自动每一行代码拼接\n所以js中不需要去刻意加";"
  3. Babel 是一个 JavaScript 编译器

🧩新的声明方式 const 定义常量

  • const 特性和 let 是一样的特性

  • 栈内存(stack)、堆内存(heap)

    • 栈内存里的存放
变量名
num 123456
str 'abcd'
obj 引用地址
arr 引用地址
  • 栈内存(stack)、堆内存(heap)

    • 堆内存里的存放
    obj: {name: '三脚猫'}
    arr: ['ES6', 'ES7', 'ES8']
    
  • JS API

    • ES5 定义常量API:
    Object.defineProperty(window, 'NAME', {
        value: '三脚猫',
        writable: false // 是否可写的,覆盖
    })
    
    • Object.freeze(obj) // 冻结对象:因为是浅层冻结,所以多级需要递归冻结

知识点

const 定义的变量不可赋值,但是对象/数组可以改变,因为对象/数组都存在堆内存,栈内存中只是引用地址不变
Test:

const obj = {
  name: '三脚猫'
}
console.log(obj);
obj.englistName = 'Harvey'
console.log(obj);

🧩解构赋值

  • 数组解构赋值

    • 可设置初始值 let [a = 1] = []
  • 对象解构赋值

    • 可设置初始值 let {a = 1} = {}
    • 起别名 let {name: username} = {name: '三脚猫'}

注意:

  1. 数组解构时,是通过顺序去解构赋值的
  2. 对象解构时,是通过key值去结构的,改变了key值顺序是没有影响
  • 字符串解构赋值

    • 同数组结构写法一样
  • 函数的解构赋值

function foo({name, age}) {
    console.log(name, age);
}
foo({name: '三脚猫', age: 18})
  • 应用:提取 json 数据
let jsonStr = '{"a": "哈哈", "b": "嘿嘿"}'
let {a, b} = JSON.parse(jsonStr)
console.log(a, b);

🧩数组的遍历方式

ES5 中的数组遍历方式
  • for 循环
  • forEach forEach(function (item, key, arrSelf) {})
    • 不支持 break
    • 不支持 continue
  • map() 返回新的Array
  • filter() 返回符合条件的元素数组
  • some() 返回boolean,判断是否有符合的元素
  • every() 返回boolean,判断每个元素都要符合条件,不然返回false
  • reduce() 接收一个函数作为累加器
    • 4个参数 prev curr index arrSelf
    • reduce第二个值是初始值,没有设置初始值,则将数组第一个元素作为初始值
    • reduce 循环找出最大值 Demo:
      let arr = [1, 2, 3]
      let max = arr.reduce(function(previous, current, index, array) {
          let maxValue = Math.max(previous, current);
          return maxValue; // 每次都返回该次循环比对的最大值,下一次循环用来比较用
      })
      console.log(max);
      
    • reduce 循环去重 Demo:
      let max = arr.reduce(function(prev, curr) {
          // indexOf 如果要检索的字符串值没有出现,则该方法返回 -1
          prev.indexOf(curr) == -1 && prev.push(curr)
          return prev
      }, [])
      console.log(max);
      
  • for in 循环
    • 会循环数组原型下定义的元素 Array.prototype 原型方法
    • Demo:
      // 这里用原型定义了一个方法,会被 for in 给循环出来
      Array.prototype.foo = function() {
        console.log('foo');
      }
      let arr = [1, 2, 3]
      for (let index in arr) {
          console.log(index, arr[index]);
      }
      
ES6 中的数组遍历方式
  • find() 返回第一个通过测试的元素
    • 未找到元素时,返回 undefined
  • findIndex() 返回的值为该通过第一个元素的索引
    • 未找到元素时,索引值返回是 -1
  • for of
    • 三种写法Demo:
      for (let item of arr.values()) { // 不加 values() 默认也是 value
          console.log(item); // 拿到每个value
      }
      for (let index of arr.keys()) { // keys() 是一个方法
          console.log(index); // 拿到每个 key
      }
      for (let [index, item] of arr.entries()) {
          console.log(index, item); // 拿到每个键值对
      }
      

🧩ES6新的特性 - 数组的扩展

  • 类数组/伪数组

    • 也有长度但是不能使用数组的方法
    • 比如:
      let divs = document.getElementByTagName('div')
      let divs2 = document.getElementByClassName('div_class')
      console.log(divs, divs2); // 这都是伪数组
      let divs3 = document.quertSelectorAll('xxx')
      console.log(divs3); // 获取的是 NodeList 节点,但也是 伪数组
      // 检测是否是数组 instanceof
      console.log(divs3 instanceof Array);
      divs3.push(123); // 通过报错检测
      
  • Array.from() 把伪数组,转成真正意义上的数组

    • ES5slice把伪数组转化为真数组
      let arr = Array.prototype.slice.call(divs3) // 它会返回一个新的数组
      console.log(arr);
      
  • Array.of() 初始化数组

    ES5:
    let arr = new Array(1, 2) // 初始化构造数组
    let arr = new Array(3) // 注意为一个值的时候 3 代表的是数组长度并不是值
    // 用 Array.of() 解决一个参数的问题
    let arr2 = Array.of(3); // [3]
    
  • copyWithin() 替换数组的元素

    • 它接受三个参数
    • target (必需):从该位置开始替换数据
    • start (可选):从该位置开始读取数据,默认为 0 。如果为负值,表示倒数
    • end (可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数
      let arr = ["哈哈", "啦啦", "哎哎", "点点", "嘿嘿", "讷讷"]
      //           0      1       2      3       4      5
      // arr.copyWithin(1, 3, 5); // 把 1 2替换到 3 4
      // arr.copyWithin(0, -2); // 把 4 5 替换到  0 1
      // arr.copyWithin(1, -2); // 把 4 5 替换到 1 2
      arr.copyWithin(4, 1); // 把 1 2 替换到 4 5
      console.log(arr);
      
  • fill() 替换填充

    • 它接受三个参数
    • 准备替换的值, start从哪里开始, end到哪里结束(不包含结束的下标)
      let arr = ["哈哈", "啦啦", "哎哎", "点点", "嘿嘿", "讷讷"]
      arr.fill('---', 2, 4);
      console.log(arr); // ["哈哈", "啦啦", "---", "---", "嘿嘿", "讷讷"]
      // 如果传一个参数,将默认为全部替换掉
      arr.fill('---');
      
  • includes()

    • ES5 写法 arr.indexOf() 检查目标数组中是否包含并返回下标,没有返回 -1
    • indexOf() 不能检测数组下的 NaN
    • includes() 直接返回bool值,并且可以判断NaN arr.includes(NaN)

注意:

js当中 console.log(NaN == NaN) // false,所以想判断数组中是否存在就用includes()

🧩ES6新的特性 - 函数的参数

  • 参数的默认值 function test(a, b = 2) {}

  • 与解构赋值结合

    • Demo:
      function ajax(url, {body = '', method = 'GET', headers = {}} = {}) {
          console.log(method); // POST
      }
      ajax('http://', {method: 'POST'})
      
  • length 属性

    • length是返回没有指定默认值的方法 Demo:
      function foo(x, y, z = 3) {
          console.log(x, y);
      }
      // length function
      console.log(foo.length); // 2
      
  • 作用域 (方法括号的作用域)

    • 面试思考题 第一个例子 Demo:
      let x = 1
      function foo(x, y = x) {
          // 受 (x, y = x) 括号的作用域的影响该值是 2
          console.log(y); // 2
      }
      foo(2);
      
    • 面试思考题 第二个例子 Demo:
      let x = 1;
      function foo(y = x) {
          let x = 2;
          // 根据作用域链,括号内的参数 x 会向外作用域找 x 定义值所以是 1
          console.log(y); // 1
      }
      foo();
      
  • 函数的name属性

    • 匿名函数用 .name 方法会获取该值 => anonymous
    • Demo:
      function foo(){}
      console.log(foo.name);
      
  • 函数的bind()方法,改变this指向

    • bind方法会创建一个函数,该函数的this指向了传入的第一个参数,当bind()的参数为空时,this指向全局对象。如浏览器中的window
    • bind().name 方法会获取该值 => bound
    • Demo:
      function foo() {
          console.log(this.name); // 三脚猫
      }
      let f = foo.bind({name: '三脚猫'});
      f();
      

扩展知识:JavaScript 中 call()、apply()、bind() 的用法,
JS中call, apply, bind 的区别 - 知乎

🧩扩展运算符 与 rest参数

  • 扩展运算符

    • 把数组或者类数组展开成用逗号隔开的值
      function foo(a, b, c) {
          console.log(a, b, c);
      }
      let arr = [1, 2, 3]
      foo(...arr)
      // 打散字符串
      let str = 'abcd'
      var arr = [...str] // ["a", "b", "c", "d"]
      
    • 数组合并 ES5 实现
      let arr1 = [1, 2, 3]
      let arr2 = [4, 5, 6]
      Array.prototype.push.apply(arr1, arr2)
      
      • 数组合并 ES6 实现
      arr1.push(...arr2)
      
  • rest参数 (rest剩余的意思)

    • 把逗号隔开的值组合成一个数组
    • 通过 arguments 实现累加Demo:
      // arguments
      function foo(x, y, z) {
          console.log(arguments); // 伪数组
          let sum = 0
          // ES5 中转化伪数组
          // Array.prototype.forEach.call(arguments, function(item) {
          //     sum += item
          // })
          // ES6 中转化伪数组
          Array.from(arguments).forEach(function (item) {
              sum += item
          })
      
          return sum
      }
      console.log(foo(1, 2)); // 3
      console.log(foo(1, 2, 3)); // 6
      
    • 通过 rest 实现累加Demo:
      function foo(...args) {
          let sum = 0
          args.forEach(function(item) {
              sum += item
          });
      
          return sum
      }
      console.log(foo(1, 2)); // 3
      console.log(foo(1, 2, 3)); // 6
      
    • 扩展写法:
      // 1. 函数 形参的使用
      function foo(x, ...args) {
          console.log(x);
          console.log(args);
      }
      foo(1, 2, 3)
      // 2. 结合 解构的使用:
      let [x, ...y] = [1, 2, 3]
      console.log(x); // 1
      console.log(y); // [2, 3]
      

🧩ES6 箭头函数

  • 写法Demo:

    • => 箭头左边是参数,右边是函数体
      let sum = function (x, y) {
          return x + y
      }
      // 由上面演变成 ↓↓↓
      // => 箭头左边是参数,右边是函数体
      let sum = (x, y) => {
          return x + y
      }  
      
      • 简写
      // 如果只有一行直接简写,默认有一个 return x + y
      let sum4 = (x, y) => x + y
      // 只有一个参数 参数括号 也可以省略
      let x = x => x
      console.log(x(123)); // 123
      
  • 箭头函数和普通函数的差别

    1. this指向定义时所在的对象,而不是调用时所在的对象
      • 其实在箭头函数里,并没有this对象,其实它的this是继承了外面上下文的this
          <button id='btn'>点我<button>
        
          let oBtn = document.querySelector('#btn')
          oBtn.addEventListener('click', function() {
              console.log(this); // <button id="btn">点我</button>
              // window.setTimeout(function() {
              //     console.log(this); // window对象
              // }, 1000)
        
              // 1. bind 改变 this 指向
              setTimeout(function() {
                console.log(this); // <button id="btn">点我</button>
              }.bind(this), 1000)
              // 2. 箭头函数改变 this 执行
              setTimeout(() => {
                console.log(this); // <button id="btn">点我</button>
              }, 1000)
          })
        
    2. 不可以当做构造函数
      /**
       * 放在 webpack 里不会报错,会转成了 ES5 执行了
       * 正常报错 People is not a constructor
       */
      let People = (name, age) => {
        this.name = name
        this.age = age
      }
      let p1 = new People('三脚猫', 18)
      console.log(p1);
      
    3. 不可以使用 arguments 对象
    // 放在 webpack 里不会报错,会转成了 ES5 执行了,应该使用 rest参数代替
    let args = () => {
      console.log(arguments);
    }
    args(1, 8)
    

🧩ES6 对象的扩展

  • ES6 属性简洁表示法

    let name = '三脚猫'
    let age = 18
    let obj = {
        name,
        age
    }
    console.log(obj); // {name: "三脚猫", age: 18}
    
  • 属性名表达式

    let name = '三脚猫'
    let age = 18
    let s = 'school'
    let obj = {
        name,
        age,
        [s]: 'Harvard' // 属性名表达式
        study() {
          console.log('我叫 ' + this.name); // 我叫 三脚猫
        }
    }
    console.log(obj);
    obj.study() // 我叫 三脚猫
    

注意:对象中不要用箭头函数定义方法,应该用ES6给出的方法定义,this会获取不到属性值

  • Object.is()

    Object.is(NaN, NaN) // true
    Object.is(obj1, obj2) // false 因为是判断的对象的栈内存的地址是否一样
    
  • 扩展运算符 与 Object.assign() // assign 用于合并对象

    let x = {
      a: 3,
      b: 4,
    }
    let y = {...x} // 扩展运算符合并对象方式
    let z = {
      a: 888
    }
    Object.assign(z, x) // 合并对象,并且后面的会覆盖前面的属性值
    
  • in 数组就是检测下标,如果是对象就是检测 是否包含某个属性

    y = {a: 1}
    console.log('a' in y); // true 是否包含某个属性
    // 检测数组
    let arr = [1, 2, 3]
    console.log(3 in arr); // false 找的是数组的 3 下标所以没有就 false
    
  • 对象的遍历方式

    let obj = {
      name: '三脚猫',
      age: 18
    }
    
    • for in 方式
      for(let key in obj) {
          console.log(key, obj[key]);
      }
      
    • Object.keys() 方式
      Object.keys(obj).forEach(key => {
          console.log(key, obj[key]);
      })
      
    • Object.getOwnPropertyNames() ES5 方式
      Object.getOwnPropertyNames(obj).forEach(key => {
          console.log(key, obj[key]);
      })
      
    • Reflect.ownKeys() ES6方法
      Reflect.ownKeys(obj).forEach(key => {
          console.log(key, obj[key]);
      })
      

🧩深拷贝与浅拷贝

  • Object.assign() 是浅拷贝 (只有一层)

    let target = {
        a: 1,
        b: {
            c: 888,
            d: 999  // 被覆盖了
        }
    }
    let source = {
        a: 1,
        b: {
            c: 3
        }
    }
    let res = Object.assign(target, source)
    console.log(res); // 结果等同于source
    
  • 深拷贝和浅拷贝的比较 (栈内存、堆内存的影响)

    • 深拷贝: b没有受影响
      let a = 2
      let b = a
      a = 10
      console.log(b); // 2
      
      • 浅拷贝: obj都被变化了
      let obj1 = {
        name: '三脚猫',
        age: 18
      }
      let obj2 = obj1 // 指向了同一块内存地址
      obj1.age = 0
      console.log(obj1);
      console.log(obj2);
      
  • 解决深拷贝的问题,复制同一个对象的方式方法

    • parse()stringify() 实现

      let obj1 = {
          name:'三脚猫',
          test: {
              'age': 18
          }
      }
      let str = JSON.stringify(obj1)
      let obj2 = JSON.parse(str)
      obj1.test.age = 88
      console.log('obj1', obj1); // age: 88
      console.log('obj2', obj2); // age: 18
      
    • 递归实现 (每一个属性赋值给新的对象/数组)

      // - 封装检查类型方法
      let checkType = data => Object.prototype.toString.call(data).slice(8, -1)
      // - 封装递归
      let deepClone = target => {
          console.log('target', target);
          let targetType = checkType(target)
          let result // 定义返回值
          if (targetType === 'Object') {
              result = {}
          }else if(targetType === 'Array') {
              result = []
          } else {
              return target // 返回当前值
          }
      
          for (let key in target) {
              let value = target[key]
              let valueType = checkType(value)
              if (valueType === 'Object' || valueType === 'Array') {
                  result[key] = deepClone(value); // 递归
              } else {
                  result[key] = value
              }
          }
      
          return result
      }
      // - 定义一个对象
      let obj1 = {
          name: '三脚猫',
          sex: 'boy',
          study: {
              game: false,
              es6: true
          }
      };
      // - 调用递归
      let obj2 = deepClone(obj1)
      obj1.study.es6 = '加油'
      console.log(obj1); // es6: 加油
      console.log(obj2); // es6: true
      

知识点:

  1. 检查数据类型不要用typeof()当检查数组和对象时,都返回的是Object
  2. 用对象原型toString().call()检查更准确js判断数据类型
  3. .slice(start, end) 字符串/数组的截取函数

🧩ES5 中的类与继承

注意:首字母大写更容易看出是类 function People()

  • 方法体本身也是 构造函数

    // 类
    function People(name, age) {
        console.log(this); // 指向实例化对象
        this.name = name
        this.age = age
        People.count++ // 使用静态属性
    }
    People.count = 0  // 定义静态属性
    // 通过 new 关键字实例化
    let p1 = new People('三脚猫', 18)
    console.log(p1);
    let p2 = new People('三脚狗', 20)
    console.log(p2);
    
  • 不要把方法定义在类里,应该定义在类的原型上

    function People(name, age) {
        this.name = name
        this.age = age
    }
    // 定义方法
    People.prototype.showName = function () {
        console.log('名字 ' + this.name);
    }
    p1.showName() // 名字 三脚 猫
    p2.showName() // 名字 三脚 狗
    
  • 静态属性,定义在类外部,可以直接用类名去访问

    // js 自带的静态方法
    Math.max() // max 是静态方法
    // 自定义静态
    People.count = 0 // 自定义静态属性
    // 定义静态方法
    People.getCount = function() {
        console.log('当前共有 ' + People.count + '次调用');
    }
    
  • ES5 中实现类的继承 (组合式继承)

    // 定义一个 动物 父类
    function Animal(name) {
        this.name = name
    }
    // 给父类定义一个方法
    Animal.prototype.showName = function () {
        console.log('名字叫:' + this.name);
    }
    // 创建 狗 的子类,准备继承父类
    function Dog(name, color) {
        // 改变 this 的指向
        Animal.call(this, name) // 第二个参数使用父类的成员属性 name 值,所以要传递进去
        this.color = color
    }
    // 修改子类原型
    // console.log(Dog.prototype); // 先查看一下 Dog 的原型
    Dog.prototype = new Animal()
    // console.log(Dog.prototype); // 改变后在查看一下
    Dog.prototype.constructor = Dog
    // console.log(Dog.prototype);
    let dog = new Dog('豆豆', '白灰') // 第一个参数使用的是父类的name
    console.log(dog);
    // 调用父类方法
    dog.showName() // 直接调用会报错,需要修改原型
    

🧩 ES6 中的类与继承

  • ES6 类的继承只是语法糖,让语法写起来很舒服,实际还是编译成了ES5

关键字:classextendsconstructorsuperstaticget / set

  • 实现一个类继承 Demo:
// 定义一个类
class People {
    constructor(name, age) {
        this.name = name
        this.age = age
        this._sex = null
    }
    showName() {
        console.log('名字:' + this.name);
    }
    // 父类定义静态方法
    static getHello() {
        return 'Hello';
    }
    // 定义 get & set
    get sex() {
        if (this._sex === 1) {
            return '女士'
        } else if (this._sex === 0) {
            return '男士'
        } else {
            return '未知'
        }
    }
    set sex(val) {
        this._sex = val
    }
}
// 定义父类的静态属性 (ES5语法)
People.world = 'World'
let str = People.getHello() + ' ' + People.world
console.log(str); // Hello World
// 实例
let _people = new People('三脚猫', 18);
console.log(_people);
_people.showName()
// 定义子类
class Coder extends People {
    constructor(name, age, company) {
        super(name, age) // 继承父类的成员属性
        this.company = company
    }
    showCompany() {
        console.log('公司是:' + this.company);
    }
}
// 实例子类
let _coder = new Coder('Harvey', 19, 'tri-footed cat')
console.log(_coder);
_coder.showCompany()
_coder.showName() // 调用父类方法
// 设置 父类 set 方法
_people.sex = 0
console.log(_people.sex);

知识点: ES6类中,定义静态属性要和ES5语法一样。

🧩ES6 - Symbol (森宝�) 独一无二的不能重复的字符串

  • 一种新的原始数据类型 (注意它不是一个对象)
    一共加上 Symbol 有7种原始数据类型

    1. undefined
    2. null
    3. bool
    4. string
    5. number
    6. object (Array 也是 object)
    7. symbol
  • 它的特性&声明方式:

    • 是独一无二的

      let s1 = Symbol()
      let s2 = Symbol()
      console.log(s1 == s2); // false
      
    • 可以把参数传进去用于描述Symbol

      let s = Symbol('foo')
      console.log(s); // Symbol(foo)
      
    • 如果参数是一个对象的话Symbol会自动调用toString()方法

      const obj = {name: '三脚猫'}
      let s = Symbol(obj)
      console.log(s); // Symbol([object object])
      
    • Symbol的方法

      • .description 获取描述

        let s = Symbol(foo)
        console.log(s.description); // foo
        
      • Symbol.for() 相当于定义在全局的环境中

        let s1 = Symbol.for('foo')
        let s2 = Symbol.for('foo') // 回去全局找有没有foo的声明,如果有就相同的用
        console.log(s1 == s2); // true
        // 全局的就算在{}作用域下也相等
        function getSymbol() {
          return Symbol.for('foo')
        }
        let s3 = getSymbol()
        console.log(s2 == s3); // true
        
      • Symbol.keyFor() 返回一个已经登记过的key

        const s1 = Symbol('foo')
        console.log(Symbol.keyFor(s1)); // undefined
        const s2 = Symbol.for('foo')
        console.log(Symbol.keyFor(s2)); // foo
        
  • 应用场景

    • 解决对象中 key 值的重复冲突问题

      const stu1 = Symbol('三脚猫')
      const stu2 = Symbol('三脚猫')
      const grade = {
          [stu1]: {address: 'yyy'}
          [stu2]: {address: 'zzz'}
      }
      // 会有两个三脚猫对应两个对象
      // Symbol(三脚猫): {address: 'yyy'} Symbol(三脚猫): {address: 'zzz'}
      console.log(grade);
      // 想要获取这种重复的key方法:
      console.log(grade[stu1]); // {address: 'yyy'}
      console.log(grade[stu2]); // {address: 'zzz'}
      
    • 在一定程度上 可以作为私有属性定义

      const sym = Symbol('imooc')
      class User {
          constructor() {
            this.name = name
            this[sym] = '三脚猫' // 定义一个变量属性
          }
          getName() {
            return this.name + this[sym]
          }
      }
      const user = new User('Harvey')
      // for in 方式取不到 sym值
      for(let key in user) {
          console.log(key); // 只有 name属性
      }
      // for of 方式也取不到 sym值
      for(let key of Object.keys(user)) {
          console.log(key); // 只有 name属性
      }
      // for of getOwnPropertySymbols() 只能取到 sym值
      for(let key of Object.getOwnPropertySymbols(user)) {
          console.log(key); // 只有 sym值
      }
      // Reflect.ownKeys() 都可以取到
      for(let key of Reflect.ownKeys(user)) {
          console.log(key); // name 和 sym 都可以取到
      }
      
    • 消除魔术字符串

      const styleType = {
          blue: Symbol(), // '蓝色' 或者 '蓝' 已经不重要了
          red: Symbol(), // '红色' 或者 '红'
      }
      function getColor(color) {
          switch (color) {
              case styleType.blue:
                  return 'this is blue'
              case styleType.red:
              default:
                  return 'this is red'
          }
      }
      let c = getColor(styleType.blue);
      console.log(c);
      

🧩ES6 - Set (一种新的数据结构)

  • 特点(没有重复的值)

    • 相对于数组来说,数组里有重复的值。set没有重复的值
    let s = new Set([1, 2, 3, 2])
    console.log(s) // Set{1, 2, 3}  没有重复的值
    
  • 一种新的数据结构

    • keyvalue 是一样的值
  • 常用方法 (并且支持链式操作)

    • .add() 添加值
    let s = new Set([1, 2, 3])
    s.add('string')
    s.add('AAA').add(123) // 链式操作
    
    • .delete() 删除值
    s.delete('2')
    
    • .clear() 清空所有值
    s.clear()
    
    • .has() 判断当前是否存在某个值
    s.has(1) // true
    
    • .size() 相当于数组的length获取元素个数
    s.size(s)  // 3
    
  • 遍历

    • forEach() 遍历
    s.forEach(item => console.log(item))
    
    • for of 三种方法都支持 .keys() .values() .entries()
    // .entries() 同时获取key和value
    for (let item of s.keys()) { // .values() set里key值等于value值
        console.log(item)
    }
    
  • 应用场景

    • 数组去重
    let arr = [1, 2, 3, 4, 3, 2]
    let s = new Set(arr)
    console.log(s) // [1, 2, 3, 4]
    
    • 合并去重
    let arr1 = [3, 4, 3, 1]
    let arr2 = [4, 3, 2]
    let s = new Set([...arr1, ...arr2]) // 扩展运算符 合并
    console.log(s) // [1, 2, 3, 4]
    console.log([...s]) // 把set 转化为数组
    console.log(Array.from(s)) // 把set 转化为数组
    
    • 获取交集
    let arr1 = [3, 4, 3, 1]
    let arr2 = [4, 3, 2]
    let s1 = new Set(arr1)
    let s2 = new Set(arr2)
    // 循环 arr1 然后用 filter过滤 .has()判断 arr2 满足条件返回
    let result = new Set(arr1.filter(item => s2.has(item)))
    console.log(result) // Set {3, 4} 输出交集重复值
    
    • 获取差集
    // 用 非 方式
    let result = new Set(arr1.filter(item => !s2.has(item)))
    console.log(result) // Set {1, 2} 输出差集
    
  • WeakSet (WeakSetSet 什么区别?)

    • WeakSet 只能存储对象
    • 不可以遍历 (垃圾回收机制)
    • 它是弱引用,对象销毁时它定义的对象也会销毁
    let ws = new WeakSet()
    ws.add({name: '三脚猫'})
    ws.delete({name: '三脚猫'})  // 存在引用地址 栈内存问题 删除不掉
    // 正确定义方式:
    const obj = {name: '三脚猫'}
    ws.add(obj)
    ws.delete(obj)
    
    • 垃圾回收机制

    每次引用变量obj引用次数都会 +1,后果是不清零会一直占用内存,多了的话会内存泄漏

    如果是WeakSet它是弱引用,当对象销毁时,它定义的对象也会跟着销毁,避免内存泄漏

注意: WeakSet删除对象时,也会存在栈内存引用地址问题,所以需要提前声明出对象赋给一个变量名

🧩ES6 - Map (一种新的数据结构)

  • 常用方法、方式

    • .set() 设置值
    // 设置键值对形式:
    let map = new Map()
    map.set('name', 'es')
    map.set('age', 18)
    // 0: {"name" => "es"}
    // 1: {"age" => 18}
    console.log(map); // set一个就是一个值
    // 设置对象是key值
    let m = new Map()
    let obj = {
      name: '三脚猫'
    }
    m.set(obj, 'es')
    // Map(1) {{…} => "es"}  {key:{name: "三脚猫"}, value: 'es'}
    console.log(m); // key 是对象, value 是 es
    
    • .get() 获取值
    console.log( m.get(obj) ); // es
    
    • .delete() 删除值
    m.delete(obj) // 返回bool值
    console.log(m);
    
    • 数组定义方式:第一个元素当做 key 第二个元素是 value
    let map = new Map([
        ['name', '三脚猫']
        // ['name', '三脚猫', 'Harvey'], // 只是两个值 key=>value,第三个值不生效
        ['age', 18]
    ])
    // 0: {"name" => "三脚猫"}
    // 1: {"age" => 18}
    console.log(map);
    console.log(map.size); // 2 获取元素个数
    console.log(map.has('name')); // true 检测是否存在key值
    console.log(map.get('age')); // 18 获取对应值
    map.set('name', 'Harvey') // 会把原有值 三脚猫 替换
    console.log(map);
    map.delete('name') // 删除键值name
    console.log(map);
    
  • 遍历

    • forEach 遍历 参数是先value 后 key
    map.forEach((value, key) => console.log(value, key))
    
    • for of 遍历 参数是先key 后 value
    // 同时也是支持 map.keys() .values() .entries() 三个方法
    for (const [key, value] of map) {
        console.log(value, key);
    }
    
  • 应用场景

    • 可以用来判断对象中否存在某个key值,不需要像Object一样去循环判断,直接.has()
    let obj = {
        name: '三脚猫'
    }
    let m = new Map(obj);
    console.log(m);
    
    • 获取对象中的键值对个数,也直接用.size()

补充:Object 两种判断key值方式

  1. Object.keys() 然后如果是深判断就需要递归比较麻烦
  2. 直接用 ObjecthasOwnProperty()方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性
  • WeakMap
    • 键名只支持 引用数据 类型 (Array、Object)
    • 不支持 .clear() 方法
    • 不支持 遍历
    • 因为不可遍历 所以也不支持 .size() 方法
    • 也是弱引用,垃圾回收机制,不管使用多少次 使用次数都是 1次,销毁后全部销毁,防止内存泄漏
    let wm = new WeakMap()
    wm.set([1], 2) // 两个参数都只能是一位
    wm.set({name: '三脚猫'}, 'es')
    console.log(wm);
    // 可以获取DOM 相应存一些 info数据
    let wm = new WeakMap()
    let elem = document.getElementsByTagName('h1')
    // 通过 `.get()` 获取到对应存储的信息
    console.log(elem);
    console.log(wm.get(elem));
    

🧩字符串的扩展

  • [了解] 字符串的Unicode表示法 (ES6 中加强了 unicode字符支持)

    • \uxxx : \u 代表这是一个 unicode,xxx 代表 码点,码点的范围是:0000~ffff
    • 在ES6中超出的码点,比如 𠮷 ,用{}去写: \u{20BB7}
    A 在ES6 之前: \u0041
    ES6 之后:\u{41} -> A
    
    • 由于ES6的改进,可以用多种方式去表示出一个字符
    比如字母说: z
    // 1. 非特殊字符表示:
    console.log('\z' === 'z'); // true
    // 2. webpack打包后,严格模式下不支持,需要拿到浏览器下查看
    // 如果 \ 后面对应的是 三个8进制的数 表示出一个字符
    console.log('\172' === 'z'); // true
    // 3. \x + 两位 16进制数 也可以表示 7A 也表示unicode的码点
    console.log('\x7A' === 'z'); // true
    // 4. unicode 表示法:
    console.log('\u007A' === 'z'); // true
    // es6 写法:
    console.log('\u{7A}' === 'z'); // true
    
  • 字符串的遍历器接口

    • for of 后面的值是可遍历的就都可以循环遍历
    for (let item of '三脚猫') {
        console.log(item);
    }
    
  • 模板字符串

    • 用 `` 反引号包裹,用 ${} 解析变量

    • 应用场景

    // 换行字符串
    const str1 = `
      <ul>
        <li>123</li>
      <ul>
    `
    // 字符串拼接
    let name = '三脚猫'
    const str2 = `我的名字是${name},我今年要冲鸭~`
    
    • 嵌套模板 实现 class 的替换
    // 定义一个判断屏幕宽度函数
    const isLargeScreen = () => {
        return true
    }
    let class1 = 'icon'
    // ES6 之前实现
    class1 += isLargeScreen() ? ' icon-big' : ' icon-small'
    console.log(class1);
    // 用 ES6 的模板字符串实现
    let class2 = `icon icon-${isLargeScreen() ? 'big' : 'small'}`
    console.log(class2);
    
    • 高阶用法: 带标签的模板字符串
    const foo = (a, b, c, d) => {
        console.log(a);
        console.log(b);
        console.log(c);
        console.log(d);
    }
    // foo(1, 2, 3, 4)
    const name = '三脚猫'
    const age = 18
    // 这种可以直接调用方法参数 第一个参数是 字符串没被替换的部分
    // b 输出 三脚猫, c 输出 18, d 输出 undefined (因为缺少一个变量)
    foo`这是${name},他的年龄是${age}岁`
    
  • String.fromCodePoint() 输出Unicode字符

    • ES5 中写法
    // ES5 中输出码点字符串 - 弊端是有些超出了码点范围
    console.log(String.fromCharCode(0x20BB7)); // ES5
    // 所以必须用 ES6 方法输出
    console.log(String.fromCodePoint(0x20BB7)); // ES6
    
  • String.prototype.includes() 是否存在某个值 返回bool

    • ES5 中写法
    // 返回字符串是否存在于某一字符串
    const str = '三脚猫'
    console.log(str.indexOf('猫')); // 2 下标,不存在是 -1
    // ES6
    console.log(str.includes('猫')); // true
    
  • String.prototype.startsWith() 是否以什么开头 返回bool

    • console.log(str.startsWith('三'));
  • String.prototype.endsWith() 是否以什么结尾 返回bool

    • console.log(str.endsWith('猫'));
  • String.prototype.repeat() 重复N次字符串

    • 在 ES5 中要重复一个字符串只能循环
    • 在 ES6 中可以用repeat方法
    const str = '三脚猫'
    const newStr = str.repeat(10) // 重复10次
    console.log(newStr);
    

🧩ES6 中的正则表达式的扩展

  • ES5 中的三个修饰符

    • i 忽略大小写
    • m 多行匹配
    • g 全局匹配
  • ES6 中添加的修饰符

    • y 修饰符,粘连修饰符
    const str = 'aaa_aa_a'
    const reg1 = /a+/g // 每次匹配剩余的
    const reg2 = /a+/y // 剩余的第一个开始匹配
    console.log(reg1.exec(str)); // aaa
    console.log(reg2.exec(str)); // aaa
    console.log(reg1.exec(str)); // aa 匹配剩余的
    console.log(reg2.exec(str)); // null 因为第二轮匹配的是 _aa 所以开头不是a
    console.log(reg1.exec(str));
    
    • u 修饰符,unicode (会把超出范围的码点当做一个字符处理,更精准准确)
    const str = '\uD842\uDFB7' // 表示一个字符
    console.log(/^\uD842/.test(str)); // es5 true ,以为es5中看成了一个字符
    console.log(/^\uD842/u.test(str)); // es6 false, 只有在es6中会看做一个字符从而匹配不到
    // . 匹配除了换行符以外的任意字符,但是如果码点超出,也会匹配不到,就必须加 u 修饰符
    console.log(/^.$/.test(str));
    console.log(/^.$/u.test(str));
    // 如果要识别码点字符,还是要加 u 的
    console.log(/\u{61}/.test('a')); // false
    console.log(/\u{61}/u.test('a')); // true
    // 再举例一种 码点超出范围的 , 如果要匹配两次
    console.log(/𠮷{2}/.test('𠮷𠮷'), '𠮷𠮷'); // false
    console.log(/𠮷{2}/u.test('𠮷𠮷'), '𠮷𠮷'); // true
    

🧩数值的扩展

  • 进制转换 十进制转二进制 二进制转十进制

    • ES5 中
    // ES5 十进制转二进制
    const a = 5
    console.log(a.toString(2)); // 把 a 转化为 二进制,输出 101
    // ES5 二进制转十进制
    const b = 101
    console.log(parseInt(b, 2)); // 把 b 当做二进制去识别,输出 5
    
    • ES6 中用 0B表示二进制 0O表示八进制
    // const test = 0101 // 在ES5中严格模式下,进制不允许前缀用0表示
    const a = 0B0101 // 带 0B 则可以识别二进制
    console.log(a); // 5
    
  • Number.isFinite() Infinity 无限的 isFinite 判断一个值是否是有限的

    console.log(Number.isFinite(5)); // true 比如 5 / 0 也是无限的 Infinity
    console.log(Number.isFinite('三脚猫')); // false
    
  • Number.isNaN() NaN: not a number isNaN 判断一个数是不是 NaN

    console.log(Number.isNaN('a' / 5)); // true
    
  • Number.parseInt() 转化为整数

    console.log(Number.parseInt(5.5));
    
  • Number.parseFloat() 转化为浮点数

    console.log(Number.parseFloat(5.00));
    
  • Number.isInteger() 判断是否是 int

    console.log(Number.isInteger(18), 'isInteger'); // true
    console.log(Number.isInteger(5.5), 'isInteger'); // false
    
  • 0.1 + 0.2 === 0.3 ??? 数字精度缺失问题

    • IEEE 754 双精度标准 存储
    • 在 ES 中整数的最大值是 2的53次方,最小值是 负2的53次方
    const max = Math.pow(2, 53)
    console.log(max); // 9007199254740992
    console.log(max + 1); // 9007199254740992 已经是最大值了 +1 也和上面一样
    console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
    
    • Number.MAX_SAFE_INTEGER 最大的安全整数 max safe integer
    • Number.MIN_SAFE_INTEGER 最小的安全证书 min safe integer
    • Number.isSafeInteger() 判断是否是安全的整数
  • Math新增方法:

    • Math.trunc()Nubmer.parseInt() 的区别?

      console.log(Math.trunc(5.5)); // 5
      console.log(Math.trunc(true)); // 1  bool值区别
      console.log(Number.parseInt(5.5)); // 5
      console.log(Number.parseInt(true)); // NaN bool值区别
      
    • Math新增方法:Math.sign() 判断参数是正数 还是 负数 或者是 0

      console.log(Math.sign(5)); // 1
      console.log(Math.sign(-5)); // -1
      console.log(Math.sign(0)); // 0
      console.log(Math.sign(true)); // 1
      console.log(Math.sign(NaN)); // NaN
      
    • Math下新增方法:Math.cbrt() 就算数值的立方根

      console.log(Math.cbrt(8)); // 2
      console.log(Math.cbrt('三脚猫')); // NaN 不能转化的都是NaN
      

🧩ES6 新特性 - Proxy (代理) 中式发音:普若C

  • ES5 代理方式:拦截对象的Api Object.defineProperty()
let obj = {}
let newVal = ''
// 第一个参数目标拦截对象 第二个参数拦截的值 第三个闭包内置get和set钩子函数(Hook)
Object.defineProperty(obj, 'name', {
    // 钩子函数 get
    get() {
        return newVal
    },
    // 钩子函数 set
    set(val) {
        // this.name = val // 这样会死循环,set设置完又set
        newVal = val
    }
})
console.log(obj.name); // ''
obj.name = 'es'
console.log(obj.name); // es
  • ES6 代理方式 Proxy
    let arr = [7, 8, 9]
    // 第一个参数是我们要包装的对象
    // 第二个参数是代理的配置
    arr = new Proxy(arr, {
        // get 获取数组中的值,如果没有返回error
        get(target, prop) {
            // in 数组就是检测下标,如果是对象就是检测 是否包含某个属性
            return prop in target ? target[prop] : 'error'
        }
    })
    console.log(arr[1]); // 8
    console.log(arr[3]); // error
    
    • 应用场景 (获取字典值)

      • get 方法
      let dict = {
          'hello': '你好',
          'world': '世界'
      }
      dict = new Proxy(dict, {
          get(target, prop) {
              // in 数组就是检测下标,如果是对象就是检测 是否包含某个属性
              return prop in target ? target[prop] : prop
          }
      })
      console.log(dict['hello']); // 你好
      console.log(dict['三脚猫']); // 三脚猫
      
      • set 方法 需要返回一个bool
      let arr = [1, 2]
      arr = new Proxy(arr, {
          // target目标数组对象,prop当前目标属性值, val要设置的值
          set(target, prop, val) {
              if (Number.isInteger(val)) {
                  target[prop] = val
                  return true
              }
              return false
          }
      })
      arr.push(5)
      console.log(arr[2]) // 5
      
      • has 方法 判断当前key是否在对象里,需要返回一个bool
      let range = {
          start: 1,
          end: 5
      }
      // 判断值是否在这个对象的区间内
      range = new Proxy(range, {
          has(target, prop) {
              return prop >= target.start && prop <= target.end
          }
      })
      console.log(5 in range);
      
      • ownKeys 循环遍历时拦截
      let userinfo = {
          username: '三脚猫',
          age: 18,
          _password: '******'
      }
      userinfo = new Proxy(userinfo, {
          ownKeys(target) {
              // 循环目标
              return Object.keys(target).filter( key => !key.startsWith('_') )
          }
      })
      // 开始循环key
      for (let key in userinfo) {
          console.log(key); // username age 没有 _password
      }
      // 对象方法也会被拦截
      console.log(Object.keys(userinfo)); // username age 没有 _password
      
    • 集合 Proxy 代理操作写一个Demo:要求是:对于下划线的字段 不允许 获取、设置、删除

      let user = {
          name: '三脚猫',
          age: 18,
          _password: '***'
      }
      user = new Proxy(user, {
          get(target, prop) {
              if (prop.startsWith('_')) {
                  throw new Error('不可访问')
              } else {
                  return target[prop]
              }
          },
          set(target, prop, val) {
              if (prop.startsWith('_')) {
                  throw new Error('不可设置')
              } else {
                  target[prop] = val
                  return true
              }
          },
          deleteProperty(target, prop) {
              if (prop.startsWith('_')) {
                  throw new Error('不可删除')
              } else {
                  delete target[prop]
                  return true
              }
          },
          ownKeys(target) {
              return Object.keys(target).filter( key => !key.startsWith('_') )
          }
      })
      // get 操作
      // console.log(user.name);
      // console.log(user._password); // 抛出异常 不可获取
      // set 操作
      // user._password = 123; // 抛出异常 不可设置
      // console.log(user);
      // delete 操作
      // console.log(delete user.age);
      // console.log(delete user._password); // 抛出异常 不可删除
      // console.log(user);
      // 拦截循环
      // for (let key in user) {
      //     console.log(key); // 没有 _password 字段
      // }
      
    • Proxy 拦截函数的调用 apply

      let sum = (...args) => {
          let num = 0
          args.forEach(item => num += item)
          return num
      }
      // 用 Proxy 拦截
      sum = new Proxy(sum, {
          // target就是函数本身, ctx上下文, args传入的值
          apply(target, ctx, args) {
              return target(...args) * 2
          }
      })
      console.log(sum(1, 2)); // 输出6 说明走了Proxy乘以2了
      
    • Proxy 拦截new一个函数类 construct

      let User = class {
          constructor(name) {
              this.name = name
          }
      }
      User = new Proxy(User, {
          // 目标对象,类的参数, 新目标
          construct(target, args, newTarget) {
              console.log('construct');
              return new target(...args)
          }
      })
      console.log(new User('三脚猫'));
      

扩展知识:

Vue2.0里实现双向数据绑定的是Object.defineProperty这样的ES5去实现的
Vue3.0里实现双向数据绑定的是用的Proxy这种拦截器

🧩ES6 中另外一个对象 Reflect (单词本意映射的意思)

下面几项是说明:Reflect 存在的目的

  • Object属于语言内部的方法放到Reflect

    • 就是 Object.defineProperty() == Reflect.defineProperty() 使用是一样使用的
    • 目的就是以前把方法都定义到了Object上了,没有分离出去,有了Reflect后面会细化分离出对象方法
  • 修改某些Obejct方法的返回结果,让其变得更合理

    • 比如定义一些不允许设置的方式时,会抛出异常 比如:
    // 如果 a 不能被代理的话,就只能用 try catch 捕获异常
    try{
        Object.defineProperty('a', {})
    } catch (e) {
        // ...
    }
    
    • 现在的新方法就可以用Reflect写,因为会返回bool
    // 会返回一个 boolean值
    if (Reflect.defineProperty('a', {})) {
        // ...
    }
    
  • Object操作变成函数行为

    • 比如说以前的写法,判断方法是否存在在某个对象下
    console.log('assign' in Object); // true
    
    • Reflect新写法:
    // 第一个参数是目标, 第二个参数是说 下面有没有这个方法
    console.log(Reflect.has(Object, 'assign')); // true
    
  • Reflect对象的方法与Proxy对象的方法一一对应

    • 比如说改了上面的Demo例子
    let user = {
        name: '三脚猫',
        age: 18,
        _password: '***'
    }
    user = new Proxy(user, {
        get(target, prop) {
            if (prop.startsWith('_')) {
                throw new Error('不可访问')
            } else {
                // return target[prop]
                // ======== 变形成 Reflect 一一对应这个Object =======
                return Reflect.get(target, prop) // 目标对象,prop要获取的值
            }
        },
        set(target, prop, val) {
            if (prop.startsWith('_')) {
                throw new Error('不可设置')
            } else {
                // target[prop] = val
                // ======== 变形成 Reflect 一一对应这个Object =======
                // target要给哪个对象设置,prop要设置的key,val要设置的值
                Reflect.set(target, prop, val)
                return true
            }
        },
        deleteProperty(target, prop) {
            if (prop.startsWith('_')) {
                throw new Error('不可删除')
            } else {
                // delete target[prop]
                // ======== 变形成 Reflect 一一对应这个Object =======
                Reflect.deleteProperty(target, prop) // 删除目标下的prop这个属性
                return true
            }
        },
        ownKeys(target) {
            // return Object.keys(target).filter( key => !key.startsWith('_') )
            // ======== 变形成 Reflect 一一对应这个Object =======
            return Reflect.ownKeys(target).filter( key => !key.startsWith('_') )
        }
    })
    
    • 变形 apply
    let sum = (...args) => {
        let num = 0
        args.forEach(item => num += item)
        return num
    }
    // 用 Proxy 拦截
    sum = new Proxy(sum, {
        // target就是函数本身, ctx上下文, args传入的值
        apply(target, ctx, args) {
            // return target(...args) * 2
            // ======== 变形成 Reflect 一一对应这个Object =======
            return Reflect.apply(target, target, [...args])
        }
    })
    console.log(sum(1, 2)); // 输出6 说明走了Proxy乘以2了
    

🧩异步操作的前置知识

  • JS是单线程的

  • 同步任务 和 异步任务
    [图片上传失败...(image-c9006c-1600260968705)]

    • 面试一个小知识点,这里的0毫秒最低最低是4毫秒执行
    setTimeout(()=>{
      console.log('Time');
    }, 0) // 这里的0毫秒最低最低是4毫秒执行
    
  • Ajax原理 全称(async javascript and xml)

    • 面试题 什么是Ajax原理? === 用原生实现一个Ajax
    function ajax(url, successCallback, failCallback) {
        // 1. 创建 `XMLHttpRequest` 对象 (IE7 以后才支持这个对象)
        var xmlHttp
        // 判断如果window下面有这个对象,判断 IE7 之后
        if (window.XMLHttpRequest) {
            xmlHttp = new XMLHttpRequest()
        } else { // 兼容 IE7 之前
            xmlHttp = new ActiveXObject('Microsoft.XMLHTTP')
        }
        // 2. 发送请求 (请求方式,地址,true为async异步 false是同步)
        xmlHttp.open('GET', url, true)
        xmlHttp.send()
        // 3. 服务端响应
        xmlHttp.onreadystatechange = function () {
            // 这里会出现三次打印分别是 readyState 2载入完成 -> 3交互 -> 4完成
            if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
                // 把接收值 转化为 一个json对象
                var obj = JSON.parse(xmlHttp.responseText)
                successCallback && successCallback(obj)
            } else if(xmlHttp.readyState === 4 && xmlHttp.status === 404) {
                // xmlHttp.readyState 响应完成都是4
                failCallback && failCallback(xmlHttp.statusText) // 这里会有错误信息
            }
        }
    }
    // 调用方法
    var url = 'http://musicapi.xiecheng.live/personalized'
    ajax(url, res => {
        console.log(res);
    })
    
  • Callback Hell (回调深渊/回调地狱) 用 ES6 - Promise 很好的解决

    • 当第二个请求结果依赖第一个请求结果时... 就以此类推多级Ajax嵌套
    • 比如说 三级联动,省市县的分类请求
    ajax(url, res => {
        console.log(res);
        ajax(url, res => {
            console.log(res);
            ajax(url, res => {
                console.log(res);
            })
        })
    })
    

🧩ES6 Promise (重要知识)

  • 主要是对异步操作的 状态管理 (异步状态管理)
  • resolve (瑞子骚)
// Promise 状态管理
console.log(1);
let p = new Promise((resolve, reject) => {
    // 在Promise中是同步操作直接输出
    console.log(2);
    var status = true
    if (status) {
        resolve() // 异步调用成功回调,最后执行
    } else {
        reject() // 执行失败后回调
    }
})
console.log(3);
// .then(音译:然后) 第一个参数必填,第二个可以不写
p.then( res => {
    console.log('成功');
}, () => {
    console.log('失败');
})

[图片上传失败...(image-51e78f-1600260968705)]

  • Promise 一共有三种状态

    • new 的时候是 pending 进行中
    • 如果成功调用 resolve 状态是 fulfilled 已成功
    • 如果失败调用 reject 状态是 rejected 已失败
    • 状态时不可逆的,一旦成功或失败是不可改变的,只有进行中是可以改变的
  • Promise解决 回调深渊/回调地狱

    • 未封装前
    // 未封装前
    new Promise((resolve, reject) => {
        ajax('static/a.json', res => {
            console.log(res);
            resolve()
        })
    }).then(res => {
        console.log('我是AAAA');
        // 当A成功后调用B
        return new Promise((resolve, reject) => { // 要把Promise return 回去
            ajax('static/b.json', res => {
                console.log(res);
                resolve()
            })
        })
    }).then(res => { // 这里是链式调用
        console.log('我是BBBB');
        // 当B成功后调用C
        // 链式调用一定要把当前Promise return 回去
        return new Promise((resolve, reject) => {
            ajax('static/c.json', res => {
                console.log(res);
                resolve()
            })
        })
    }).then(res => {
        console.log('我是CCCC');
    })
    
    • 简化封装:
    // 封装 Promise
    function getPromise(url){
        return new Promise((resolve, reject) => {
            ajax(url, res => {
                console.log(res);
                resolve(res)
            }, err => {
                reject(err)
            })
        })
    }
    // 调用函数
    getPromise('static/a.json').then(res => {
        console.log('我是A');
        return getPromise('static/b.json')
    }, err => {
        console.log(err); // 可以接收到Ajax的err错误,但是后面的还会执行
    }).then(res => {
        console.log('我是B');
        return getPromise('static/c.json')
    }).then(res => {
        console.log('我是最后一个C');
    }).catch(err => {
        console.log('统一捕获所有请求失败');
    })
    

🧩ES6 Promise 静态方法

  • .then() .catch() 都是 Promise 的实例方法,下面的都是静态方法

  • Promise.resolve()Promise.reject()

    • Demo
    // 不需要实例只是返回字符村
    let p1 = Promise.resolve('success')
    p1.then(res => {
        console.log(res);
    })
    // 不需要实例只是捕获错误返回字符串
    let p2 = Promise.reject('fail')
    p2.catch(res => {
        console.log(res);
    })
    
    • 应用场景:当有些时候并没有也不需要实例只是单纯的返回字符串/bool
    // 定义一个方法
    function foo(flag){
        if (flag) {
            return new Promise(resolve => {
                // 异步操作
                resolve('成功')
            })
        } else {
            // 错误的时候只返回一个字符串,不需要实例就用到了静态方法
            return Promise.reject('失败') // 调用静态
        }
    }
    // 调用方法
    foo(false).then(res => {
        console.log(res);   // 成功
    }).catch(res => {
        console.log(res);   // 失败
    })
    
  • Promise.all([]) 所有Promise执行完后再去做一些事情 Do something ~

    • 应用场景:比如上传到服务器三张图片,异步操作后最后全部上传后要提示图片全部上传完成
      let p1 = new Promise((resolve, reject) => {
          setTimeout(() => {
              console.log('这是第 1 张图片上传中');
              resolve('1成功')
          }, 1000);
      })
      let p2 = new Promise((resolve, reject) => {
          setTimeout(() => {
              console.log('这是第 2 张图片上传中');
              // resolve('2成功')
              reject('2失败') // 当有一个失败的话,就会直接进入失败
          }, 2000);
      })
      let p3 = new Promise((resolve, reject) => {
          setTimeout(() => {
              console.log('这是第 3 张图片上传中');
              resolve('3成功')
          }, 3000);
      })
      // 调用静态方法 all
      Promise.all([p1, p2, p3]).then(res => {
          console.log(res);
      }, err => {
          console.log(err);
      })
      
    • 应用场景:上传图片简写
      const imgArr = ['1.jpg', '2.jpg', '3.jpg']
      let promiseArr = []
      imgArr.forEach(item => {
          promiseArr.push(new Promise((resolve, reject) => {
              // 图片上传操作
              resolve()
          }))
      })
      // 调用静态
      Promise.all(promiseArr).then(res => {
          // 插入数据库操作
          console.log('图片全部上传完成');
      })
      
  • Promise.race() (瑞思) 只要一个完成就立马执行.then()报告完成

    • 应用场景:图片加载超时
      // 定义一个异步上传图片
      function getImg(){
          return new Promise((resolve, reject) => {
              let img = new Image()
              img.onload = function () {
                  resolve(img)
              }
              img.src = 'http://www.xxx.com/xx.png'
              // img.src = 'https://www.imooc.com/static/img/index/logo.png'
          })
      }
      // 定义一个异步定时器(超时器)
      function timeOut(){
          return new Promise((resolve, reject) => { 
              setTimeout(() => {
                  reject()
              }, 2000);
          })
      }
      // 调用Promise的 race 静态方法
      Promise.race([getImg(), timeOut()]).then(res => {
          console.log(res, '未超时,上传成功');
      }, err => {
          console.log(err, '图片上传超时,请重试');
      })
      

🧩ES6 Generator 也叫 生成器函数 (另一种异步解决方案) 摘呢瑞特

  • 关键词 * yield(艾尔的) .next()

    • 使用Demo
      function* foo() {
          for (let $i = 0; $i < 3; $i++) {
              yield $i
          }
      }
      // 错误写法:
      // console.log(foo().next()); // {value: 0, done: false}
      // console.log(foo().next()); // {value: 0, done: false}
      // 正确写法:
      let f = foo();
      console.log(f.next()); // {value: 0, done: false}
      console.log(f.next()); // {value: 1, done: false}
      console.log(f.next()); // {value: 2, done: false}
      console.log(f.next()); // {value: undefined, done: true}
      
  • 特点

    • 不能作为构造函数去使用Generator
    • yield 只能在 Generator 里使用,比如会报错:
      // 下面的方法会报错,因为 yield 在 forEach 闭包里使用了
      function* gen(args) {
          args.forEach(item => {
              yield item + 1
          })
      }
      
  • 深入复杂用法

    • Demo
      function* gen(x) {
          let y = 2 * (yield (x + 1))
          let z = yield (y / 3)
          return x + y + z
      }
      // 调用
      let g = gen(5)
      console.log(g.next()); // {value: 6, done: false}
      // 12 表示的是上一条 yield的返回值,所以相当于 2 * (12)
      console.log(g.next(12)); // y=24 -> yield (24 / 3) 输出 {value: 8, done: false}
      // 13 表示的是:
      // let z = yield (y / 3) -> let z = yield 13 所以 z=13
      console.log(g.next(13)); // 最终输出:y=24 + z=13 + x=5 = 42 输出 {value: 42, done: true}
      
    • 应用场景:实现一个 7 的倍数,类似 说7喝酒的游戏,说7或7的倍数 就罚喝酒
      function* count(x = 1){
          while (true) {
              if (x % 7 === 0) {
                  yield x
              }
              x++
          }
      }
      // 调用
      let n = count()
      console.log(n.next().value); // 7
      console.log(n.next().value); // 14
      console.log(n.next().value); // 21
      console.log(n.next().value); // 28
      
    • 举一反三 - 应用场景2:实现一个自增ID
      function* addId() {
          let id = 0;
          while (true) {
              yield (id + 1)
              id++
          }
      }
      // 调用
      let i = addId();
      console.log(i.next().value); // 1
      console.log(i.next().value); // 2
      console.log(i.next().value); // 3
      
  • Generator 解决 回调深渊/回调地狱

    function ajax(url, success, error){
        let xmlHttp
        if (window.XMLHttpRequest) {
            xmlHttp = new XMLHttpRequest()
        } else {
            // 麦克若 扫福特
            xmlHttp = new ActiceXObject('Microsoft.XMLHTTP')
        }
        // 发送请求
        xmlHttp.open('Get', url, true)
        xmlHttp.send()
        // 服务端响应
        xmlHttp.onreadystatechange = function () {
            if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
                // 把数值转化为对象
                let obj = JSON.parse(xmlHttp.responseText);
                success(obj)
            } else if(xmlHttp.readyState == 4 && xmlHttp.status != 200) {
                error(xmlHttp.statusText)
            }
        }
    }
    // 调用 Ajax
    function request(url) {
        ajax(url, res => {
            genData.next(res)
        })
    }
    // 定义 Generator
    function* gen(){
        let res1 = yield request('static/a.json');
        let res2 = yield request('static/b.json')
        let res3 = yield request('static/c.json')
        console.log(res1);
        console.log(res2);
        console.log(res3);
    }
    // 注意调用 Genarator 一定要赋值给变量去调用,不然堆内存会每次都不一样
    let genData = gen();
    genData.next();
    

🧩迭代器 Iterator A特瑞特

  • 懵逼术语三连:
    1.是一种接口机制,为各种不同的数据结构提供统一访问的机制
    2.主要供 for...of 消费
    3.一句话:不支持遍历的数组结构变成 可遍历

  • 可迭代协议:要具备有 Symbol.iterator

  • 迭代器协议:必须符合这个格式 :

  • return的{ next(){ return {value, done} } }

    1. 必须返回对象
    2. 实现 next 并返回对象
    3. 返回对象并有 value, done的key
  • 封装一个实现一个 iterator 方法

    // 定义一个 iterator 方法
    function makeIterator(arr) {
        // 定义一个循环索引值
        let nextIndex = 0
        // 1. 要实现返回一个对象
        return {
            // 2. 要实现一个 next() 方法,并且方法里要返回一个对象
            next() {
                // 返回的对象必须有两个参数:value done
                // if (nextIndex < arr.length) {
                //     // 继续循环
                //     return {
                //         value: arr[nextIndex++], // 先使用取值,后++运算
                //         done: false // 表示还未完成
                //     }
                // } else {
                //     return {
                //         value: undefined,
                //         done: true // 表示循环完毕
                //     }
                // }
                // 上面的判断可以 简写为:
                return nextIndex < arr.length ? {
                    value: arr[nextIndex++],
                    done: false // 表示还未完成
                } : {
                    value: undefined,
                    done: true // 表示循环完毕
                }
            }
        }
    }
    // 一定要赋值给变量,再去 .next() 调用,栈内存引用地址问题
    let it = makeIterator(['a', 'b', 'c'])
    console.log(it.next()); // {value: "a", done: false}
    console.log(it.next()); // {value: "b", done: false}
    console.log(it.next()); // {value: "c", done: false}
    console.log(it.next()); // {value: undefined, done: true}
    
  • 原生具备 iterator 接口的数据结构

    1. Array
    2. Map
    3. Set
    4. String
    5. TypedArray (作用:对于底层二进制描述用)
    6. 函数的arguments对象 (arguments是类数组)
    7. NodeList对象 (也是类数组)
  • Array 数组的 iterator 方法举例

    let arr = [1, 2, 3]
    console.log(arr); // 查看是否有 Symbol.iterator 这个方法
    // 取出这个方法 就可以像 迭代器/Generator 一样遍历
    let it = arr[Symbol.iterator]()
    console.log(it.next()); // {value: 1, done: false}
    console.log(it.next()); // {value: 2, done: false}
    console.log(it.next()); // {value: 3, done: false}
    console.log(it.next()); // {value: undefined, done: true}
    
  • Map new Map()iterator 方法举例

    let map = new Map()
    map.set('name', 'es')
    map.set('age', 18)
    // 有 Symbol(Symbol.iterator) 方法,可遍历
    console.log(map); // 0: {"name" => "es"}
    let it = map[Symbol.iterator]()
    console.log(it.next()); // {value: Array(2), done: false} 因为map是键值对
    console.log(it.next()); // {value: Array(2), done: false} 所以是数组
    console.log(it.next()); // {value: undefined, done: true}
    
  • 循环遍历一种不可遍历的对象

    • 应用场景:当所有页面都使用该对象时,不用每次都遍历出每一个键值,直接迭代器封装好。当然如果只需要一次,是可以用.去找每个对象依次循环值

      // 循环遍历一种不可遍历的对象 course 扣赛 课程
      let course = {
          allCourse: {
              frontend: ['ES', '小程序', 'Vue', 'React'],
              backend: ['Java', 'Python', 'PHP'],
              webapp: ['Android', 'ios']
          }
      }
      
      // 可迭代协议:要具备有 Symbol.iterator
      // 迭代器协议:必须符合这个格式 return的{ next(){ return {value, done} } }
      // 1. 必须返回对象
      // 2. 实现 next 并返回对象
      // 3. 返回对象并有 value, done的key
      // 开始改变
      course[Symbol.iterator] = function () {
          let allCourse = this.allCourse
          let keys = Reflect.ownKeys(allCourse) // 获取到所有的 key
          let values = []
          return {
              next() {
                  // 判断values是否为空 为空就执行真区间
                  if (!values.length) {
                      // 如果keys值都被踢出到0后 就不再踢出
                      if (keys.length) {
                          // 把当前 key 值放入 values
                          values = allCourse[keys[0]]
                          keys.shift() // 从前面踢出
                      }
                  }
                  return {
                      done: !values.length, // 这里 false 表示没有循环完成
                      value: values.shift()
                  }
              }
          }
      }
      // 循环遍历
      for (const item of course) {
          console.log(item); // ES 小程序 Vue React Java Python PHP Android ios
      }
      
    • Generator 的写法写 循环遍历一种不可遍历的对象

      • 因为 Generator 自带next()方法,并且自带done、value返回格式,所以写起来迭代器跟方便
      let course = {
          allCourse: {
              frontend: ['ES', '小程序', 'Vue', 'React'],
              backend: ['Java', 'Python', 'PHP'],
              webapp: ['Android', 'ios']
          }
      }
      // Generator 自带next()方法,并且自带done、value返回格式
      course[Symbol.iterator] = function* () {
          let allCourse = this.allCourse; // 获取key值下的所有内容
          // 获取所有 key
          let keys = Reflect.ownKeys(allCourse); // ["frontend", "backend", "webapp"]
          // 定义返回数据的数组
          let values = []
          // 无限循环 直到完成输出
          while (true) {
              if (!values.length) {
                  // 判断还有 key 可以循环
                  if (keys.length) {
                      // console.log(keys[0]); // frontend
                      values = allCourse[keys[0]] // 获取第一组 ['ES', '小程序', 'Vue', 'React']
                      keys.shift()
                      yield values.shift() // 每次输出1个元素
                  } else {
                      return false
                  } 
              } else {
                  // 当一组没有被取完就一直走这个区间
                  console.log(values, 'values有值');
                  yield values.shift() // 小程序 ...Vue ...React
              }
          }
      }
      // 循环遍历
      for (const item of course) {
          console.log(item); // ES 小程序 Vue React Java Python PHP Android ios
      }
      

[图片上传失败...(image-70b3dc-1600260968705)]

注意:因为当前没有数据没有 Symbol.iterator 这个方法,就是无法遍历的意思。

只要有该方法,遵循 iterator 协议的都可遍历。

🧩ES6 Module

  • 优势:

    1. 插件模块化
    2. 封装函数模块,提高重复使用率
    3. 解决重名,解决模块化依赖关系 按顺序加载
    4. 因为是模块,里面的变量也不会挂在在window上,也解决全局变量污染的问题
  • 导入:import、别名、类

    // 导出
    const a = 5
    class People {
        constructor(name){
            this.name = name
        }
        showName() {
            console.log(this.name);
        }
    }
    export {
        a,
        sum,
        People
    }
    // ======== module分割线 ======== //
    import {
        a as aa, // 起别名
        People
    } from './module.js'
    console.log(aa); // 5
    let p = new People('三脚猫')
    p.showName() // 三脚猫
    
  • export defult

    const a = 5
    // export default a
    // 错误写法,会报错
    // export default const a = 5
    export default a
    export c = '我是C'
    // ======== module分割线 ======== //
    // 如果是 export default导出,import的时候名字随便起,因为只有一个默认值
    import xxx, {c} from './module.js'
    console.log(xxx, c); // 5 我是C
    
  • * 导入多个

    const a = 5
    const b = 'string'
    export default{
        a,
        b,
    }
    // ======== module分割线 ======== //
    // 1. 对象访问
    import mod from './module.js'
    console.log(mod); // a: 5, b: "string"} 打来出来是一个对象
    // 2. Module 类
    import * as mod2 from './module.js'
    console.log(mod2); // Module {__esModule: true, …}... ...
    // 这里要加 default 访问
    console.log(mod2.default.a); // 5
    

🧩ES7 ECMAScript7 (2016)

  • 数组扩展
    • Array.prototype.includes(searchElement, fromIndex)
    • includes VS indexOf 什么情况下用 includes 什么情况下用 indexOf
      • 当需要判断 NaN 时只能用 includes
      • 当需要判断值是否存在并且需要返回下标用 indexOf
    // includes 会返回 -> boolean型
    const arr = ['es6', 'es7', 'es8']
    console.log(arr.includes('es7')); // true
    console.log(arr.includes('es7', 1)); // true
    console.log(arr.includes('es7', 2)); // false
    console.log(arr.includes('es7', -2)); // true
    // 检查一个数组类型,得出结论 只能判断基础数据类型,不能判断引用数据类型
    const arr2 = ['es6', ['es7', 'es8'], 'es9']
    console.log(arr2.includes(['es7', 'es8']), 'arr2'); // false
    console.log(arr2.includes('es7'), 'arr2'); // false
    console.log(arr2.indexOf(['es7', 'es8'])); // -1
    // NaN 特殊值
    const arr3 = ['es6', 'es7', NaN, 2]
    console.log(arr3.includes(NaN), 'NaN'); // true
    console.log(arr3.indexOf(NaN), 'NaN'); // -1
    // 测试严格检查
    console.log(arr3.includes('2'), '测试严格类型检查'); // false
    console.log(arr3.indexOf('2'), '测试严格类型检查'); // -1
    

🧩ES7 新特性 数值扩展->幂运算符(指数运算符)

  • 运算标识符** 等同于 Math.pow()

    console.log(Math.pow(2, 10)); // 1024
    console.log(2 ** 10); // 1024
    
  • 2的10次方 不用函数自己封装:

    function pow(x, y){
        let res = 1
        for (let i = 0; i < y; i++) {
            res *= x
        }
        return res
    }
    console.log(pow(2, 10)); // 1024
    

🧩ES8 ECMAScript8 (2017) Async Await(重要知识)

  • Async AwaitGenerator 的语法糖

    • 基本用法:
    // 定义一个异步操作
    function timeOut() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log(1, 'timeOut');
                // TODO 如果不写 resolve 就不打印输出2
                resolve(888)
            }, 1000);
        })
    }
    async function foo() {
        let res = await timeOut()
        console.log(res); // 888
        console.log(2);
    }
    foo() // 先输出 1 然后输出 888 然后输出 2
    
  • Async Await 方式解决 回调深渊

    // 用 Module 引入之前封装的 ajax
    import ajax from './ajax'
    // 封装接口调用函数
    function request(url) {
        return new Promise(resolve => {
            ajax(url, res => {
                resolve(res) // 返回请求的值
            })
        })
    }
    // 获取数据
    async function getData() {
        let res1 = await request('static/a.json')
        console.log(res1); // {a: "我是A"}
        let res2 = await request('static/b.json')
        console.log(res2); // {b: "我是B"}
        let res3 = await request('static/c.json')
        console.log(res3); // {c: "我是C"}
        // let res3 = await request('static/c.json', res3...) // 可以加参数 request(url, res)
    }
    // 调用获取数据方法
    getData()
    

🧩ES8 对象的扩展

  • Object.values()

    const obj = {
        name: '三脚猫',
        age: 18,
        course: 'es8'
    }
    // 以前的方式获取所有 value 值
    const res = Object.keys(obj).map(key => obj[key])
    console.log(res); // ["三脚猫", 18, "es8"]
    // ES8 获取
    console.log(Object.values(obj)); // ["三脚猫", 18, "es8"]
    
  • Object.entries()

    let obj2 = Object.entries(obj);
    console.log(Object.entries(189195));
    for (const [key, val] of obj2) {
        // name: 三脚猫
        // age: 18
        // course: es8
        console.log(`${key}: ${val}`);
    }
    // 转换数组 并没有太大的意义
    const arr = ['a', 'b', 'c'];
    console.log(Object.entries(arr));
    

🧩ES8 对象属性描述符

  • Object.getOwnPropertyDescriptors()
    • value 对象的值

    • writable (外特保) 表示对象属性是否可以改

    • enumerable (N牛若宝) 是否可以通过fo in循环出来

    • configurable (config若宝) 是否可以用delete运算符删除掉

    • Object.getOwnPropertyDescriptors() 使用Demo:获取对象属性

      const obj = {
          name: '三脚猫',
          course: 'es'
      }
      let desc = Object.getOwnPropertyDescriptors(obj);
      console.log(desc); // value: "三脚猫" ...writable: true ...configurable: true ...enumerable: true
      
    • 给对象设置属性

      const obj2 = {}
      // 第一个参数传入对象 第二个要给这个对象设置什么属性
      Object.defineProperty(obj2, 'name', {
          value: '三脚猫猫',
          writable: false, // name 不可被修改
          configurable: false, // 不能删除 name 属性
          enumerable: false, // 不能循环 name
      })
      Object.defineProperty(obj2, 'age', {
          value: 18,
          writable: false, // age 不可被修改
          configurable: false, // 不能删除 age 属性
          enumerable: true, // 能循环 age
      })
      obj2.name = '嗷嗷嗷' // 赋值不成功
      delete obj2.name // 删除不成功
      console.log(obj2);
      console.log('开始循环 =====');
      for (const item in obj2) {
          console.log(item); // age 因为 name 设置了不允许循环
      }
      
    • 扩展知识:获取对象下的指定属性的描述

      const obj3 = {
          name: '三脚猫',
          age: 18
      }
      console.log(Object.getOwnPropertyDescriptor(obj3, 'age'));
      

🧩ES8 (ES2017) 字符串扩展

  • String.prototype.padStart() 在开始的地方填充

    const str = '三脚猫'
    // 从开头填充8位,以什么字符串去填充
    console.log(str.padStart(8, '1234567')) // 12345三脚猫
    console.log(str.padEnd(8, 'x')) // 三脚猫xxxxx
    // 第二个参数是可选的,如果不写会以 空格 去填充
    console.log(str.padStart(10))   //        三脚猫
    
    • padStart() 应用场景
      // 应用场景1: yyyy-mm-dd 2020-04-01 里面的月份和天数 前面需要填充0
      const now = new Date() // 实例化当前日期
      const year = now.getFullYear() // 获取年
      const month = now.getMonth() + 1 // 获取月份 注意这里打印是 0~11 需要+1
      const day = now.getDate() // 获取天数
      console.log(`${year}-${month}-${day}`); // 2020-8-27
      // 补全月份和日期:
      let month2 = month.toString().padStart(2, 0)
      let day2 = day.toString().padStart(2, 0)
      console.log(`${year}-${month2}-${day2}`); // 2020-08-27
      
      // 应用场景2: 手机号 *******6789
      const tel = '18511116789'
      const newTel = tel.slice(-4).padStart(tel.length, '*')
      console.log(newTel); // *******6789
      
  • String.prototype.padEnd() 在结束的地方填充

    • padStart() 应用场景
      // 应用场景: 后端传输的时间戳m为单位,前端需要补全为13位
      console.log(new Date().getTime()) // 1598537583239 (13位 ms为单位的时间戳)
      let time = 1598537678       // 模拟后端传输      (2020-08-27 22:14:38)
      let newTime = time.toString().padEnd(13, 0) // (2020-08-27 22:14:38:000)
      console.log(time, newTime); // 1598537678 "1598537678000"
      

🧩ES8 新特性 尾逗号 Trailing commas

  • 允许函数参数列表使用尾逗号
    • 一般就是方便后续继续添加参数,git记录少一步冲突
    • 在编译的ES5的时候,还是会去掉逗号的
    • Demo:
      function foo(a, b, c,) {
          console.log(a, b, c,);
      }
      // foo(
      //  4,
      //  5,
      //  6,
      //)
      foo(4, 5, 6,) // 4 5 6
      

🧩ES9 ECMAscript9 (ES2018)

  • 异步迭代

    • for-await-of 异步迭代

    • Symbol.asyncIterator

    • 同步迭代:

      const arr = ['es6', 'es7', 'es8', 'es9']
      
      arr[Symbol.iterator] = function () {
          let nextIndex = 0
          return {
              next() {
                  return nextIndex < arr.length ? {
                      // 后++的所以 从0开始
                      value: arr[nextIndex++],
                      done: false
                  } : {
                      value: undefined,
                      done: true
                  }
              }
          }
      }
      for (let item of arr) {
          console.log(item);
      }
      
    • 异步迭代 Symbol.asyncIterator

      // 封装异步管理
      function getPromise(time) {
          return new Promise((resolve, reject) => {
              setTimeout(function() {
                  // resolve(time) 改进:
                  resolve({
                      value: time,
                      done: false
                  })
              }, time);
          })
      }
      const arr2 = [getPromise(1000), getPromise(2000), getPromise(3000)]
      // 异步迭代 Symbol.asyncIterator
      arr2[Symbol.asyncIterator] = () => {
          let nextIndex = 0
          return {
              next() {
                  return nextIndex < arr2.length ? arr2[nextIndex++] :
                      Promise.resolve({
                          value: undefined,
                          done: true
                      })
              }
          }
      }
      // ES9 新特性 for await of
      async function test() {
          for await (let item of arr2) {
              console.log(item); // 1000 2000 3000 是异步等待几秒后加载出来
          }
      }
      test()
      

🧩ES9 (2018) 正则表达式扩展

  • ES5里的修饰符:g i m ES6:y u s

  • dotAll (dot 点的意思) 就是修饰符 s

    // 普通的.匹配
    let reg = /./
    console.log(reg.test('x')); // true
    console.log(reg.test('\n')); // false
    console.log(reg.test('\r')); // false
    console.log(reg.test('\u{2028}')); // false
    // 加上 dotAll 修饰符 s 匹配
    let reg2 = /./s
    console.log(reg2.test('x')); // true
    console.log(reg2.test('\n')); // true
    console.log(reg2.test('\r')); // true
    console.log(reg2.test('\u{2028}')); // true
    
  • 具名组匹配 (?<name>)

    // 不使用之前这样写
    const reg3 = /(\d{4})-(\d{2})-(\d{2})/
    let res = reg3.exec('2020-08-31')
    console.log(res); // groups: undefined
    console.log(res[1]);  // 2020
    // 使用具名组匹配
    const reg4 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
    let res2 = reg4.exec('2020-08-31')
    // let year = res2.groups.year
    // let month = res2.groups.month
    // let day = res2.groups.day
    // 上面的用 解构赋值 简写:
    let {year, month, day} = res2.groups
    console.log(year, month, day);  // 2020 08 31
    
  • ES9 后行断言 (?<=ecma)、(?<!ecma) (相对应ES5的先行断言 (?=script)是之前就有的)

    // - 先行断言:先匹配前面的值,后面的值是否是断言值
    const str = 'ecmascript'
    console.log(str.match(/ecma(?=script)/)); 
    // ["ecma", index: 0, input: "ecmascript", groups: undefined]
    // - 后行断言:后面先确定下来,匹配前面的值
    console.log(str.match(/(?<=ecma)script/)); 
    // ["script", index: 4, input: "ecmascript", groups: undefined]
    console.log(str.match(/(?<!ecma)script/));  // 这样写是不等于ecma的
    

🧩ES9 对象扩展 (用...扩展对象)

  • Rest & Spread
// 复习之前数组合并
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const arr3 = [...arr1, ...arr2]
console.log(arr3);
// 用来 - 克隆对象
let obj1 = {
    name: '三脚猫',
    age: 18,
    money: {'RMB': 5, 'USD': 3}
}
let obj2 = {...obj1}
// console.log(obj2);
obj2.school = 'Harvard'
obj1.money.RMB = 88
console.log(obj1);
console.log(obj2); // 浅拷贝 money: {RMB: 88, USD: 3}
// 用来 - 合并对象
let obj3 = {
    sex: '男'
}
let obj4 = {...obj1, ...obj3}
console.log(obj4, 'obj4');
// 用来 - 剩余运算符
let obj5 = {
    name: '三脚猫',
    age: 18,
    school: 'Harvard',
    sex: 2
}
let {name, age, ...rest} = obj5
console.log(name); // 三脚猫
console.log(age);  // 18
console.log(rest); // {school: "Harvard", sex: 2}

🧩ES9 Promise扩展 finally()

  • Promise.prototype.finally() (finally 烦呢类 最终的意思)

    • Demo:

      new Promise((resolve, reject) => {
          // resolve('success')
          reject('fail')
      }).then(res => {
          console.log(res);
      }).catch(err => {
          console.log(err);
      }).finally(() => {
          console.log('finally');
      })
      // fail finally
      
    • 应用场景: 加载完异步后隐藏 加载中的loading

🧩ES9 字符串扩展(特性)

  • 放松了模板字符串转义序列的语法限制
    // - 只有在标签模板字符串中放松了
    const foo = arg => {
        console.log(arg);
    }
    foo`\u{61} and \u{62}` // ["a and b", raw: Array(1)]
    
    // - 模板字符串中还是未改变
    foo`\u{61} and \unicode`; // 不报错,放松了
    let str = `\u{61} and \u{62}` // 会报错
    

🧩ES10 ECMAScript10 (2019) 对象扩展

  • Object.fromEntries() 反转entries格式

    // ES8 中 Object.entries() 把对象的每一个值转化为一个数组
    const obj = {
        name: '三脚猫',
        course: 'es'
    }
    let entries = Object.entries(obj)
    console.log(entries); // [["name","三脚猫"],["course","es"]]
    // ES10 Object.fromEntries() 反转上面的entries格式
    let fromEntries = Object.fromEntries(entries);
    console.log(fromEntries); // {name: "三脚猫", course: "es"}
    
  • 应用场景 map -> 转换为对象

    const map = new Map()
    map.set('name', 'Harvey')
    map.set('study', 'ES6')
    console.log(map); // Map(2) {"name" => "Harvey", "study" => "ES6"}
    let fromEntries2 = Object.fromEntries(map);
    console.log(fromEntries2); // {name: "Harvey", study: "ES6"}
    
  • 应用场景2 找出80分以上的 (科目 和 分数)

      const course = {
          math: 80,
          english: 85,
          chinese: 90
      }
      console.log(Object.entries(course)); // [["math",80],["english",85],["chinese",90]]
      // ([key, val]) === item  结构方法获取到 val 值
      const res = Object.entries(course).filter(([key, val]) => {
          return val > 80
      })
      console.log(Object.fromEntries(res)); // {english: 85, chinese: 90}
    

🧩ES10 (2019) 字符串扩展 去掉字符串前后空格

  • String.prototype.trimStart() 去掉字符串前面的空格
  • String.prototype.trimEnd() 去掉字符串后面的空格
  • 以前只能用正则表达式的方式:str.replace(/^\s+/g, '')
    // 去掉字符串的空格
    const str = '    三脚猫   1    ';
    console.log(str);
    // 以前只能用正则表达式的方式:
    console.log(str.replace(/^\s+/g, '')); // 去掉前面的空格
    console.log(str.replace(/\s+$/g, '')); // 去掉后面的空格
    // ES10
    // 去掉前面的空格
    console.log(str.trimStart());
    console.log(str.trimLeft());
    // 去掉后面的空格
    console.log(str.trimEnd());
    console.log(str.trimRight());
    // 去掉前后的空格
    console.log(str.trim());
    

🧩ES10 (2019) 数组的扩展

  • Array.prototype.flat() 扁平化数组(拍平)

    // 多维数组 扁平化(拍平)
    const arr = [1, 2, 3, [4, 5, 6, [7, 8, 9, [10, 11, 12]]]]
    console.log(arr.flat()); // [1, 2, 3, 4, 5, 6, Array(4)]
    console.log(arr.flat().flat()); // [1, 2, 3, 4, 5, 6, 7, 8, 9, Array(3)]
    console.log(arr.flat().flat().flat()); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
    // - 深度传参 传比3大的都行
    console.log(arr.flat(3)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
    // - 无限的扁平化 Infinity
    console.log(arr.flat(Infinity)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
    
  • Array.prototype.flatMap() 循环拍平数组

    // 比如想 循环+1
    const arr2 = [1, 2, 3, 4, 5]
    // const res = arr2.map(x => x + 1)
    // const res = arr2.map(x => [x + 1]).flat() // 同上 +1 的用法
    const res = arr2.flatMap(x => [x + 1]) // 相当于结合了上面的两种方法 flat + map
    console.log(res); // [2, 3, 4, 5, 6]
    

🧩ES10 (2019) 修订Function.prototype.toString() 新特性

  • 返回源代码中的实际文本片段
    // 修订`Function.prototype.toString()`
    function foo(){
        // 这是ES10
        console.log('三脚猫');
    }
    // 以前调用 toString 只返回函数的主体,不会返回注释、空格等
    console.log(foo.toString());
    // 下面是打印出来的值:
    // function foo() {
    //  // 这是ES10
    //  console.log('三脚猫');
    // }
    

🧩ES10 (2019) 可选的 Catch Binding

  • 省略catch绑定的参数和括号

    // 封装一个方法,验证是否是 json 格式的数据
    const validJSON = json => {
        // 以前的 try catch 格式:
        // try{
        //     JSON.parse(json)
        // } catch(e){
        //     console.log(e);
        // }
        // ES10 的格式,可以省略掉 catch 的后面括号内容
        try{
            JSON.parse(json)
            return true;
        } catch {
            return false;
        }
    }
    // 定义一个 json 字符串
    const json = '{"name":"三脚猫", "course":"es"}'
    let res = validJSON(json)
    console.log(res); // true
    
  • ES10 的格式,可以省略掉 catch 的后面括号内容

    try{
        JSON.parse(json)
        return true;
    } catch {
        return false;
    }
    

🧩ES10 (2019) JSON扩展 新特性

  • JSON superset JSON 超集

    // JSON 超集 (以前的规定JSON只是ES的一个子集,早期ES不支持 行分隔符(\u2028)/段分隔符(\u2029))
    eval('var str="三脚猫";\u2029 function foo(){return str;}')
    console.log(foo());
    
  • JSON.stringify() 增强能力

    • JSON.stringify() 是有一定的解析范围的,是从 0xD800~0xDfffES10弥补了这个范围
    • 比如当下的 emoji 表情 \uD83D\uDE0E 多字节的一个字符,代表一个表情,所以它超出了范围
      // JSON.stringify() 是有一定的解析范围的,是从 0xD800~0xDfff
      // 比如当下的 emoji 表情 \uD83D\uDE0E 多字节的一个字符,代表一个表情,所以它超出了范围
      let emoji = JSON.stringify('\uD83D\uDE0E') // emoji 表情
      console.log(emoji);
      let test = JSON.stringify('\uD83D') // "\ud83d" 一半,它什么都不代表
      console.log(test);
      

🧩ES10 (2019) Symbol 扩展

  • Symbol.prototype.descriptionES10才被纳入标准,以前也可以用的
    const s = Symbol('三脚猫')
    console.log(s); // Symbol(三脚猫)
    console.log(s.description); // 三脚猫
    // description是只读属性,不能写,不能赋值
    s.description = 'es'
    console.log(s.description, '重写'); // 三脚猫 重写
    // 未定义时
    const s2 = Symbol()
    console.log(s2.description); // undefined
    

🧩ES11 (2020)

  • 全局模式捕获:String.prototype.matchAll()

    • 举例:.exec()

      const str = `
          <html>
              <body>
                  <div>第一个div</div>
                  <p>这是p</p>
                  <div>第二个div</div>
                  <span>这是span</span>
              </body>
          </html>
      `
      // exec 可以设置一个正则表达式,可以获取具名组
      // 封装一个正则方法
      function selectDiv(regExp, str) {
          let matches = [] // 匹配多个后要返回的数组
          while(true) {
              console.log(regExp.lastIndex); // 正则的底层原理:正则的索引下标
              const match = regExp.exec(str)
              if (match == null) {
                  break
              }
              matches.push(match.groups.div) // 具名组匹配
          }
          return matches
      }
      // 调用
      const regExp = /<div>(?<div>.*)<\/div>/g  // 如果不加 g 会死循环,因为每次都在第一个div循环
      const res = selectDiv(regExp, str)
      console.log(res); // ["第一个div", "第二个div"]
      
    • 举例:.match()

      // match 获取所有的匹配,多余的匹配了 <div> 标签
      console.log(str.match(regExp));  // ["<div>第一个div</div>", "<div>第二个div</div>"]
      
    • 举例:.replace()

      // replace
      function selectDiv2(regExp, str) {
          let matches = []
          str.replace(regExp, (all, first) => {
              console.log(first); // 第一个div ... 第二个div
              matches.push(first)
          })
          return matches
      }
      const res2 = selectDiv2(regExp, str)
      console.log(res2); // ["第一个div", "第二个div"]
      
    • 举例:.matchAll()

      // matchAll 注意:正则的参数必须加 g 修饰符
      function selectDiv3(regExp, str) {
          let matches = []
          for (let item of str.matchAll(regExp)) {
              matches.push(item[1])
          }
          return matches
      }
      const res3 = selectDiv3(regExp, str)
      console.log(res3); // ["第一个div", "第二个div"]
      

扩展知识:正则的底层原理lastIndex属性,正则的索引下标,寻找字符串时,会从上次的下标开始找

🧩ES11 (2020) Dynamic import() (带奶麦克)

  • 动态导入 (按需导入)

    • 大多数用于首屏 也就是用户第一次加载的时候会动态加载需要的模块
    • Vue 里的 路由懒加载 就是这样
  • 使用方式:

    const Foo = () => import('./Foo.vue')
    

🧩ES11 (2020) BigInt 新纳入的标准(新的原始数据类型)

  • 新的原始数据类型:BigInt

    // 整型的取值范围是:2 ** 53   **是ES7语法 幂运算符
    const max = 2 ** 53
    console.log(max); // 9007199254740992
    // MAX_SAFE_INTEGER 是表示最大值的常量
    console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 这个会少一个数
    console.log(max === max + 1); // true 超出了最大值怎么比较都是 true
    
  • 如果想用比int值大

    1. 第一种定义 bigInt 的方式:加 n
    const bigInt = 9007199254740993n
    console.log(bigInt); // 9007199254740993n 加一个 n 就可以输出出比最大值还大的
    console.log(typeof bigInt); // bigint
    // 在数值后面加一个 n 数值不会变化,只是类型变化了
    console.log(1n == 1); // true
    console.log(1n === 1); // false 就相当于是 bigint === number
    
    1. 第二种定义 BigInt() 方式
    const bigInt2 = BigInt(9007199254740993n)
    console.log(bigInt2); // 9007199254740993n
    
    // 测试是否会相加
    const num = bigInt + bigInt2
    console.log(num); // 18014398509481986n
    // 如果不想要 n ,只能用字符串的形式存储大的数值
    console.log(num.toString()); // 18014398509481986
    

扩展知识:不想要后面跟n就只能用字符串的形式存储,用.toString()转化

🧩ES11 (2020) Promise扩展

  • Promise.allSettled() 扩展的静态方法 赛头儿的 (allSettled 稳定的固定的)
  • allSettled() VS all()
    Promise.allSettled([
        Promise.resolve({
            code: 200,
            data: [1, 2, 3]
        }),
        Promise.reject({
            code: 500,
            data: [1, 2, 3]
        }),
        Promise.resolve({
            code: 200,
            data: [1, 2, 3]
        })
    ]).then(res => {
        // 原静态方法 .all 的调用后必须全成功
        // [
        //      {"code":200,"data":[1,2,3]},
        //      {"code":200,"data":[1,2,3]},
        //      {"code":200,"data":[1,2,3]}
        // ]
        console.log(res);
        console.log(JSON.stringify(res));
        console.log('成功');
        // 换成静态方法 .allSettled 后
        // [
        //     {"status":"fulfilled","value":{"code":200,"data":[1,2,3]}},
        //     {"status":"rejected","reason":{"code":500,"data":[1,2,3]}},
        //     {"status":"fulfilled","value":{"code":200,"data":[1,2,3]}}
        //  ]
        let success = res.filter(item => item.status == 'fulfilled')
        // [
        //     {"status":"fulfilled","value":{"code":200,"data":[1,2,3]}},
        //     {"status":"fulfilled","value":{"code":200,"data":[1,2,3]}}
        // ]
        console.log(success);
        console.log(JSON.stringify(success));
    }).catch(err => {
        // 如果有错误直接 全部走错误区间了
        console.log(err); // {"code":500,"data":[1,2,3]}
        console.log(JSON.stringify(err));
        console.log('失败');
    })
    

🧩ES11 (2020) 引入了新的对象 globalThis

  • 提供了一个标准的方式,去获取不同环境下的全局镜像

    • 写法:
      console.log(globalThis); // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}...
      
  • js 在不同环境下获取全局对象是不一样的:

    1. node 的全局对象叫 global
    2. web端 的全局对象叫 global windows === self
  • ES11 globalThis 之前,想获取任何下面的全局对象,需要判断

    const getGlobal = () => {
        // 判断当前是不是有全局对象
        if (typeof self !== 'undefined') {
            return self
        }
        if (typeof window !== 'undefined') {
            return window
        }
        if (typeof global !== 'undefined') {
            return global
        }
        // 如果都没有 抛出错误
        throw new Error('无法找到全局对象')
    }
    const global = getGlobal()
    console.log(global); // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}...
    // ES11 写法
    console.log(globalThis); // 同上相同结果
    

🧩ES11 (2020) 新特性 可选链?. (Optional chaining)

  • 可选链 (就是对象下面的属性.属性.属性,并且每次都要判断下面的属性是否存在)

    const user = {
        address: {
            street: 'xx街道',
            getNum() {
                return '88号'
            }
        }
    }
    // 获取上面的属性时,需要每次都判断
    const street = user && user.address && user.address.street
    console.log(street);
    // 细节 方法名 先不加括号是判断方法是否存在
    const num = user && user.address && user.address.getNum && user.address.getNum()
    console.log(num);
    
  • ES11 可选链

    const street2 = user ?. address ?. street
    console.log(street2); // 如果没有是 undefined
    const num2 = user ?. address ?. getNum ?. ()
    console.log(num2);
    

🧩ES11 (2020) 新特性 ?? Nullish coalescing Operator

  • 空值合并运算符 (有些时候需要给某些值设置默认值)

  • 大多数,这种情况用来判断的是 null、undefined 如果值就是 0 或 false,也会走默认值,是错误的

    const b = false
    const a = b || 5 // 如果 b 没有值,就默认给 5
    console.log(a); // 5
    
  • 用空值运算符解决

    const c = false  // 不管是 ''、false、0、'false'、'null' 都不会走默认值
    const d = c ?? 888 // 只有是 null、undefined 是默认 888
    console.log(d); // false
    

🧩常用数组/对象方法

方法名 用途 参数/说明
数组
.splice(startIndex, replaceNum, arge+) 替换/插入 数组元素 startIndex开始的下标位置
replaceNum替换的元素,如果是插入该值设为0
argeN个元素
.slice(start, end) 剪切数组 ...
.flat(Infinity) 扁平化数组 Infinity无限的扁平化
.push() 入栈 尾部新增
.pop() 出栈 尾部取出
.unshift() 头部新增 在数组头部添加新元素
.shift() 头部取出 取出数组头部元素

🧩ECMAScript 2015~2020 语法全解析

慕课网解析-文档地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,719评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,337评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,887评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,488评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,313评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,284评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,672评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,346评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,644评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,700评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,457评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,316评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,706评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,261评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,648评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,859评论 2 335

推荐阅读更多精彩内容