ES6

ES 全称 EcmaScript,是脚本语言的规范,而平时经常编写的 JavaScript,是 EcmaScript 的一种实现,

所以,ES 新特性其实指的就是 JavaScript 的新特性。        // ES 6-11,指的是 ES 的几个版本

为什么要学习新特性 ?

  • 语法简洁、功能丰富
  • 框架开发应用
  • 前端开发职位要求

EcmaScript 概述

ECMAScript 是由 Ecma 国际通过 ECMA-262 标准化的脚本程序设计语言。

ECMA( European Computer Manufacturers Association )中文名称为欧洲计算机制造商协会,这个组织的目标是评估、开发和认可电信和计算机标准。1994 年后该组织改名为 Ecma 国际。

  • Ecma 国际制定了许多标准,而 ECMA-262 只是其中的一个:

(1)ECMA-262( ECMAScript )历史版本:       // 从 ES6 开始,每年发布一个版本,版本号比年份最后一位大 1

  • 第 1  版    —    1997 年:制定了语言的基本规范
  • 第 2  版    —    1998 年:较小改动
  • 第 3  版    —    1999 年:引入正则、异常处理、格式化输出等。IE 开始支持
  • 第 4  版    —    2007 年:过于激进,未发布
  • 第 5  版    —    2009 年:引入严格模式、JSON,扩展对象、数组、原型、字符串、日期方法
  • 第 6  版    —    2015 年:模块化、面向对象语法、Promise、箭头函数、let、const、数组解构赋值等等
  • 第 7  版    —    2016 年:幂运算符、数组扩展、Async/await 关键字
  • 第 8  版    —    2017 年:Async/await、字符串扩展
  • 第 9  版    —    2018 年:对象解构赋值、正则扩展
  • 第 10 版    —    2019 年:扩展对象、数组方法
  • 第 11 版    —    2020 年:链式操作、动态导入等
  • ...

(2)谁在维护 ECMA-262

TC39( Technical Committee 39 )是推进 ECMAScript 发展的委员会。其会员都是公司( 其中主要是浏览器厂商,有苹果、谷歌、微软、因特尔等 )。TC39 定期召开会议,会议由会员公司的代表与特邀专家出席。

(3)为什么要学习 ES6

  • ES6 的版本变动内容最多,具有里程碑意义
  • ES6 加入许多新的语法特性,编程实现更简单、高效
  • ES6 是前端发展趋势,就业必备技能

(4)ES6 兼容性:http://kangax.github.io/compat-table/es6


ES6 新特性

[1] let 关键字:声明局部变量                                      // 以后声明变量,使用 let 就对了

    <script>
        let a;  // 单个声明
        let b,c,d;  // 批量声明
        let e = 100;  // 单个声明,并赋值
        let f = 200,g = 'test';  // 批量声明,并赋值
    </script>

特性:

  • 不允许重复声明
    <script>
        let dog = '小狗';
        let dog = '大狗';  // Uncaught SyntaxError: Identifier 'dog' has already been declared
    </script>
  • 块级作用域( 局部变量 )
    <script>
        {
            let cat = "猫";
            console.log(cat);  // 猫
        }
        console.log(cat);  // index.html:15 Uncaught ReferenceError: cat is not defined
    </script>
  • 不存在变量提升
    <script>
        console.log(dog);  // undefined
        console.log(cat);  // Uncaught ReferenceError: Cannot access 'cat' before initialization

        var dog = "狗";  // 存在声明提升,输出变量的默认值
        let cat = "猫";  // 不存在声明提升
    </script>
  • 不影响作用域链
    <script>
        {
            let cat = "猫";
            function fn(){
                console.log(cat);  // 猫
            }
            fn();
        }
    </script>

[2] const 关键字:声明常量                // 通常,声明对象类型使用 const,非对象类型声明选择 let

    <script>
        const DOG = '狗';
        console.log(DOG)
    </script>

特性:

  • 声明必须赋初始值
    <script>
        const DOG;
        console.log(DOG);  // Uncaught SyntaxError: Missing initializer in const declaration
    </script>
  • 常量的标识符一般大写( 纯属习惯 )
  • 常量的值不允许修改
    <script>
        const DOG = "狗";
        console.log(DOG);  // 狗
        DOG = "大狗";
        console.log(DOG);  // Uncaught TypeError: Assignment to constant variable.
    </script>
  • 不允许重复声明,同 let
  • 块级作用域( 局部变量 ),同 let
  • 对于数组和对象的元素进行修改,不算做对常量的修改,不会报错( 因为地址没有发生变化 )
    <script>
        const arr = [1,2,3]
        console.log(arr);  // [1, 2, 3]
        arr.push(4);
        console.log(arr);  // [1, 2, 3, 4]
    </script>

[3] 变量和对象的解构赋值                              // 频繁使用对象方法、数组元素,就可以使用解构赋值形式

