美文网首页
Hello ECMAScript .

Hello ECMAScript .

作者: Super三脚猫 | 来源:发表于2020-06-28 02:04 被阅读0次

    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 语法全解析

    慕课网解析-文档地址

    相关文章

      网友评论

          本文标题:Hello ECMAScript .

          本文链接:https://www.haomeiwen.com/subject/tlnbfktx.html