12.3 ES6 新特性- Set、Map、class 类和模块化

妖狐艹你老母 2022-10-11 00:59 216阅读 0赞

文章目录

      1. Set
      1. Map
      1. class 类
      • 3.1 类的定义与声明
      • 3.2 类的属性
      • 3.3 类的方法
      • 3.4 类的实例化
      • 3.5 继承
      1. 模块化
      • 4.1 特点
      • 4.2 export 与 import
      • 4.3 import 命令的特点
      • 4.4 as 的用法
      1. 数值扩展

1. Set

ES6 提供了新的数据结构Set(集合)。类似于数组,但Set 中元素值都是唯一的Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

集合实现了iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历,集合的属性和方法:

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

Set相关属性和方法的使用

  1. let mySet = new Set();
  2. mySet.add(1); // Set [ 1 ]
  3. mySet.add(5); // Set [ 1, 5 ]
  4. mySet.add(5); // 自动去重:Set [ 1, 5 ]
  5. mySet.add("some text"); // Set [ 1, 5, "some text" ]
  6. let o = {
  7. a: 1, b: 2};
  8. mySet.add(o);
  9. mySet.add({
  10. a: 1, b: 2}); // o 指向的是不同的对象,所以没问题
  11. mySet.size; // 5
  12. mySet.has(1); // true
  13. mySet.has(3); // false
  14. mySet.has(5); // true
  15. mySet.has(Math.sqrt(25)); // true
  16. mySet.has("Some Text".toLowerCase()); // true
  17. mySet.has(o); // true
  18. mySet.delete(5); // true, 从set中移除5
  19. mySet.has(5); // false, 5已经被移除
  20. mySet.size; // 4, 刚刚移除一个值
  21. console.log(mySet);
  22. // logs Set(4) [ 1, "some text", {…}, {…} ] in Firefox
  23. // logs Set(4) { 1, "some text", {…}, {…} } in Chrome

迭代Set

  1. // 迭代整个set
  2. // 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
  3. for (let item of mySet) console.log(item);
  4. // 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
  5. for (let item of mySet.keys()) console.log(item);
  6. // 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
  7. for (let item of mySet.values()) console.log(item);
  8. // 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
  9. //(键与值相等)
  10. for (let [key, value] of mySet.entries()) console.log(key);
  11. // 用forEach迭代
  12. mySet.forEach(function(value) {
  13. console.log(value);
  14. });

Set 和 Array互换

  1. let myArray = ["value1", "value2", "value3"];
  2. // 用Set构造器将Array转换为Set
  3. let mySet = new Set(myArray);
  4. mySet.has("value1"); // returns true
  5. // 用...(展开操作符)操作符将Set转换为Array
  6. [...mySet]; // 与myArray完全一致
  7. // 使用 Array.from 转换Set为Array
  8. var myArr = Array.from(mySet);
  9. //数组去重
  10. const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
  11. console.log([...new Set(numbers)])
  12. // [2, 3, 4, 5, 6, 7, 32]

字符串相关

  1. let text = 'India';
  2. let mySet = new Set(text); // Set {'I', 'n', 'd', 'i', 'a'}
  3. mySet.size; // 5
  4. // 大小写敏感 & duplicate ommision
  5. new Set("Firefox") // Set(7) [ "F", "i", "r", "e", "f", "o", "x" ]
  6. new Set("firefox") // Set(6) [ "f", "i", "r", "e", "o", "x" ]

2. Map

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

1. Objects 和 maps 的比较

ObjectsMaps 类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此过去我们一直都把对象当成 Maps 使用。不过 MapsObjects 有一些重要的区别,在下列情况里使用 Map 会是更好的选择









































。。。。 Map Object
意外的键 Map 默认情况不包含任何键。只包含显式插入的键。 一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。注意: 虽然 ES5 开始可以用 Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。
键的类型 一个 Map的键可以是任意值,包括函数、对象或任意基本类型。 一个Object 的键必须是一个 String 或是Symbol
键的顺序 Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。 一个 Object 的键是无序的注意:自ECMAScript 2015规范以来,对象确实保留了字符串和Symbol键的创建顺序; 因此,在只有字符串键的对象上进行迭代将按插入顺序产生键。
Size Map 的键值对个数可以轻易地通过size 属性获取 Object 的键值对个数只能手动计算
迭代 Mapiterable 的,所以可以直接被迭代。 迭代一个Object需要以某种方式获取它的键然后才能迭代。
性能 在频繁增删键值对的场景下表现更好。 在频繁添加和删除键值对的场景下未作出优化。