什么是解构赋值?ES6 允许按照一定模式,从数组和对象中提取值,然后对变量进行赋值,这被称为“解构赋值”。

    <script>
        // 数组的解构赋值
        let [a,b,c] = ['大哥','二哥','三哥'];
        console.log(a);  // 大哥
        console.log(b);  // 二哥
        console.log(c);  // 三哥

        // 对象的解构赋值
        let {name,run} = {
            name:"小明",
            age:10,
            run(){
                console.log('跑步');
            }
        }
        console.log(name);  // 小明
        run();  // 跑步
    </script>

[4] 模板字符串:声明自带格式的字符串                           // 当遇到字符串和变量拼接的时候,可以使用模板字符串

模板字符串,是加强版的字符串,用反引号(``)表示,其特点为:

  • 自带格式,字符串中可用出现换行
  • 变量拼接,可用使用 ${ x } 的形式引用变量
    <script>
        let str1 = `字符串`;  
        let str2 = `字
        符
        串`;
        console.log(str1); // 字符串
        console.log(str2);  // (自带格式)
        let str3 = `这是一个${str1}`;
        console.log(str3);  // 这是一个字符串
    </script>

[5] 简化对象和函数写法

ES6 允许在大括号里,直接写入变量和函数,作为对象的属性和方法,这样的写法更为简洁。

    <script>
        let name = "小明";
        let sing = function(){
            console.log('唱歌');
        }
        let obj = {
            // 完整写法
            // name:name,
            // sing:sing,
            // 简化写法
            name,
            sing,
            age:10,
            // 方法声明(简化)
            run(){
                console.log('跑步');
            }
        }
        console.log(obj.name);  // 小明
        obj.sing();  // 唱歌
        obj.run();  // 跑步
    </script>

[6] 箭头函数:简化函数写法

ES6 允许使用箭头( => )来定义函数,箭头函数提供了一种更为简洁的函数书写方式,多用于匿名函数的定义。

    <script>
        function func1() {
            console.log('方法1');
        }
        func1();  // 方法1
        // ES6写法
        let func2 = () => {
            console.log('方法2')
        }
        func2();  // 方法2
    </script>

其特点为:

  • 箭头函数不能作为构造函数实例化;
  • 不能使用 arguments;
  • 如果形参只有一个,小括号可以省略;
  • 如果函数体只有一条语句,花括号也可以省略,函数的返回值为该条语句的执行结果;
    <script>
        let func3 = function (a, b) {
            return a + b;
        }
        console.log(func3(10, 20));  // 30
        // ES6 写法
        let func4 = (a, b) => {
            return a + b;
        }
        console.log(func4(20, 50));  // 70
    </script>
  • 箭头函数的 this 是静态的,始终指向声明时所在作用域下的 this 值;
<body>
    <div id="sec1" style="width:100px;height: 100px;background: blue;"></div>
    <div id="sec2" style="width:100px;height: 100px;background: blue;"></div>
    <script>
        // 效果:点击 div#sec,2秒后颜色变为粉色(pink)
        let sec1 = document.getElementById('sec1');
        let sec2 = document.getElementById('sec2');
        // 传统写法
        sec1.addEventListener('click',function(){
            let that = this;
            setTimeout(function(){
                // 报错
                // this.style.background = "pink";  // Uncaught TypeError: Cannot set properties of undefined (setting 'background')
                // 解决
                that.style.background = "pink";
            },2000)
        })
        // ES6 写法
        sec2.addEventListener('click',function(){
            setTimeout(() => {
                this.style.background = "red";
            },2000)
        })

    </script>
</body>

[7] ES6 中函数参数的默认值:给函数的参数设置默认值

    <script>
        // 默认值
       function func(a,b,c = 10){
           return a + b + c;
       }
       console.log(func(1,2));  // 13

       // 与解构赋值配合使用
       function connect({host="127.0.0.1",username,password,port}){
        console.log(host);  // 127.0.0.1
        console.log(username);  // root
        console.log(password);  // root
        console.log(port);  // 8080
       }
       connect({
           username:'root',
           password:'root',
           port:8080
       })
    </script>

[8] rest 参数:获取实参( 实际上是函数的剩余参数 )                                // ES6 rest 参数

    <script>
        let func1 = function () {
            console.log(arguments)  // Arguments(3) ['大哥', '二哥', '三哥', callee: ƒ, Symbol(Symbol.iterator): ƒ]
            // Rest 参数简化了使用 arguments 获取多余参数的方法
            var args = Array.prototype.slice.call(arguments);
            console.log(args); // ['大哥', '二哥', '三哥']
        }
        func1("大哥", "二哥", "三哥");  
        
        // ES6引入了rest参数( ...args ),rest参数必须放在最后,否则会报错
        let func2 = function (...args) {
            console.log(args);  // ['大哥', '二哥', '三哥']
        }
        func2("大哥", "二哥", "三哥");
        let func3 = function (a,b,...args) {
            console.log(a);  // 大哥
            console.log(args);   // ['三哥']
        }
        func3("大哥", "二哥", "三哥"); 
    </script>

[9] 扩展运算符:将一个数组转化为用逗号分隔的参数序列

