ES6新特性·下

39 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情

生成器

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

function * gen(){
   yield '一只没有耳朵';
   console.log(222);
   yield '一只没有尾巴';
   yield '真奇怪';
}
let iterator = gen();
console.log(iterator.next());  //一只没有耳朵 
console.log(iterator.next());   // 先打印222 再返回一只没有尾巴
console.log(iterator.next());   //真奇怪

代码说明:

  1. * 的位置没有限制
  2. 生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法返回的是yield 语句后的值
  3. yield 相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次 next方法,执行一段代码(到yield截至)
  4. next 方法可以传递实参,作为 上一个 yield 语句的返回值

生成器函数的参数

<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()); //AAA  111
        //next方法可以传入实参
        console.log(iterator.next('BBB')); // BBB 222
        console.log(iterator.next('CCC')); // CCC 333
        console.log(iterator.next('DDD')); // DDD undefine
        
    </script>

生成器函数示例

示例一:1s 后控制台输出 111 2s后输出 222 3s后输出 333(总时长6秒) 如果使用回调函数:

        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();
            },2000)
        }

        function three(){
            setTimeout(()=>{
                console.log(333);
                iterator.next();
            },3000)
        }

        function * gen(){
            yield one();
            yield two();
            yield three();
        }

        //调用生成器函数
        let iterator = gen();
        iterator.next();

这个next调用一定是在每一次回调结束后再去执行。如果最后并列着写三个next调用,那么总时长只有三秒,因为他们几乎是同一时间被加入到异步进程处理器中。

示例二: 模拟获取 用户数据 订单数据 商品数据

  <script>
        //模拟获取  用户数据  订单数据  商品数据 
        function getUsers(){
            setTimeout(()=>{
                let data = '用户数据';
                //调用 next 方法, 并且将数据传入
                iterator.next(data);
            }, 1000);
        }

        function getOrders(){
            setTimeout(()=>{
                let data = '订单数据';
                iterator.next(data);
            }, 1000)
        }

        function getGoods(){
            setTimeout(()=>{
                let data = '商品数据';
                iterator.next(data);
            }, 1000)
        }

        function * gen(){
            let users = yield getUsers();
            let orders = yield getOrders();
            let goods = yield getGoods();
        }

        //调用生成器函数
        let iterator = gen();
        iterator.next();

    </script>

Set

ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历,集合的属性和方法:

  1. size 返回集合的元素个数
  2. add 增加一个新元素,返回当前集合
  3. delete 删除元素,返回 boolean 值
  4. has 检测集合中是否包含某个元素,返回 boolean 值
  5. clear 清空集合,返回 undefined

例如:

<script>
        //声明一个 set
        let s = new Set();
        let s2 = new Set(['大事儿','小事儿','好事儿','坏事儿','小事儿']);

        //元素个数
        // console.log(s2.size);
        //添加新的元素
        // s2.add('喜事儿');
        //删除元素
        // s2.delete('坏事儿');
        //检测
        // console.log(s2.has('糟心事'));
        //清空
        // s2.clear();
        // console.log(s2);

        for(let v of s2){
            console.log(v);
        }
        
    </script>

实践

<script>
        let arr = [1,2,3,4,5,4,3,2,1];
        //1. 数组去重
        // let result = [...new Set(arr)];
        // console.log(result);
        //2. 交集
        let arr2 = [4,5,6,5,6];
        // let result = [...new Set(arr)].filter(item => {
        //     let s2 = new Set(arr2);// 4 5 6
        //     if(s2.has(item)){
        //         return true;
        //     }else{
        //         return false;
        //     }
        // });
        // let result = [...new Set(arr)].filter(item => new Set(arr2).has(item));
        // console.log(result);

        //3. 并集
        // let union = [...new Set([...arr, ...arr2])];
        // console.log(union);

        //4. 差集
        let diff = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)));
        console.log(diff);

    </script>

Map

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。Map 的属性和方法:

  1. size 返回 Map 的元素个数
  2. set 增加一个新元素,返回当前 Map
  3. get 返回键名对象的键值
  4. has 检测 Map 中是否包含某个元素,返回 boolean 值
  5. clear 清空集合,返回 undefined

例如:

<script>
        //声明 Map
        let m = new Map();

        //添加元素
        m.set('name','NEFU');
        m.set('change', function(){
            console.log("我们可以改变你!!");
        });
        let key = {
            school : 'NEFUER'
        };
        m.set(key, ['北京','上海','深圳']);  //键是对象的情况

        //size
        // console.log(m.size);

        //删除
        // m.delete('name');

        //获取
        // console.log(m.get('change'));
        // console.log(m.get(key));

        //清空
        // m.clear();

        //遍历
        for(let v of m){
            console.log(v);
        }

        // console.log(m);

    </script>

