ES6 提供了新的数据结构Set(集合)。类似于数组,但Set 中元素值都是唯一的,Set
对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
集合实现了iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历,集合的属性和方法:
size 返回集合的元素个数
add 增加一个新元素,返回当前集合
delete 删除元素,返回boolean 值
has 检测集合中是否包含某个元素,返回boolean 值
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" ]
ES6 提供了Map 数据结构。它类似于对象,也是键值对的集合,并且能够记住键的原始插入顺序。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。
1. Objects 和 maps 的比较
Objects
和 Maps
类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此过去我们一直都把对象当成 Maps
使用。不过 Maps
和 Objects
有一些重要的区别,在下列情况里使用 Map
会是更好的选择
。。。。 | Map | Object |
---|---|---|
意外的键 | Map 默认情况不包含任何键。只包含显式插入的键。 |
一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。注意: 虽然 ES5 开始可以用 Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。 |
键的类型 | 一个 Map 的键可以是任意值,包括函数、对象或任意基本类型。 |
一个Object 的键必须是一个 String 或是Symbol 。 |
键的顺序 | Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。 |
一个 Object 的键是无序的注意:自ECMAScript 2015规范以来,对象确实保留了字符串和Symbol键的创建顺序; 因此,在只有字符串键的对象上进行迭代将按插入顺序产生键。 |
Size | Map 的键值对个数可以轻易地通过size 属性获取 |
Object 的键值对个数只能手动计算 |
迭代 | Map 是 iterable 的,所以可以直接被迭代。 |
迭代一个Object 需要以某种方式获取它的键然后才能迭代。 |
性能 | 在频繁增删键值对的场景下表现更好。 | 在频繁添加和删除键值对的场景下未作出优化。 |
2. Map 的属性和方法
size 返回Map 的元素个数
set 增加一个新元素,返回当前Map
get 返回键名对象的键值
delete 如果 Map
对象中存在该元素,则移除它并返回 true
;否则如果该元素不存在则返回 false
。
has 检测Map 中是否包含某个元素,返回boolean 值
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
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。class 的本质是 function。类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
// 匿名类
let Example = class {
constructor(a) {
this.a = a;
}
}
// 命名类
let Example = class Example {
constructor(a) {
this.a = a;
}
}
//类的声明
class Example {
constructor(a) {
this.a = a;
}
}
注意要点
不可重复声明。
类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。
类中方法不需要 function 关键字。
方法间不能加分号。
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
类的所有方法都定义在类的prototype
属性上面。
在类的实例上面调用方法,其实就是调用原型上的方法。b
是B
类的实例,它的constructor
方法就是B
类原型的constructor
方法。
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
类的所有实例共享一个原型对象。p1
和p2
都是Point
的实例,它们的原型都是Point.prototype
,所以__proto__
属性是相等的。
var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__ === p2.__proto__ //true
1. constructor 方法:constructor 方法是类的默认方法,创建类的实例化对象时被调用。
class Example{
constructor(){
console.log('我是constructor');
}
}
new Example(); // 我是constructor
2. 静态方法:该方法不会实例被继承,而是直接通过类来调用
静态方法只能在当前类上调用,不能被该类的实例对象调用。父类的静态方法可以被子类继承。
因此静态方法被调用的方式一共有三种(三种调用方式都在下面一段代码中使用到了,请耐心阅读):
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);
}
}
}
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
通过 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()(父类构造函数)实现 实例属性和方法的继承;
子类原型继承父类原型,实现原型对象上方法的继承。
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。
模块功能主要由两个命令构成:export 和 import。
ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict;。
模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。
每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取。
模块导入导出各种类型的变量,如字符串,数值,函数,类。
/*-----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!
建议使用大括号指定所要输出的一组变量写在文档尾部,明确导出的接口。
函数与类都需要有对应的名称,导出文档尾部也避免了无对应名称。
只读属性:不允许在加载模块的脚本里面,改写接口的引用指向,即可以改写 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
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
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 对象的方法
Object.is 比较两个值是否严格相等,与『===』行为基本一致(+0 与NaN)
Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象
proto、setPrototypeOf、setPrototypeOf 可以直接设置对象的原型