扩展运算符( ... ),好比 rest 参数的逆运算,将一个数组转化为用逗号分隔的参数序列,对数组进行解包。

    <script>
        let arr = ['大哥','二哥','三哥'];
        let func = function (a, b, ...args) {
            console.log(a);
            console.log(args);
        }
        func(...arr);

        // 功能:数组合并
        let arr1 = [1,2];
        let arr2 = [3,4];
        // 传统方式
        let arr3 = arr1.concat(arr2);
        console.log(arr3);  // [1,2,3,4]
        // 使用扩展运算符
        let arr4 = [...arr1,...arr2];
        console.log(arr4);  // [1,2,3,4]
    </script>

    <script>
        let func = function(){
            console.log(arguments);  // 伪数组  // Arguments(3) ['大哥', '二哥', '三哥', callee: ƒ, Symbol(Symbol.iterator): ƒ]
            // 将伪数组转化为真正的数组
            console.log([...arguments]);  // ['大哥', '二哥', '三哥']
        }
        func('大哥','二哥','三哥');  
    </script>

[10] Symbol:表示独一无二的值                                             // Symbol 内置值的使用( 了解 )

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JS 的第 7 种数据类型,一种类似于字符串的数据类型

    <script>
        // 创建Symbol - 使用函数 Symbol() 创建
        // 方式1:
        let s1 = Symbol();  // 说明:这是一个随机的值
        console.log(s1,typeof s1);  // Symbol() 'symbol' 
        let s2 = Symbol('只是一个标识');  // Symbol(只是一个标识)
        console.log(s2);
        let s3 = Symbol('只是一个标识');  // Symbol(只是一个标识)
        console.log(s3 == s2);  // false
        // 方式2:
        let s4 = Symbol.for('只是一个标识');
        console.log(s4,typeof s4);  // Symbol(只是一个标识) 'symbol'
        let s5 = Symbol.for('只是一个标识');
        console.log(s5 == s4);  // true
    </script>

其特点为:

  • Symbol 的值是唯一的,用来解决命名冲突的问题;
    <script>
        // Symbol 的主要应用场景是给对象添加独一无二的属性和方法
        const game = {
            name:'游戏名',
            up(){
                console.log('向上')
            },
            down(){
                console.log('向下')
            }
        }
        // 把它作为一个唯一的值,用来解决命名冲突的问题
        let method = {
            up:Symbol(),
            down:Symbol()
        }
        game[method.up] = function(){
            console.log('upup')
        }
        game[method.down] = function(){
            console.log('down')
        }
        game.up();  //  向上
        game[method.up]();  // upup
    </script>
  • Symbol 的值不能与其他数据进行计算;
    <script>
        // Symbol 的值不能与其他数据类型进行计算
        let s6 = Symbol();
        // console.log(s6 + 100);  // Uncaught TypeError: Cannot convert a Symbol value to a number
        // console.log(s6 + 'test');  // Uncaught TypeError: Cannot convert a Symbol value to a string
    </script>
  • Symbol 定义的对象属性不能使用 for ... in 循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名;
  • Symbol 的内置值:除了使用自己定义的 Symbol 值外,ES6 还提供了 11 个 内置的 Symbol 值,指向语言内部使用的方法,可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行

说明:Symbol 内置值的使用,都是作为某个对象类型的属性去使用

    <script>
        class Person{
            static [Symbol.hasInstance](param){
                console.log(param);  // {name: '对象'},可以作为一个参数被传递进来
                console.log('进行类型检测了')
            }
        }
        let obj = {name:'对象'};
        // 当进行类型检测的时候就会被触发
        obj instanceof Person;  // 进行类型检测了
    </script>

[11] 迭代器:用来遍历集合、数组等

遍历器( Iterator )就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。

任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。


ES6 创造了一种新的遍历命令 for ... of 循环,Iterator 接口主要供 for ... of 消费;

原生具备 Iterator 接口的数据( 可以使用 for ... of 遍历 )有:

  • Array
  • Arguments
  • Set
  • Map
  • String
  • TypeArray
  • NodeList

工作原理:

    <script>
        const arr = ['唐僧','孙悟空','猪八戒','沙僧'];
        for(let v of arr){
            console.log(v);  // 依次打印:唐僧 孙悟空 猪八戒 沙僧
        }
        for(let i in arr){
            console.log(i);  // 依次打印:0 1 2 3
        }

        let iterator = arr[Symbol.iterator]();
        console.log(iterator.next());  // {value: '唐僧', done: false}
        console.log(iterator.next());  // {value: '孙悟空', done: false}
        console.log(iterator.next());  // {value: '猪八戒', done: false}
        console.log(iterator.next());  // {value: '沙僧', done: false}
        console.log(iterator.next());  // {value: undefined, done: true}
    </script>
  • 创建一个指针对象,指向当前数据结构的起始位置;
  • 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员;
  • 接下来不断调用 next 方法,指针一直向后移动,直到指向最后一个成员;
  • 每调用 next 方法,返回一个包含 value 和 done 属性的对象。

