关于ES6的一些学习

Set和Map数据结构

Set

用法:

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

Set本身是一个构造函数,用来生成Set数据结构.

const s = new Set();
[2,3,4,5,4,2,2].forEach(x => s.add(x));
for(let i of s){
  console.log(i);
}
// 2,3,4,5
//Set结构不会添加重复的值

Set函数可以接受一个数组(或者具有iterable接口的其他数据结构)作为参数,用来初始化.

// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]

// 例二
function divs () {
  return [...document.querySelectorAll('div')];
}

const set = new Set(divs());
set.size // 56

// 类似于
divs().forEach(div => set.add(div));
set.size // 56

所以去除数组重复成员的方法:

[...new Set(array)]

向Set加入值的时候,不会发生类型转换

在Set内部,两个NaN是相等的

两个对象总是不相等的

Set实例的属性和方法

属性:

Set.prototype.constructor:构造函数,默认就是Set函数
Set.prototype.size:返回Set实例的成员总数.

方法:分为两大类

1.操作方法(用于操作数据)

2.遍历方法(用于遍历成员)

操作方法:

add(value):添加某个值,返回Set结构本身.
delete(value):删除某个值,返回一个布尔值,表示删除是否成功.
has(value):返回一个布尔值,表示该值是否为Set的成员.
clear():清除所有成员,没有返回值.
## Array.from方法可以将Set解构转为数组.

作用:去除数组重复成员

function dedupe(array){
    return Array.from(new Set(array));
}
dedupe([1,1,2,3])//[1,2,3]

遍历方法:

keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
forEach():使用回调函数遍历每个成员

keys(),values(),entries()
keys方法,values方法,entries方法返回的都是遍历器对象.由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致.


let set = new Set(['red','green','blue']);
for (let item of set.keys()){
  console.log(item);
}
//red 
//green
//blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

Set结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法.这意味着,可以省略values方法,直接用for…of循环遍历Set.

forEach()

Set结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值.

set = new Set([1,4,9]);
set.forEach((value,key) => console.log(key + ':' + value););
//1:1
//4:4
//9:9

遍历的应用

扩展运算符(…)内部使用for…of循环,所以也可以用于Set结构.

let set = new Set(['red','green','blue']);
let arr = [...set];
//['red','green','blue']

扩展运算符和Set结构相结合,就可以去除数组的重复成员.

let arr = [3,5,2,2,5,5];
let unique = [...new Set(arr)];
//[3,5,2]

而且,数组的map和filter方法也可以间接用于Set了.

let set = new Set([1,2,3]);
set = new Set([...set].map(x => x*2));
//返回Set结构{2,4,6}
let set = new Set([1,2,3,4,5]);
set = new Set([...set].filter(x => (x%2) == 0));
//返回Set结构{2,4}

WeakSet

含义:WeakSet结构与Set类似,也是不重复的值的集合.但是,它与Set区别:

WeakSet的成员只能是对象,而不能是其他类型的值.

Map

Map数据结构,类似于对象,也是键值对的集合,但是’键’的范围不限于字符串,各种类型的值(包括对象)都可以当做键.也就是说,Object结构提供了"字符串-值"的对应,Map结构提供了"值-值"的对应,是一种更完善的Hash结构实现.

const m = new Map();
const o = {p:'Hello World'};
m.set(o,'content');
m.get(o);//"content"
m.has(o);//true
m.delete(o);//true
m.has(o);//false

实例的属性和操作方法

size,set(key,value),get(key),has(key),delete(key),clear()

遍历方法

Map结构原生提供三个遍历器生成函数和一个遍历方法

keys():返回键名的遍历器.
values():返回键值的遍历器.
entries():返回所有成员的遍历器.
forEach():遍历Map的所有成员.

注意:Map的遍历顺序就是插入顺序.

Map结构转为数组结构,比较快速的方法是使用扩展运算符(…).
结合数组的map方法,filter方法,可以实现Map的遍历和过滤(Map本省没有map和filter方法).

WeakMap

WeakMap与Map的区别有:

(1)WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名.
(2)WeakMap的键名所指向的对象,不计入垃圾回收机制.