2. Map 的属性和方法

  1. size 返回Map 的元素个数
  2. set 增加一个新元素,返回当前Map
  3. get 返回键名对象的键值
  4. delete 如果 Map 对象中存在该元素,则移除它并返回 true;否则如果该元素不存在则返回 false
  5. has 检测Map 中是否包含某个元素,返回boolean 值
  6. clear 清空集合,返回undefined

使用Map对象

  1. let myMap = new Map();
  2. let keyObj = {
  3. };
  4. let keyFunc = function() {
  5. };
  6. let keyString = 'a string';
  7. // 添加键
  8. myMap.set(keyString, "和键'a string'关联的值");
  9. myMap.set(keyObj, "和键keyObj关联的值");
  10. myMap.set(keyFunc, "和键keyFunc关联的值");
  11. myMap.size; // 3
  12. // 读取值
  13. myMap.get(keyString); // "和键'a string'关联的值"
  14. myMap.get(keyObj); // "和键keyObj关联的值"
  15. myMap.get(keyFunc); // "和键keyFunc关联的值"
  16. myMap.get('a string'); // "和键'a string'关联的值"
  17. // 因为keyString === 'a string'
  18. myMap.get({
  19. }); // undefined, 因为keyObj !== {}
  20. myMap.get(function() {
  21. }); // undefined, 因为keyFunc !== function () {}

迭代Map

  1. // 使用for..of循环来实现迭代:
  2. let myMap = new Map();
  3. myMap.set(0, "zero");
  4. myMap.set(1, "one");
  5. for (let [key, value] of myMap) {
  6. console.log(key + " = " + value); // 将会显示两个log。一个是"0 = zero"另一个是"1 = one"
  7. }
  8. for (let key of myMap.keys()) {
  9. console.log(key); // 将会显示两个log。 一个是 "0" 另一个是 "1"
  10. }
  11. for (let value of myMap.values()) {
  12. console.log(value); // 将会显示两个log。 一个是 "zero" 另一个是 "one"
  13. }
  14. //通过forEach()方法迭代:
  15. myMap.forEach(function(value, key) {
  16. console.log(key + " = " + value);
  17. })
  18. // 将会显示两个logs。 一个是 "0 = zero" 另一个是 "1 = one"

复制或合并Maps

  1. //Map 能像数组一样被复制:
  2. let original = new Map([
  3. [1, 'one']
  4. ]);
  5. let clone = new Map(original);
  6. console.log(clone.get(1)); // one
  7. console.log(original === clone); // false. 浅比较 不为同一个对象的引用
  8. //Map对象间可以进行合并,但是会保持键的唯一性。
  9. let first = new Map([
  10. [1, 'one'],
  11. [2, 'two'],
  12. [3, 'three'],
  13. ]);
  14. let second = new Map([
  15. [1, 'uno'],
  16. [2, 'dos']
  17. ]);
  18. // 合并两个Map对象时,如果有重复的键值,则后面的会覆盖前面的。
  19. // 展开运算符本质上是将Map对象转换成数组。
  20. let merged = new Map([...first, ...second]);
  21. console.log(merged.get(1)); // uno
  22. console.log(merged.get(2)); // dos
  23. console.log(merged.get(3)); // three

3. class 类

在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。class 的本质是 function。类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。

3.1 类的定义与声明

  1. // 匿名类
  2. let Example = class {
  3. constructor(a) {
  4. this.a = a;
  5. }
  6. }
  7. // 命名类
  8. let Example = class Example {
  9. constructor(a) {
  10. this.a = a;
  11. }
  12. }
  13. //类的声明
  14. class Example {
  15. constructor(a) {
  16. this.a = a;
  17. }
  18. }