应用场景:需要自定义遍历数据的时候,要想到迭代器。

    <script>
        let obj = {
            name: '一班',
            member: ['小明', '小红', '小白'],
            // 自定义遍历对象
            [Symbol.iterator]: function () {
                let index = 0;
                let that = this;
                return {
                    next: function () {
                        if (index < that.member.length) {
                            const result = {
                                value: that.member[index],
                                done: false
                            }
                            index++;
                            return result;
                        }else{
                            return {value:undefined,done:true}
                        }
                    }
                }
            }
        }

        // 自定义遍历对象 — 要求:输出member中的元素
        for (let v of obj) {
            console.log(v);
        }
    </script>

[12] 生成器:一种异步编程解决方案

生成器函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完成不同。

    <script>
        // 传统函数
        function gen1(){
            console.log(111);
            console.log(222);
            console.log(333);
        }
        gen1();  // 一次打印

        // 生成器函数:在function和函数名之间加一个 *
        function * gen2(){
            console.log(111);
            console.log(222);
            console.log(333);
        }
        // gen();  // 无法调用
        // 其返回值实际是一个迭代器
        let iterator2 = gen2();
        iterator2.next();  // 可以调用

        // 同时,可以搭配yield使用:yield 函数代码的分隔符
        function * gen3(){
            console.log(111);
            yield '片段1'
            console.log(222);
            yield '片段2'
            console.log(333);
            yield '片段3'
        }
        let iterator3 = gen3();
        console.log(iterator3.next())  // 执行片段1部分:111 {value: '片段1', done: false}
        // ...
        // 因为是一个迭代器,因此可以使用 for of 遍历
        for(let v of gen3()){
            console.log(v)
        }
    </script>

生成器函数的参数传递:

    <script>
        function* gen(arg) {
            console.log(arg);
            let one = yield 111;
            console.log(one);
            let two = yield 222;
            console.log(two);
            let three = yield 333;
            console.log(three);
        }
        let iterator = gen("AAA");
        console.log(iterator.next()); // 会执行yield 111;
        // next()方法是可以传入参数的,传入的参数作为第一条(上一条)语句yield 111的返回
        console.log(iterator.next("BBB")); // 会执行yield 222; console.log(iterator.next("CCC")); // 会执行yield 333; console.log(iterator.next("DDD")); // 继续往后走,未定义;
    </script>

// 代码示例:

    <script>
        // 异步编程 文件操作 网络操作(ajax,request) 数据库操作
        // 需求:1s后控制台输出111 再过2s后控制台输出222 再过3s后控制台输出333
        // 一种做法:回调地狱
        // setTimeout(()=>{ // console.log(111); // setTimeout(()=>{
        //      console.log(222);
        //      setTimeout(()=>{
        //          console.log(333);
        //      },3000)
        //  },2000)
        // },1000)
        // 另一种做法 function one(){
        setTimeout(() => {
            console.log(111);
            iterator.next();
        }, 1000) }
        function two() {
            setTimeout(() => {
                console.log(222);
                iterator.next();
            }, 1000)
        }
        function three() {
            setTimeout(() => {
                console.log(333);
                iterator.next();
            }, 1000)
        }
        function* gen() {
            yield one();
            yield two();
            yield three();
        }
        // 调用生成器函数
        let iterator = gen(); iterator.next();
    </script>

// 代码示例:

    <script>
        // 模拟获取: 用户数据 订单数据 商品数据 
        function getUsers() {
            setTimeout(() => {
                let data = "用户数据";
                // 第二次调用next,传入参数,作为第一个的返回值 iterator.next(data); // 这里将data传入
            }, 1000);
        }
        function getOrders() {
            setTimeout(() => {
                let data = "订单数据";
                iterator.next(data); // 这里将data传入 
            }, 1000);
        }
        function getGoods() {
            setTimeout(() => {
                let data = "商品数据"; iterator.next(data); // 这里将data传入
            }, 1000);
        }
        function* gen() {
            let users = yield getUsers(); console.log(users);
            let orders = yield getOrders(); console.log(orders);
            let goods = yield getGoods(); console.log(goods); 
        }
        let iterator = gen();
        iterator.next();
    </script>

[13] Promise:非常强大的一步编程的新解决方案

Promise 是 ES6 引入的异步编程的新解决方案。

语法上 Promise 是一个构造函数,用来封装异步操作并可以获取成功或者失败的结果。


  • Promise 构造函数: Promise (excutor) {}
    <script>
        // Promise 对象的三种状态:初始化、成功、失败
        // 初始化:实例化Promise对象
        let p = new Promise(function(resolve,reject){
            let data = 123;
            // resolve(data);  // 当使用resolve返回时,为成功状态,可以使用then方法的第一个函数接收数据
            reject('出错了!')  // 当使用reject返回时,为失败状态,可以使用then方法的第二个函数接收,或者使用.catch方法接收
        })
        // then 方法
        p.then(function(value){
            console.log(value)
        },function(reason){
            console.log(reason)  // 出错了!
        })
        // catch 方法,一个语法糖,用起来更简洁一些
        p.catch(function(reason){
            console.log(reason)  // // 出错了!
        })
    </script>

代码示例:使用 Promise 封装读取文件

let fs = require('fs');