API区别:WeakMap只有四个方法可用:get(),get(),has(),delete()

Proxy

理解为:在目标对象之前架设一层’拦截’,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写.Proxy这个词的原意是代理,用在这里表示由它"代理"某些操作,可以译为"代理器".

Reflect

概述

Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API.

Reflect对象的设计目的:

(1)将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上.现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上.也就是说,从Reflect对象上可以拿到语言内部的方法.

(2)修改某些Object方法的返回结果,让其变得更合理.比如,Object.defineProperty(obj,name,desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj,name,desc)则会返回false.

/ 老写法
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}

// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

(3)让Object操作变成函数行为.某些Object操作是命令式比如name in obj 和 delete obj[name],而Reflect.has(obj,name)和Reflect.deleteProperty(obj,name)让它们变成了函数行为.

//老写法
'assign' in Object //true
//新写法
Reflect.has(Objcet,'assign') //true

(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法,这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础.

var loggedObj = new Proxy(obj, {
  get(target, name) {
    console.log('get', target, name);
    return Reflect.get(target, name);
  },
  deleteProperty(target, name) {
    console.log('delete' + name);
    return Reflect.deleteProperty(target, name);
  },
  has(target, name) {
    console.log('has' + name);
    return Reflect.has(target, name);
  }
});

Reflect对象一共有13个静态方法

Reflect.apply(target,thisArg,args)

Reflect.construct(target,args)

Reflect.get(target, name, receiver)

Reflect.set(target, name, value, receiver)

Reflect.defineProperty(target, name, desc)

Reflect.deleteProperty(target, name)

Reflect.has(target, name)

Reflect.ownKeys(target)

Reflect.isExtensible(target)

Reflect.preventExtensions(target)

Reflect.getOwnPropertyDescriptor(target, name)

Reflect.getPrototypeOf(target)

Reflect.setPrototypeOf(target, prototype)

实例:使用Proxy实现观察者模式

观察者模式(Obsever mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行.

Promise对象

含义:Promise是异步编程的一种解决方案,比传统的解决方案–回调函数和事件–更合理更强大.ES6将其写进了语言标准,统一了用法,原生提供了Promise对象.

Promise对象有两个特点:

对象的状态不受外界影响.Promise对象代表异步操作,有三种状态:pending(进行中),fulfilled(已成功),和rejected(已失败).

一旦状态改变,就不会再变,任何时候都可以得到这个结果.

缺点:

无法取消Promise,一旦新建它就会立即执行,无法中途取消.

如果不设置回调函数,Promise内部抛出的错误,不会反应到外部.

基本用法

let promise = new Promise(function(resolve,reject){
  console.log('Promise');
  resolve();
});
promise.then(function(){
  console.log('resolved');
});
console.log('Hi');
//Promise
//Hi
//resolved

Promise.prototype.then()

Promise实例具有then()方法,也就是说then方法是定义在原型对象Promise.prototype上的,它的作用是为Promise实例添加状态改变时的回调函数.then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数.

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null,rejection)的别名,用于指定发生错误时的回调函数.

Promise.all()

Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例.

const p=Promise.all([p1,p2,p3]);

p的状态由p1,p2,p3决定,分成两种情况.

(1)只有p1,p2,p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1,p2,p3的返回值组成一个数组,传递给p的回调函数.

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数.

Promise.race()

Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例.

const p = Promise.race([p1,p2,p3]);

上面代码中,只要p1,p2,p3之中有一个实例率先改变状态,p的状态就跟着改变.那个率先改变的Promise实例的返回值,就传递给p的回调函数.

Promise.resolve()

有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用.

Generator函数的语法

执行Generator函数会返回一个遍历器对象,也就是说.Generator函数除了状态机,还是一个遍历器对象生成函数.返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态.

形式上,Generator函数是一个普通函数,但是有两个特征:

一是,function关键字与函数名之间有一个星号;

二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思是"产出").

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

async函数

async函数是什么?一句话,它就是Generator函数的语法糖.

(语法糖:计算机语言中添加的某种语法,使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。)