注意要点

  1. 不可重复声明。
  2. 类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。
  3. 类中方法不需要 function 关键字。
  4. 方法间不能加分号。

3.2 类的属性

1. prototype:ES6 中,prototype 仍旧存在,虽然可以直接自类中定义方法,但是其实方法还是定义在 prototype 上的。 覆盖方法 / 初始化时添加方法

  1. Example.prototype={
  2. //methods
  3. }
  4. //添加方法
  5. Object.assign(Example.prototype,{
  6. //methods
  7. })

2. 静态属性:class 本身的属性,即直接定义在类内部的属性( Class.propname ),不需要实例化。 ES6 中规定,Class 内部只有静态方法,没有静态属性。

  1. class Example {
  2. // 新提案
  3. static a = 2;
  4. }
  5. // 目前可行写法
  6. Example.b = 2;

3. 公共属性

  1. class Example{
  2. }
  3. Example.prototype.a = 2;

4. 实例属性:定义在实例对象( this )上的属性。

  1. //实例属性除了定义在constructor()方法里面的this上面,也可以定义在类的最顶层。
  2. class Example {
  3. a = 2;
  4. constructor () {
  5. this._count = 0;
  6. console.log(this.a);
  7. }
  8. }
  9. //上面代码中,a 就是Example的实例属性。在Example的实例上,可以读取这个属性。

5. name 属性:返回跟在 class 后的类名(存在时)。

  1. let Example = class Exam {
  2. constructor(a) {
  3. this.a = a;
  4. }
  5. }
  6. console.log(Example.name); // Exam
  7. let Example=class {
  8. constructor(a) {
  9. this.a = a;
  10. }
  11. }
  12. console.log(Example.name); // Example

3.3 类的方法

类的所有方法都定义在类的prototype属性上面。

在类的实例上面调用方法,其实就是调用原型上的方法。bB类的实例,它的constructor方法就是B类原型的constructor方法。

  1. class B {
  2. }
  3. let b = new B();
  4. b.constructor === B.prototype.constructor // true

类的所有实例共享一个原型对象。p1p2都是Point的实例,它们的原型都是Point.prototype,所以__proto__属性是相等的。

  1. var p1 = new Point(2,3);
  2. var p2 = new Point(3,2);
  3. p1.__proto__ === p2.__proto__ //true

1. constructor 方法:constructor 方法是类的默认方法,创建类的实例化对象时被调用。

  • 在使用new关键字生成对象时,constructor方法会被执行,最终return的结果就是生成的对象实例
  • 当一个类没有constructor方法时会自动生成一个空的constructor方法,返回结果为空。
  • 用new关键字实例化对象时传入的参数会做为constructor构造函数的参数传入

    class Example{

    1. constructor(){
    2. console.log('我是constructor');
    3. }

    }
    new Example(); // 我是constructor

2. 静态方法:该方法不会实例被继承,而是直接通过类来调用

静态方法只能在当前类上调用,不能被该类的实例对象调用。父类的静态方法可以被子类继承。

因此静态方法被调用的方式一共有三种(三种调用方式都在下面一段代码中使用到了,请耐心阅读):

  • 父类直接调用
  • 子类继承父类后调用
  • 子类通过super对象调用

    class Foo {

    static classMethod() {

    1. return 'hello';

    }
    }
    //父类直接调用
    Foo.classMethod(); //hello
    //子类继承父类后调用
    class Bar extends Foo {

    }
    Bar.classMethod(); //hell
    //子类通过super对象调用
    class Cla extends Foo {

    1. return super.classMethod(); //hello

    }

3. 原型方法

  1. class Example {
  2. sum(a, b) {
  3. console.log(a + b);
  4. }
  5. }
  6. let exam = new Example();
  7. exam.sum(1, 2); // 3

4. 实例方法

  1. class Example {
  2. constructor() {
  3. this.sum = (a, b) => {
  4. console.log(a + b);
  5. }
  6. }
  7. }

3.4 类的实例化