// Nodejs 常规写法
fs.readFile('./index.html', (err, data) => {
    if (err) {
        return;
    }
    console.log(data.toString())
})

// Nodejs 使用 Primise 封装写法
let p = new Promise((resolve, reject) => {
    fs.readFile('./index.html', (err, data) => {
        if (err) {
            reject(err)
        }
        resolve(data)
    })

})
p.then(value => {
    console.log(value.toString())
}).catch((err) => {
    console.log(err)
})

  • Promise.prototype.then 方法的链式调用
    <script>
        // 创建 Promise 对象
        const p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("用户数据");
            }, 1000);
        });
        // 调用then方法,then方法的返回结果是promise对象, // 对象的状态有回调函数的结果决定;
        const result = p.then(value => {
            console.log(value);
            // 1、如果回调函数中返回的结果是 非promise 类型的数据, // 状态为成功,返回值为对象的成功值resolved
            // [[PromiseStatus]]:"resolved"
            // [[PromiseValue]]:123
            // return 123;
            // 2、如果...是promise类型的数据
            // 此Promise对象的状态决定上面Promise对象p的状态
            // return new Promise((resolve,reject)=>{
            // // resolve("ok"); // resolved
            // reject("ok"); // rejected
            // });
            // 3、抛出错误
            // throw new Error("失败啦!");
            // 状态:rejected
            // value:失败啦!
        }, reason => {
            console.error(reason);
        })
        // 链式调用
        // then里面两个函数参数,可以只写一个 p.then(value=>{},reason=>{}).then(value=>{},reason=>{});
        console.log(result);
    </script>

代码示例:

        // 1、引入 fs 模块
        const fs = require("fs");
        // 2、调用方法,读取文件 - 回调地狱写法
        // fs.readFile("resources/text.txt", (err, data1) => {
        //     fs.readFile("resources/test1.txt", (err, data2) => {
        //         fs.readFile("resources/test2.txt", (err, data3) => {
        //             let result = data1 + data2 + data3;
        //             console.log(result);
        //         });
        //     });
        // });
        fs.readFile("resources/test2.txt", (err, data3) => {
            let result = data1 + data2 + data3;
            console.log(result);
        });
        // 3、使用Promise实现 - Promise 写法
        const p = new Promise((resolve, reject) => {
            fs.readFile("resources/text.txt", (err, data) => {
                resolve(data);
            });
        });
        p.then(value => {
            return new Promise((resolve, reject) => {
                fs.readFile("resources/test1.txt", (err, data) => {
                    resolve([value, data]);
                });
            })
        }).then(value => {
            return new Promise((resolve, reject) => {
                fs.readFile("resources/test2.txt", (err, data) => { // 存入数组
                    value.push(data);
                    resolve(value);
                });
            })
        }).then(value => {
            console.log(value.join("\r\n"));
        })

[14] Set 集合:类似数组,但元素不重复的集合

ES6 提供了一种新的数据结构 Set( 集合 ),它类似于数组,但成员的值都是唯一的( 没有重复 )。

集合实现了 Iterator 接口,因此,可以使用 “ 扩展运算符 ” 和 “ for ... of  ”。


集合的属性和方法有:

  • size 属性,返回集合的元素个数;
  • add() 方法,增加一个新元素,返回当前集合;
  • delete() 方法,删除一个元素,返回 boolean 值;
  • has() 方法,检测集合中是否包含某个元素,返回 boolean 值;
  • clear() 方法,清空集合,返回 undefined;
    <script>
        let s = new Set();
        console.log(s,typeof s);  // Set(0) {size: 0} 'object'

        let s1 = new Set(['大哥','二哥','三哥','三哥','大哥']) 
        // 自动去重
        console.log(s1);  // Set(3) {'大哥', '二哥', '三哥'}
        // 返回集合的元素个数
        console.log(s1.size);  // 3
        // add() 增加一个新元素,返回当前集合
        console.log(s1.add('大姐'));  // Set(4) {'大哥', '二哥', '三哥', '大姐'}
        // delete() 删除一个元素,返回boolean值
        console.log(s1.delete('二哥'));  // true
        console.log(s1);  // Set(3) {'大哥', '三哥', '大姐'}
        console.log(s1.delete('二弟'))  // false
        // has() 判断集合中是否包含某个元素,返回boolean值
        console.log(s1.has('大姐'));  // true
        console.log(s1.has('三弟'));  // false
        // clear() 清空集合,返回undefined
        console.log(s1.clear())  // undefined
        console.log(s1)  // Set(0) {size: 0}
    </script>

代码示例:

    <script>
        let arr = [1,2,3,4,5,6,5,4,3,2,1];
        // 数组去重
        let s = new Set(arr);
        console.log(s);  // Set(6) {1, 2, 3, 4, 5, 6}
        // 转化为数组
        arr = [...s];
        console.log(arr);  // [1, 2, 3, 4, 5, 6]
        // 取两个数组的交集
        let arr1 = [3,4,5,6,7,8,3,4,5]
        let s1 = new Set(arr1);
        let result1 = [];
        arr.filter(item => {
            if(s1.has(item)){
                result1.push(item);
            }
        })
        console.log(result1);  // [3, 4, 5, 6]
        // 取两个数组的并集
        let result2 = [...new Set([...arr,...arr1])];
        console.log(result2);  // [1, 2, 3, 4, 5, 6, 7, 8]
    </script>