class 类

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

知识点:

  1. class 声明类
  2. constructor 定义构造函数初始化
  3. extends 继承父类
  4. super 调用父级构造方法
  5. static 定义静态方法和属性
  6. 父类方法可以重写

例如:

<script>
		//使用ES5的语法
        //手机
        function Phone(brand, price){
            this.brand = brand;
            this.price = price;
        }

        //添加方法
        Phone.prototype.call = function(){
            console.log("我可以打电话!!");
        }

        //实例化对象
        let Huawei = new Phone('华为', 5999);
        Huawei.call();
        console.log(Huawei);

        //使用ES6的class语法
        class Shouji{
            //构造方法 名字不能修改
            constructor(brand, price){
                this.brand = brand;
                this.price = price;
            }

            //方法必须使用该语法, 不能使用 ES5 的对象完整形式
            call(){
                console.log("我可以打电话!!");
            }
        }

        let onePlus = new Shouji("1+", 1999);

        console.log(onePlus);
    </script>

class里的静态成员

例如:

<script>
        // function Phone(){

        // }
        // Phone.name = '手机';
        // Phone.change = function(){
        //     console.log("我可以改变世界");
        // }
        // Phone.prototype.size = '5.5inch';

        // let nokia = new Phone();

        // console.log(nokia.name);
        // // nokia.change();
        // console.log(nokia.size);

        class Phone{
            //静态属性
            static name = '手机';
            static change(){
                console.log("我可以改变世界");
            }
        }

        let nokia = new Phone();
        console.log(nokia.name);
        console.log(Phone.name);
    </script>

实例对象和函数对象的属性是不互通的,实例对象和构造函数的原型对象(也就是Phone.prototype)是互通的。

说白了静态属性是一种类属性,他属于类,不属于实例对象,类访问得到,而对象访问不到。(跟java有些不同,java是允许的)

构造函数继承

ES5写法:

<script>
        //手机
        function Phone(brand, price){
            this.brand = brand;
            this.price = price;
        }

        Phone.prototype.call = function(){
            console.log("我可以打电话");
        }

        //智能手机
        function SmartPhone(brand, price, color, size){
            Phone.call(this, brand, price);
            this.color = color;
            this.size = size;
        }

        //设置子级构造函数的原型
        SmartPhone.prototype = new Phone;
        SmartPhone.prototype.constructor = SmartPhone;

        //声明子类的方法
        SmartPhone.prototype.photo = function(){
            console.log("我可以拍照")
        }

        SmartPhone.prototype.playGame = function(){
            console.log("我可以玩游戏");
        }

        const chuizi = new SmartPhone('锤子',2499,'黑色','5.5inch');

        console.log(chuizi);

    </script>

ES6写法:

<script>
        class Phone{
            //构造方法
            constructor(brand, price){
                this.brand = brand;
                this.price = price;
            }
            //父类的成员属性
            call(){
                console.log("我可以打电话!!");
            }
        }

        class SmartPhone extends Phone {
            //构造方法
            constructor(brand, price, color, size){
                super(brand, price);// Phone.call(this, brand, price)
                this.color = color;
                this.size = size;
            }

            photo(){
                console.log("拍照");
            }

            playGame(){
                console.log("玩游戏");
            }

            call(){
                console.log('我可以进行视频通话');
            }
        }

        const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch');
        // console.log(xiaomi);
        xiaomi.call();
        xiaomi.photo();
        xiaomi.playGame();
    </script>

子类对父类方法的重写

直接同名方法重写即可。

class里的get、set方法

<script>
        // get 和 set  
        class Phone{
            get price(){
                console.log("价格属性被读取了");
                return 'iloveyou';
            }

            set price(newVal){
                console.log('价格属性被修改了');
            }
        }

        //实例化对象
        let s = new Phone();

        // console.log(s.price); //-->价格属性被读取了  iloveyou
        s.price = 'free';  //-->价格属性被修改了
    </script>

数值扩展

  • 二进制和八进制

ES6 提供了二进制和八进制数值的新的写法,分别用前缀 0b 和 0o 表示。

  • Number.isFinite() 与 Number.isNaN()

Number.isFinite() 用来检查一个数值是否为有限的 Number.isNaN() 用来检查一个值是否为 NaN

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

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

  • Math.trunc

用于去除一个数的小数部分,返回整数部分。

  • Number.isInteger

Number.isInteger() 用来判断一个数值是否为整数

例如:

<script>
        //0. Number.EPSILON 是 JavaScript 表示的最小精度
        //EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16
        // function equal(a, b){
        //     if(Math.abs(a-b) < Number.EPSILON){
        //         return true;
        //     }else{
        //         return false;
        //     }
        // }
        // console.log(0.1 + 0.2 === 0.3);
        // console.log(equal(0.1 + 0.2, 0.3))

        //1. 二进制和八进制
        // let b = 0b1010;
        // let o = 0o777;
        // let d = 100;
        // let x = 0xff;
        // console.log(x);

        //2. Number.isFinite  检测一个数值是否为有限数
        // console.log(Number.isFinite(100));
        // console.log(Number.isFinite(100/0));
        // console.log(Number.isFinite(Infinity));
        
        //3. Number.isNaN 检测一个数值是否为 NaN 
        // console.log(Number.isNaN(123)); 

        //4. Number.parseInt Number.parseFloat字符串转整数
        // console.log(Number.parseInt('5211314love'));
        // console.log(Number.parseFloat('3.1415926神奇'));

        //5. Number.isInteger 判断一个数是否为整数
        // console.log(Number.isInteger(5));
        // console.log(Number.isInteger(2.5));

        //6. Math.trunc 将数字的小数部分抹掉  
        // console.log(Math.trunc(3.5));

        //7. Math.sign 判断一个数到底为正数 负数 还是零
        console.log(Math.sign(100));
        console.log(Math.sign(0));
        console.log(Math.sign(-20000));

    </script>

对象拓展

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

  1. Object.is 比较两个值是否严格相等,与『===』行为基本一致(+0 与 NaN)
  2. Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象
  3. _proto_、setPrototypeOf、 setPrototypeOf 可以直接设置对象的原型

例如:

<script>
        //1. Object.is 判断两个值是否完全相等 
        // console.log(Object.is(120, 120));// === 
        // console.log(Object.is(NaN, NaN));// === 
        // console.log(NaN === NaN);// === 

        //2. Object.assign 对象的合并
        // const config1 = {
        //     host: 'localhost',
        //     port: 3306,
        //     name: 'root',
        //     pass: 'root',
        //     test: 'test'
        // };
        // const config2 = {
        //     host: 'http://atguigu.com',
        //     port: 33060,
        //     name: 'atguigu.com',
        //     pass: 'iloveyou',
        //     test2: 'test2'
        // }
        // console.log(Object.assign(config1, config2));

        //3. Object.setPrototypeOf 设置原型对象  Object.getPrototypeof
        const school = {
            name: 'NEFU'
        }
        const cities = {
            xiaoqu: ['北京','上海','深圳']
        }
        Object.setPrototypeOf(school, cities);
        console.log(Object.getPrototypeOf(school));
        console.log(school);

    </script>

模块化

模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。

模块化的优势有以下几点

  1. 防止命名冲突
  2. 代码复用
  3. 高维护性

ES6 模块化语法: 模块功能主要由两个命令构成:export 和 import。

  • export 命令用于规定模块的对外接口
  • import 命令用于输入其他模块提供的功能

暴露方法汇总

①分别暴露

//位置./src/js/m1.js
//分别暴露
export let school = 'NEFU';

export function teach() {
    console.log("哈哈哈哈哈啊哈");
}
    <script type="module">
        //引入 m1.js 模块内容
        import * as m1 from "./src/js/m1.js";
        console.log(m1);
    </script>

②统一暴露

//统一暴露
let school = 'NEFU';

function findJob(){
    console.log("哈哈哈!!");
}

//
export {school, findJob};
import * as m2 from "./m2.js";
m2.findJob();

③默认暴露

//默认暴露
export default {
    school: 'NEFU',
    change: function(){
        console.log("哈哈哈!!");
    }
}
import * as m3 from "./m3.js";
m3.default.change();

默认暴露返回的是一个default对象

导入方法汇总

①通用的导入方式

        //引入 m1.js 模块内容
        import * as m1 from "./src/js/m1.js";
        // //引入 m2.js 模块内容
        import * as m2 from "./src/js/m2.js";
        // //引入 m3.js 
        import * as m3 from "./src/js/m3.js";

②解构赋值形式

import {school, teach} from "./src/js/m1.js";
import {school as sc, findJob} from "./src/js/m2.js";
import {default as m3} from "./src/js/m3.js";

如果重名可以用as重新命名 而如果使用的是默认暴露则一定要有as

③简便形式 (针对默认暴露)

import m3 from "./src/js/m3.js";

也就是直接重命名,不需要用as(反正是必须的,所以可以省略)

浏览器中使用模块化的第二种方式

原先我们都是在script标签中直接进行引入。 而第二种方式:创建一个入口文件,然后再用一个单独的script标签引入

例如:

//入口文件
//地址:./src/js/app.js
//模块引入
import * as m1 from "./m1.js";
import * as m2 from "./m2.js";
import * as m3 from "./m3.js";

引入:

<script src="./src/js/app.js" type="module"></script>

考虑到兼容性的问题,项目中一般使用babel来进行处理