new:class 的实例化必须通过 new 关键字。

  1. lass Example {
  2. constructor(a, b) {
  3. this.a = a;
  4. this.b = b;
  5. console.log('Example');
  6. }
  7. sum() {
  8. return this.a + this.b;
  9. }
  10. }
  11. //共享原型对象
  12. let exam1 = new Example(2, 1);
  13. let exam2 = new Example(3, 1);
  14. console.log(exam1._proto_ == exam2._proto_); // true
  15. exam1._proto_.sub = function() {
  16. return this.a - this.b;
  17. }
  18. console.log(exam1.sub()); // 1
  19. console.log(exam2.sub()); // 2

3.5 继承

通过 extends 实现类的继承。

  1. class Calculator {
  2. constructor(a,b){
  3. this.a = a;
  4. this.b = b;
  5. }
  6. add(a,b){
  7. return ("相加結果:"+ (a+b))
  8. }
  9. }
  10. class Son extends Calculator{
  11. add(a,b){
  12. console.log(super.add(a,b)) //super.add(a,b)就是调用父类中的普通函数
  13. }
  14. }
  15. var rt = new Son()
  16. rt.add(3, 4)

子类 constructor 方法中必须有 super且必须出现在 this 之前

  1. class Father {
  2. constructor() {
  3. }
  4. }
  5. class Child extends Father {
  6. constructor() {
  7. }
  8. // or
  9. // constructor(a) {
  10. // this.a = a;
  11. // super();
  12. // }
  13. }
  14. let test = new Child(); // Uncaught ReferenceError: Must call super
  15. // constructor in derived class before accessing 'this' or returning
  16. // from derived constructor

调用父类构造函数,只能出现在子类的构造函数。

  1. class Father {
  2. test(){
  3. return 0;
  4. }
  5. static test1(){
  6. return 1;
  7. }
  8. }
  9. class Child extends Father {
  10. constructor(){
  11. super();
  12. }
  13. }
  14. class Child1 extends Father {
  15. test2() {
  16. super(); // Uncaught SyntaxError: 'super' keyword unexpected
  17. // here
  18. }
  19. }

调用父类方法, super 作为对象,在普通方法中,指向父类的原型对象,在静态方法中,指向父类

  1. class Child2 extends Father {
  2. constructor(){
  3. super();
  4. // 调用父类普通方法
  5. console.log(super.test()); // 0
  6. }
  7. static test3(){
  8. // 调用父类静态方法
  9. return super.test1+2;
  10. }
  11. }
  12. Child2.test3(); // 3

总结:

ES5中:利用借用构造函数实现实例属性和方法的继承 ; 利用原型链或者寄生式继承实现 共享的原型属性和方法的继承 。

ES6中:

利用class定义类,extends实现类的继承;

子类constructor里调用super()(父类构造函数)实现 实例属性和方法的继承;

子类原型继承父类原型,实现原型对象上方法的继承。

4. 模块化

模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。

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

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

4.1 特点

ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict;

模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。

每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。

每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取。

4.2 export 与 import

模块导入导出各种类型的变量,如字符串,数值,函数,类。

  • 导出的函数声明与类声明必须要有名称(export default 命令另外考虑)。
  • 不仅能导出声明还能导出引用(例如函数)。
  • export 命令可以出现在模块的任何位置,但必需处于模块顶层。
  • import 命令会提升到整个模块的头部,首先执行。

    /——-export [test.js]——-/
    let myName = “Tom”;
    let myAge = 20;
    let myfn = function(){

    1. return "My name is" + myName + "! I'm '" + myAge + "years old."

    }
    let myClass = class myClass {

    1. static a = "yeah!";

    }
    export {

    1. myName, myAge, myfn, myClass }

    /——-import [xxx.js]——-/
    import {

    1. myName, myAge, myfn, myClass } from "./test.js";

    console.log(myfn());// My name is Tom! I’m 20 years old.
    console.log(myAge);// 20
    console.log(myName);// Tom
    console.log(myClass.a );// yeah!

建议使用大括号指定所要输出的一组变量写在文档尾部,明确导出的接口。

函数与类都需要有对应的名称,导出文档尾部也避免了无对应名称。

4.3 import 命令的特点

