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


1. Set

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

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

  1. size 返回集合的元素个数

  2. add 增加一个新元素,返回当前集合

  3. delete 删除元素,返回boolean 值

  4. has 检测集合中是否包含某个元素,返回boolean 值

  5. clear 清空集合,返回undefined

Set相关属性和方法的使用

let mySet = new Set();

mySet.add(1); // Set [ 1 ]
mySet.add(5); // Set [ 1, 5 ]
mySet.add(5); // 自动去重:Set [ 1, 5 ]
mySet.add("some text"); // Set [ 1, 5, "some text" ]
let o = {a: 1, b: 2};
mySet.add(o);
mySet.add({a: 1, b: 2}); // o 指向的是不同的对象,所以没问题

mySet.size; // 5

mySet.has(1); // true
mySet.has(3); // false
mySet.has(5);              // true
mySet.has(Math.sqrt(25));  // true
mySet.has("Some Text".toLowerCase()); // true
mySet.has(o); // true

mySet.delete(5);  // true,  从set中移除5
mySet.has(5);     // false, 5已经被移除
mySet.size; // 4, 刚刚移除一个值

console.log(mySet);
// logs Set(4) [ 1, "some text", {…}, {…} ] in Firefox
// logs Set(4) { 1, "some text", {…}, {…} } in Chrome

迭代Set

// 迭代整个set
// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
for (let item of mySet) console.log(item);

// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
for (let item of mySet.keys()) console.log(item);

// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
for (let item of mySet.values()) console.log(item);

// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
//(键与值相等)
for (let [key, value] of mySet.entries()) console.log(key);

// 用forEach迭代
mySet.forEach(function(value) {
  console.log(value);
});

Set 和 Array互换

let myArray = ["value1", "value2", "value3"];

// 用Set构造器将Array转换为Set
let mySet = new Set(myArray);

mySet.has("value1"); // returns true

// 用...(展开操作符)操作符将Set转换为Array
[...mySet]; // 与myArray完全一致

// 使用 Array.from 转换Set为Array
var myArr = Array.from(mySet); 

//数组去重
const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)])
// [2, 3, 4, 5, 6, 7, 32]

字符串相关

let text = 'India';

let mySet = new Set(text);  // Set {'I', 'n', 'd', 'i', 'a'}
mySet.size;  // 5

// 大小写敏感 & duplicate ommision
new Set("Firefox")  // Set(7) [ "F", "i", "r", "e", "f", "o", "x" ]
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 会是更好的选择

。。。。MapObject
意外的键Map 默认情况不包含任何键。只包含显式插入的键。一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。注意: 虽然 ES5 开始可以用 Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。
键的类型一个 Map的键可以是任意值,包括函数、对象或任意基本类型。一个Object 的键必须是一个 String 或是Symbol
键的顺序Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。一个 Object 的键是无序的注意:自ECMAScript 2015规范以来,对象确实保留了字符串和Symbol键的创建顺序; 因此,在只有字符串键的对象上进行迭代将按插入顺序产生键。
SizeMap 的键值对个数可以轻易地通过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对象

let myMap = new Map();

let keyObj = {};
let keyFunc = function() {};
let keyString = 'a string';

// 添加键
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyObj, "和键keyObj关联的值");
myMap.set(keyFunc, "和键keyFunc关联的值");

myMap.size; // 3

// 读取值
myMap.get(keyString);    // "和键'a string'关联的值"
myMap.get(keyObj);       // "和键keyObj关联的值"
myMap.get(keyFunc);      // "和键keyFunc关联的值"

myMap.get('a string');   // "和键'a string'关联的值"
                         // 因为keyString === 'a string'
myMap.get({});           // undefined, 因为keyObj !== {}
myMap.get(function() {}); // undefined, 因为keyFunc !== function () {}

迭代Map

// 使用for..of循环来实现迭代:
let myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
for (let [key, value] of myMap) {
  console.log(key + " = " + value); // 将会显示两个log。一个是"0 = zero"另一个是"1 = one"
}

for (let key of myMap.keys()) {
  console.log(key); // 将会显示两个log。 一个是 "0" 另一个是 "1"
}

for (let value of myMap.values()) {
  console.log(value); // 将会显示两个log。 一个是 "zero" 另一个是 "one"
}

//通过forEach()方法迭代:
myMap.forEach(function(value, key) {
  console.log(key + " = " + value);
})
// 将会显示两个logs。 一个是 "0 = zero" 另一个是 "1 = one"

复制或合并Maps

//Map 能像数组一样被复制:
let original = new Map([
  [1, 'one']
]);

let clone = new Map(original);

console.log(clone.get(1)); // one
console.log(original === clone); // false. 浅比较 不为同一个对象的引用

//Map对象间可以进行合并,但是会保持键的唯一性。
let first = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let second = new Map([
  [1, 'uno'],
  [2, 'dos']
]);

// 合并两个Map对象时,如果有重复的键值,则后面的会覆盖前面的。
// 展开运算符本质上是将Map对象转换成数组。
let merged = new Map([...first, ...second]);
console.log(merged.get(1)); // uno
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three

3. class 类

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

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