[15] Map 集合:键值对集合        —  升级版的“对象”

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。

但是,“键”的范围并不局限于字符串,各种类型的值( 包括对象 )都可以作为键。

Map 集合也实现了 Iterator 接口,所以,可以使用 “ 扩展运算符 ” 和 “ for ... of ”。


Map 的属性和方法:

  • size 属性,返回 Map 集合的元素个数;
  • set() 方法,增加一个新元素,返回当前 Map;
  • delete() 方法,删除一个元素,返回 boolean 值;
  • get() 方法,返回键名对象对应的键值;
  • has() 方法,检测 Map 集合中是否包含某个元素,返回 boolean 值;
  • clear() 方法,清空集合,返回 undefined;
<script>
    let m1 = new Map();  // 创建一个空集合
    console.log(m1,typeof m1);  // Map(0) {size: 0} 'object'

    let m2 = new Map([
        ['name','名字'],
        ['slogan','宣传标语']
    ])
    console.log(m2);  // Map(2) {'name' => '名字', 'slogan' => '宣传标语'}
    console.log(m2.size)  // 2
    m2.set('address','地址')
    console.log(m2)  // Map(3) {'name' => '名字', 'slogan' => '宣传标语', 'address' => '地址'}
    // 删除
    m2.delete('name');
    console.log(m2);  // Map(2) {'slogan' => '宣传标语', 'address' => '地址'}
    console.log(m2.get('slogan'))  // 宣传标语
    console.log(m2.has('address'))  // true
    console.log(m2.clear());  // undefined
    console.log(m2)  // Map(0) {size: 0}
</script>

[16] Class 类:像 Java 实体类一样声明 JS 类

ES6 提供了更接近传统语言的写法,引入了 class( 类 )这个概念,作为对象的模板。

通过 class 关键字,可以定义类。

基本上,ES6 的 class 可以看作是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更加面向对象编程的语法而已。

<script>
    // ES5 构造函数写法
    function Tel(brand,price){
        this.brand = brand;
        this.price = price;
    }
    Tel.prototype.call = function(){
        console.log('拨打电话')
    }
    let xiaomi = new Tel('小米',3000);
    console.log(xiaomi.brand,xiaomi.price);  // 小米 3000
    xiaomi.call();  // 拨打电话
    // class 写法
    class Phone {
        // 构造函数
        constructor(brand,price){
            this.brand = brand;
            this.price = price;
        }
        call(){
            console.log('打电话');
        }
    }
    let huawei = new Phone('华为',5000);
    console.log(huawei.brand,huawei.price);  // 华为 5000
    huawei.call();  // 打电话
</script>

  • static 定义静态属性和方法
<script>
    class Phone{
        static name = '名字'
        static change(){
            console.log('方法执行了')
        }
        call(){
            console.log('打电话')
        }
    }
    let xiaomi = new Phone();
    console.log(xiaomi.name)  // undefined
    Phone.change();  // 方法执行了
    // xiaomi.change();  // xiaomi.change is not a function
    xiaomi.call();  // 打电话
</script>

  • class 类继承 & 对父类方法进行重写
<script>
    class Phone{
        constructor(brand,price){
            this.brand = brand;
            this.price = price;
        }
        call(){
            console.log('打电话')
        }
    }
    class SmartPhone extends Phone{
        constructor(brand,price,color){
            super(brand,price);
            this.color = color;
        }
        // 对父类方法进行重写
        call(){
            console.log('视频通话')
        }
    }
    let huawei = new SmartPhone('华为',5000,'黑色');
    console.log(huawei);  // SmartPhone {brand: '华为', price: 5000, color: '黑色'}
    huawei.call();  // 视频通话
</script>

  • class 中的 getter 和 setter 设置
<script>
    class Phone{
        get price(){
            console.log('价格被读取了')
            return 1000
        }
        set price(args){
            console.log('价格被设置了')
            this._price = args;
            // this.price = args;  // 不要这样写,因为给this.price赋值的时候,会调用 set price,这样会导致无限递归直到栈溢出。
        }
    }
    let xiaomi = new Phone();
    console.log(xiaomi.price);  // 价格被读取了 1000
    xiaomi.price = 2999;  // 价格被设置了
    console.log(xiaomi._price);  // 2999
</script>

[17] 数值扩展:增加一些数值相关的方法等

  • Number.EPSILON

Number.EPSILON 是 JavaScript 表示的最小精度;

EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16;

<script>
    // Number.EPSILON
    console.log(Number.EPSILON);  // 2.220446049250313e-16
    // 数值相加
    console.log(0.1 + 0.2);  // 0.30000000000000004
    console.log(0.1 + 0.2 == 0.3);  // false
    
    // 实际上,我们可以这样认为:如果两个值的精度相差小于 Number.EPSILON,则这两个值相等
    function equal(a,b){
        return Math.abs(a - b) < Number.EPSILON
    }
    console.log(equal(0.1 + 0.2,0.3));  // true