只读属性:不允许在加载模块的脚本里面,改写接口的引用指向,即可以改写 import 变量类型为对象的属性值,不能改写 import 变量类型为基本类型的值。

  1. import {
  2. a} from "./xxx.js"
  3. a = {
  4. }; // error
  5. import {
  6. a} from "./xxx.js"
  7. a.foo = "hello"; // a = { foo : 'hello' }

单例模式:多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import 。

  1. import {
  2. a } "./xxx.js";
  3. import {
  4. a } "./xxx.js";
  5. // 相当于 import { a } "./xxx.js";
  6. import {
  7. a } from "./xxx.js";
  8. import {
  9. b } from "./xxx.js";
  10. // 相当于 import { a, b } from "./xxx.js";

静态执行特性:import 是静态执行,所以不能使用表达式和变量。

  1. import {
  2. "f" + "oo" } from "methods";
  3. // error
  4. let module = "methods";
  5. import {
  6. foo } from module;
  7. // error
  8. if (true) {
  9. import {
  10. foo } from "method1";
  11. } else {
  12. import {
  13. foo } from "method2";
  14. }
  15. // error

4.4 as 的用法

export 命令导出的接口名称,须和模块内部的变量有一一对应关系。

导入的变量名,须和导出的接口名称相同,即顺序可以不一致。

  1. 使用 as 重新定义导出的接口名称,隐藏模块内部的变量
  2. /*-----export [test.js]-----*/
  3. let myName = "Tom";
  4. export {
  5. myName as exportName }
  6. /*-----import [xxx.js]-----*/
  7. import {
  8. exportName } from "./test.js";
  9. console.log(exportName);// Tom
  10. //不同模块导出接口名称命名重复, 使用 as 重新定义变量名。
  11. /*-----export [test1.js]-----*/
  12. let myName = "Tom";
  13. export {
  14. myName }
  15. /*-----export [test2.js]-----*/
  16. let myName = "Jerry";
  17. export {
  18. myName }
  19. /*-----import [xxx.js]-----*/
  20. import {
  21. myName as name1 } from "./test1.js";
  22. import {
  23. myName as name2 } from "./test2.js";
  24. console.log(name1);// Tom
  25. console.log(name2);// Jerry

5. 数值扩展

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

  1. console.log(0b1111);//15
  2. console.log(0o10);//16

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

  1. console.log(Number.isFinite(15));//true
  2. console.log(Number.isFinite(NaN));//false
  3. console.log(Number.isFinite(10/0));//false

Number.isNaN() 用来检查一个值是否为NaN

  1. console.log(Number.isNaN(NaN));//true
  2. console.log(Number.isNaN(5));//true

Number.isInteger() 判断一个数是不是整数

  1. console.log(Number.isInteger(25));//true
  2. console.log(Number.isInteger(25.0));//true
  3. console.log(Number.isInteger(25.1));//false

Number.parseInt 转为整数 Number.parseFloat 转为带小数的(字符串转为数字

  1. console.log(Number.parseInt('5211314love')); //5211314
  2. console.log(Number.parseFloat('3.1415926hh')); //3.1415926

**Math.trunc()**用于去除一个数的小数部分,返回整数部分。

  1. console.log(Math.trunc(4.1));//4
  2. console.log(Math.trunc(-4.1));//-4

Math.sign 判断一个数是正数还是负数还是0

  1. console.log(Math.sign(100)); //1
  2. console.log(Math.sign(0)); // 0
  3. console.log(Math.sign(-100)); //-1

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

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

发表评论

表情:
评论列表 (有 0 条评论,216人围观)

还没有评论,来说两句吧...

相关阅读

    相关 es6特性

    es6语法 > es6语法用起来是十分方便的,但是有些浏览器还是不支持,但是做大型项目中基本上要用到转码器(babel转码器),可以把es6语法转为es5直接使用。 T

    相关 ES6特性

    1.变量声明let和const 在ES6以前,var关键字声明变量。无论声明在何处,都会被视为声明在函数的最顶部(不在函数内即在全局作用域的最顶部)。这就是函数变量提升例如:

    相关 ES6特性

    1.声明变量的关键字:const 和 let JavaScript ES6中引入了另外两个声明变量的关键字:const和let。在ES6中,我们将很少能看到var了。 co