3.1 类的定义与声明

// 匿名类
let Example = class {
    constructor(a) {
        this.a = a;
    }
}
// 命名类
let Example = class Example {
    constructor(a) {
        this.a = a;
    }
}

//类的声明
class Example {
    constructor(a) {
        this.a = a;
    }
}

注意要点

  1. 不可重复声明。

  2. 类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。

  3. 类中方法不需要 function 关键字。

  4. 方法间不能加分号。

3.2 类的属性

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

Example.prototype={
    //methods
}
//添加方法
Object.assign(Example.prototype,{
    //methods
})

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

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

3. 公共属性

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

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

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

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

let Example = class Exam {
    constructor(a) {
        this.a = a;
    }
}
console.log(Example.name); // Exam
 
let Example=class {
    constructor(a) {
        this.a = a;
    }
}
console.log(Example.name); // Example

3.3 类的方法

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

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

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

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

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

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

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

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

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

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

  • 父类直接调用
  • 子类继承父类后调用
  • 子类通过super对象调用
class Foo {
  static classMethod() {
    return 'hello';
  }
}
//父类直接调用
Foo.classMethod();  //hello
//子类继承父类后调用
class Bar extends Foo {
}
Bar.classMethod();  //hell
//子类通过super对象调用
class Cla extends Foo {
    return super.classMethod(); //hello
}

3. 原型方法

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

4. 实例方法

class Example {
    constructor() {
        this.sum = (a, b) => {
            console.log(a + b);
        }
    }
}

3.4 类的实例化

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

lass Example {
    constructor(a, b) {
        this.a = a;
        this.b = b;
        console.log('Example');
    }
    sum() {
        return this.a + this.b;
    }
}
//共享原型对象
let exam1 = new Example(2, 1);
let exam2 = new Example(3, 1);
console.log(exam1._proto_ == exam2._proto_); // true
 
exam1._proto_.sub = function() {
    return this.a - this.b;
}
console.log(exam1.sub()); // 1
console.log(exam2.sub()); // 2

3.5 继承

通过 extends 实现类的继承。

class Calculator {
    constructor(a,b){
        this.a = a;
        this.b = b;
    }
    add(a,b){
        return ("相加結果:"+ (a+b))
    }
}
class Son extends Calculator{
    add(a,b){
        console.log(super.add(a,b)) //super.add(a,b)就是调用父类中的普通函数
    }
}
var rt = new Son()
rt.add(3, 4)

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

class Father {
    constructor() {}
}
class Child extends Father {
    constructor() {}
    // or 
    // constructor(a) {
        // this.a = a;
        // super();
    // }
}
let test = new Child(); // Uncaught ReferenceError: Must call super 
// constructor in derived class before accessing 'this' or returning 
// from derived constructor

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

class Father {
    test(){
        return 0;
    }
    static test1(){
        return 1;
    }
}
class Child extends Father {
    constructor(){
        super();
    }
}
class Child1 extends Father {
    test2() {
        super(); // Uncaught SyntaxError: 'super' keyword unexpected     
        // here
    }
}

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

class Child2 extends Father {
    constructor(){
        super();
        // 调用父类普通方法
        console.log(super.test()); // 0
    }
    static test3(){
        // 调用父类静态方法
        return super.test1+2;
    }
}
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(){
    return "My name is" + myName + "! I'm '" + myAge + "years old."
}
let myClass =  class myClass {
    static a = "yeah!";
}
export { myName, myAge, myfn, myClass }
 
/*-----import [xxx.js]-----*/
import { 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 变量类型为基本类型的值。

import {a} from "./xxx.js"
a = {}; // error
 
import {a} from "./xxx.js"
a.foo = "hello"; // a = { foo : 'hello' }

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

import { a } "./xxx.js";
import { a } "./xxx.js";
// 相当于 import { a } "./xxx.js";
 
import { a } from "./xxx.js";
import { b } from "./xxx.js";
// 相当于 import { a, b } from "./xxx.js";

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

import { "f" + "oo" } from "methods";
// error
let module = "methods";
import { foo } from module;
// error
if (true) {
  import { foo } from "method1";
} else {
  import { foo } from "method2";
}
// error

4.4 as 的用法

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

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

使用 as 重新定义导出的接口名称,隐藏模块内部的变量
/*-----export [test.js]-----*/
let myName = "Tom";
export { myName as exportName }
 
/*-----import [xxx.js]-----*/
import { exportName } from "./test.js";
console.log(exportName);// Tom

//不同模块导出接口名称命名重复, 使用 as 重新定义变量名。
/*-----export [test1.js]-----*/
let myName = "Tom";
export { myName }
/*-----export [test2.js]-----*/
let myName = "Jerry";
export { myName }
/*-----import [xxx.js]-----*/
import { myName as name1 } from "./test1.js";
import { myName as name2 } from "./test2.js";
console.log(name1);// Tom
console.log(name2);// Jerry

5. 数值扩展

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. Object.is 比较两个值是否严格相等,与『===』行为基本一致(+0 与NaN)

  2. Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象

  3. proto、setPrototypeOf、setPrototypeOf 可以直接设置对象的原型

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值