</script>

  • 二进制和八进制:ES6 提供了二进制和八进制数值的新的写法,分别用前缀 0b 和 0o 表示。
<script>
    // 二进制和八进制
    console.log(0b1010);  // 二进制 10
    console.log(0o777);  // 八进制 511
    console.log(100);  // 十进制 100
    console.log(0xff);  // 十六进制 255
</script>

  • Number.isFinite() 用来检查一个数值是否为有限的
<script>
    // 判断一个数值是否为有限,如果是有限数字,返回true,否则返回false
    // Number.inFinite 如果检测值不是Number类型,则返回false
    // 也就是说,只有数值类型的值,且是有穷的,才会返回true
    console.log(Number.isFinite(100));  // true
    console.log(Number.isFinite(100/0));  // false
    console.log(Number.isFinite(Infinity));  // false
</script>
  • Number.isNaN() 用来检查一个值是否为 NaN
<script>
    // 检测一个数值是否为 NAN
    // Number.isNaN 如果检测值不是 Number 类型,直接返回false
    // 也就是说,只有数值类型的值,且是NaN,才会返回true
    // 具体可以看百科中,返回 NaN 的运算
    console.log(Number.isNaN(NaN))  // true
    console.log(Number.isNaN(0/0))  // true
    console.log(Number.isNaN(123));  // false
    console.log(Number.isNaN('测试'))  // false
</script>

  • Number.parseInt() 与 Number.parseFloat()

ES6 将全局方法 parseInt 和 parseFloat,移植到 Number 对象上面,使用不变;

<script>
    // Number.parseInt() 与 Number.parseFloat()
    console.log(Number.parseInt('1234.23sdfsfd'));  // 1234
    console.log(Number.parseFloat('1234.23sdfsfd'))  // 1234.23
</script>

  • Math.trunc  用于去除一个数的小数部分,返回整数部分
<script>
    // Math.trunc 用于去除一个数的小数部分,返回整数部分
    console.log(Math.trunc('1234.23sdfsfd'));  // NaN
    console.log(Math.trunc(1234.23));  // 1234
</script>

  • Number.isInteger()  用来判断一个数值是否为整数
<script>
    // Number.isInteger() 用来判断一个数值是否为整数
    console.log(Number.isInteger(123));  // true
    console.log(Number.isInteger(123.12));  // false
    console.log(Number.isInteger('123'))  // false
</script>

[18] 对象扩展:增加一些对象相关的方法等

ES6 新增了一些 Object 对象的方法:

  • Object.is 比较两个值是否严格相等,与『===』行为基本一致(+0 与 NaN)
    <script>
        // Object.is 比较两个值是否严格相等,与『===』行为基本一致(+0 与 NaN)
        console.log(Object.is(123,123));  // true
        console.log(Object.is(123,'123'));  // false
        // 但不同的是,
        console.log(Object.is(NaN,NaN));  // true
        // NaN与任何数值做===比较都是false,跟他自己也如此
        console.log(NaN === NaN);  // false
    </script>
  • Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象
    <script>
        // Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象
        const obj1 = {
            http:'http://127.0.0.1',
            port:8080
        }
        const obj2 = {
            http:'http://127.0.0.1',
            port:8081
        }
        // 如果前后都有,后面的会覆盖前面的
        Object.assign(obj1,obj2);
        console.log(obj1);  // {http: 'http://127.0.0.1', port: 8081}
        console.log(obj2);  // {http: 'http://127.0.0.1', port: 8081}
        // 如果前边没有后边有,会添加
        Object.assign(obj1,{
            http:'http://localhost',
            port:9000,
            username:'root',
            password:'root'
        })
        console.log(obj1);  // {http: 'http://localhost', port: 9000, username: 'root', password: 'root'}
    </script>
  • proto、setPrototypeOf、 setPrototypeOf 可以直接设置对象的原型
    <script>
        // __proto__、setPrototypeOf、 getPrototypeOf 可以直接设置对象的原型
        // 这种方式,并不推荐使用
        const school = {
            name:'学校名'
        }
        const city = {
            area:['北京','杭州']
        }
        // 设置school的原型为city
        Object.setPrototypeOf(school,city);
        console.log(school)
        // 获取原型对象
        console.log(Object.getPrototypeOf(school));  // {area: Array(2)} 
    </script>

[19] 模块化 ?!简单来说就是,将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来


[20] Babel 对 ES6 模块化代码转换( 为了适配浏览器,将更新的 ES 规范转换为 ES5 规范 )

Babel 是一个 JavaScript 编译器,能够将新的ES规范语法转换成 ES5 的语法。

因为不是所有的浏览器都支持最新的 ES 规范,所以,一般项目中都需要使用 Babel 进行转换

步骤:使用 Babel 转换 JS 代码  —  打包成一个文件  —  使用时引入即可

安装工具:

  • babel-cli( 命令行工具 )
  • babel-preset-env( ES转换工具 )
  • browserify( 打包工具, 项目中使用的是 webpack )

  • 初始化项目