Generator函数,依次读取两个文件

const fs = require('fs');
const readFile = function(fileName){
  return new Promise(function(resolve,reject){
   fs.readFile(fileName,function(error,data){
    if(error) return reject(error);
    resolve(data);
   });
  });
}
const gen = function* (){
  const f1 = yield readFile('/etc/aaa');
  const f2 = yield readFile('/etc/bbb');
  console.log(f1.toString());
  console.log(f2.toString());
}

写成async函数,如下

const asyncReadFile = async function() {
  const f1 = await readFile('/etc/aaa');
  const f2 = await readFile('/etc/bbb');
  console.log(f1.toString());
  console.log(f2.toString());
}

比较发现,async函数就是将Generator函数的星号(*)替换async,将yield替换成await,仅此而已.

async函数对Generator函数的改进有四点:

(1)内置执行器.
async函数的执行,与普通函数一模一样,只要一行.

asyncReadFile();

上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果.这完全不像Generator函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果.

(2)更好的语义.

(3)更广的适应性.

co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值,字符串和布尔值,但这时等同于同步操作).

(4)返回值是Promise.

async函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了.你可以用then方法指定下一步操作.

进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖.

Class的基本语法

传统方法:构造函数

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

用ES6的class改写

//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

ES6的类,完全可以看做构造函数的另一种写法.使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致.

class Bar {
  doStuff() {
    console.log('stuff');
  }
}

var b = new Bar();
b.doStuff() // "stuff"

类的所有方法都定义在类的prototype属性上面.
Object.assign方法可以很方便地一次向类添加多个方法.

class Point {
  constructor(){
   //...
  }
}
Object.assign(Point.prototype,{
  toString(){},
  toValue(){}
});

prototype对象的constructor属性,直接指向’类’的本身,这与ES5的行为是一致的.

Point.prototype.constructor === Point //true

Class的继承

Class可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多.

class Point{}
class ColorPoint extends Point{}

注意:在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错.这是因为子类实例的构建,是基于对父类实例的加工,只有super方法才能返回父类实例.

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
  }
}

下面是生成子类实例的代码:

let cp = new ColorPoint(25,8,'green');

cp instanceof ColorPoint //true
cp instanceof Point //true

父类的静态方法,也会被子类继承.

Object.getPrototypeOf()

Object.getPrototypeOf方法可以用来从子类上获取父类.

Object.getPrototypeOf(ColorPoint)===Point //true

因此可以使用这个方法判断,一个类是否继承了另一个类.

Module的语法

export命令

模块功能主要有两个命令构成:export和import.export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能.

//profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year =1958;

export {firstName,lastName,year};

通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名.

function v1() {...}
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

import命令

//main.js
import {firstName, lastName, year} from './profile';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名.

import {lastName as surname} from './profile';

注意,import命令具有提升效果,会提升到整个模块的头部,首先执行.因为import在静态解析阶段执行,所以它是一个模块之中最早执行的.

模块的整体加载

import * as circle from './circle';

console.log('圆面积:'+circle.area(4));
console.log('圆周长:'+circle.circumference(14));

export default命令

export default 命令,为模块指定默认输出.

其他模块加载该模块时,import命令可以为该匿名函数指定任意名字.

// import-default.js
import customName from './export-default';
customName(); // 'foo'

上面代码的import命令.可以用任意名称指向export-default.js输出的方法,这时就不需要知道原模块输出的函数名.需要注意的是,这时import命令后面,不使用大括号.

ES6模块与CommonJS模块的差异

CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用.

CommonJS模块是运行时加载,ES6模块是编译时输出接口.

第一个差异:

CommonJS模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值.

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};

上面代码输出内部变量counter和改写这个变量的内部方法incCounter.然后,在main.js里面加载这个模块.

// main.js
var mod = require('./lib');

console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 3

第二个差异:
是因为CommonJS加载的是一个对象(即module.export属性),该对象只有在脚本运行完才会生成.而ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成.

ES6模块的运行机制与CommonJS不一样.JS引擎对脚本静态分析的时候,遇到模块加载命令import.就会生成一个只读引用,等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值