nom init -y
  • 安装
npm i babel-cli babel-preset-env browserify -D
  • 使用 babel 转换
npx babel js(js目录) -d dist/js(转化后的js目录) --presets=babel-preset-env
  • 打包
npx browserify dist/js/app.js -o dist/bundle.js
  • 在使用时引入 bundle.js
<script src="./js/bundle.js" type="module"></script>

ES7 新特性

  • Array.prototype.includes  用来检测数组中是否包含某元素,返回值为布尔值,语法:arr.includes(元素值)
<script>
    // 判断数组中是否包含某个元素
    let arr = [1,2,3,4,5];
    console.log(arr.includes(1));  // true
    console.log(arr.includes(6));  // false
</script>
  • 指数运算符 ** ,即:幂运算的简化写法,例如:2的10次方:2**10
<script>
    console.log(2 ** 10);  // 1024
    console.log(Math.pow(2,10));  // 1024
</script>

ES8 新特性

  • async 函数 +  await 表达式:异步函数

async 函数,其返回值为 promise 对象;而,promise 对象的结果由 async 函数执行的返回值决定

<script>
    async function func(){
        // return 123;  // Promise {<fulfilled>: 123}
            // 若报错,则返回的Promise对象也是错误的
        // throw new Error('出错啦');  // Promise {<rejected>: Error: 出错啦

            // 如果返回的是Promise对象,那么返回的结果就是Promise对象的结果
            return new Promise((resolve,reject)=>{
                resolve('成功啦');  // Promise {<pending>} 成功
            })
    }
    let result = func();
    // console.log(result)
    // 调用 then 方法
    result.then(value => {
        console.log(value)  // 成功啦
    },reason => {
        console.log(reason)
    })
</script>

await 函数,await 必须写在 async 函数中;await 右侧的表达式一般为 promise 对象;await 返回的是 promise 成功的值;如果 await 的 promise 失败了,就会抛出异常,需要通过 try ... catch 捕获处理。

<script>
    // async 函数 + await 表达式:异步函数

    // 函数调用,返回一个promise对象
    function func(){
        return new Promise((resolve,reject) => {
            let data = '成功啦';
            resolve(data);
        })
    }

    // 或者,创建一个 promise 对象
    let p = new Promise((resolve,reject) => {
        resolve('成功执行')
    })

    async function fn(){
        let res1 = await func();
        console.log(res1);  // 成功啦

        let res2 = await p;
        console.log(res2);  // 成功执行
    }
    fn();
</script>

  • 对象方法扩展
    <script>
        // Object.values()方法:返回一个给定对象的所有可枚举属性值的数组;
        // Object.entries()方法:返回一个给定对象自身可遍历属性 [key,value] 的数组;
        // Object.getOwnPropertyDescriptors() 该方法:返回指定对象所有自身属性的描述对象;

        let obj = {
            name:'名称',
            age:18
        }
        console.log(Object.keys(obj));  // ['name', 'age']  // 键
        console.log(Object.values(obj));  // ['名称', 18]  // 值
        console.log(Object.entries(obj));  // [['name', '名称'],['age', 18]]
        console.log(Object.getOwnPropertyDescriptors(obj));  // 说明:一个详细的描述对象
    </script>

ES9 新特性

  • 在对象中使Rest参数与spread扩展运算符
<script>
    // 在对象中使用扩展运算符
    function connect({host,port,...user}){
        console.log(host);  // http://127.0.0.1
        console.log(port);  // 8080
        console.log(user);  // {username: 'root', password: 'root'}
    }
    connect({
        host:'http://127.0.0.1',
        port:8080,
        username:'root',
        password:'root'
    })
</script>
<script>
    // 对象中的rest参数
    const skill1 = {
        q:'技能1'
    }
    const skill2 = {
        w:'技能2'
    }
    const skill3 = {
        e:'技能3'
    }
    const skill4 = {
        r:'技能4'
    }
    // 对象合并
    const skill = {...skill1,...skill2,...skill3,...skill4}
    console.log(skill);  // {q: '技能1', w: '技能2', e: '技能3', r: '技能4'}
</script>
  • 正则扩展:简化和增强正则匹配

ES10 新特性

  • Object.fromEntries 将二维数组或者map转换成对象
  • trimStart 和 trimEnd 去除字符串前后的空白字符
  • Array.prototype.flat 与 flatMap 将多维数组降维
  • Symbol.prototype.description 获取Symbol的字符串描述

ES11 新特性

  • String.prototype.matchAll 用来得到正则批量匹配的结果
  • 类的私有属性:私有属性外部不可访问直接
  • Promise.allSettled 获取多个promise执行的结果集
  • 可选链操作符:简化对象存在的判断逻辑
  • 动态 import 导入:动态导入模块,什么时候使用什么时候导入
  • BigInt:大整型
  • globalThis 对象:始终指向全局对象 window
<script>
    // Window {window: Window, self: Window, document: document, name: '', location: Location, …}
    console.log(globalThis);  
    console.log(window);
</script>