10.ES6使用小结

这部分呢打算对项目中用到的ES6语法做个小总结吧: )仅是对项目使用的部分做记录,有兴趣的可以去看看阮一峰的ECMAScript 6 入门

1.promise

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

基本用法

// 创建promise实例
const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

// Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

举两个栗子吧

// 简单操作
function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done'); // 第三个参数会作为resolve函数的参数使用
  });
}

timeout(100).then((value) => {
  console.log(value); // 打印done
});    

// 稍复杂操作
const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response); 
      } else {
        reject(new Error(this.statusText)); // 分类进不同的回调
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise; // 返回promise对象是继续回调的关键
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});

promise新建后会执行some code的代码块,再执行后面的代码,最后执行resolve和reject的部分

链式回调,避免回调地狱

多层的回调看起来十分不美观而且不易数据维护,在js中引入链式操作会变得优雅很多。

yPromise(value){
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            if(value == 1002){
                resolve(value+1)
            }
            else {
                reject('yuhui')
            }
        },value)
    })
}


this.yPromise(1000)
  .catch(function(err){ // 调用resolve或reject并不会终结 Promise 的参数函数的执行。
    console.log(err)
    // return Promise.resolve(1000)
    return Promise.reject(2000) // 要想继续回调必须返回一个promise对象
}).catch(function(err){
    console.log(err)
    return Promise.resolve(1000)
}).then(function(res){
    console.log(res)
    // var res2 = res + 1000
    // return new Promise(function(resolve, reject){
    //     resolve(res2)
    // })
    return Promise.resolve(res+1000) //等价于上面的写法
}).then(function(res){
    console.log(res)
    return Promise.resolve(res+1000)
})

运行结果:

2.箭头函数

待续。。。: p

3.Set数据结构

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。

// 记住简单的用法就行
let set = new Set([1,2,2,3,3,4])
[...set]  // [1,2,3,4]
set.size // 4

实例化Set

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

const set = new Set([1,2,3,3,4,5])
console.log([...set])
// or

const set = new Set()
[1,2,3,3,4,5].forEach ((val, index) => {set.add(val)})
console.log([...set])
// 1,2,3,4,5  size:5

// 有个比较有意思的现象
// 直接add一个数组,而不是把数组元素一个个add
const set = new Set()
set.add([1,2,3,4,5,5])
console.log([...set])
// [1,2,3,4,5,5] size:1  会多一个一样的add的数组且可以含有重复元素

1.既可以在实例化Set的时候把数组当参数传进Set()构造函数,也可以用Set对象的add方法添加成员。
2.Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化

特性和用法

1.针对Set数据结构元素不重复的特性,可以很简单的对数组去重

[...new Set(array)] // 去重array

也可以用于字符串去重

[...new Set('aabbccd')].join('') // 去重array

join() 方法用于把数组中的所有元素放入一个字符串,默认以逗号分割;可接受字符串参数x,即以x分割数组

2.向 Set 加入值的时候,不会发生类型转换,所以5和”5”是两个不同的值。Set内部判断两个值是否不同,使用的算法类似于精确相等运算符(===),主要的区别是在Set内部NaN等于自身,而精确相等运算符认为NaN不等于自身。

let set = new Set()
let a = NaN
let b = NaN
set.add(a)
set.add(b)
set // Set {NaN} 去重后

NaN === NaN // false

另外,两个对象总是不相等的。

let set = new Set();

set.add({})
set.size // 1

set.add({})
set.size // 2

{} === {} // false

Set实例的属性和方法

  1. Set 结构的实例有以下属性。

    • Set.prototype.constructor:构造函数,默认就是Set函数。
    • Set.prototype.size:返回Set实例的成员总数。
  2. Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。

    • add(value):添加某个值,返回 Set 结构本身。
    • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
    • has(value):返回一个布尔值,表示该值是否为Set的成员。
    • clear():清除所有成员,没有返回值。
      s.add(1).add(2).add(2);
      // 注意2被加入了两次
      

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2);
s.has(2) // false


另外,除了之前的[...set]这种方式把set类型转换成数组外,Array的from()方法也可以。
```javascript
let set = new Set([1,2,2,3,3,4])
let arr = Array.from(set) // [1,2,3,4]

遍历操作

Set结构的实例有四个遍历方法,可以用于遍历成员。

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

需要特别指出的是,Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用Set保存一个回调函数列表,调用时就能保证按照添加顺序调用。

keys(),values(),entries()

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

let set = new Set(['yu', 'zheng', 'hui']);

for (let item of set.keys()) {
  console.log(item);
}
// yu
// zheng
// hui

for (let item of set.values()) {
  console.log(item);
}
// yu
// zheng
// hui

for (let item of set.entries()) {
  console.log(item);
}
// ["yu", "yu"]
// ["zheng", "zheng"]
// ["hui", "hui"]

entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。

另外,Set结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。

Set.prototype[Symbol.iterator] === Set.prototype.values
// true

// 可以省略values方法,直接用for...of循环遍历 Set
for (let item of set) { // 等同于for (let item of set.values())
  console.log(item)
}
// yu
// zheng
// hui
forEach()

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

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

Set的forEach方法的参数就是一个处理函数,该函数的参数与数组的forEach一致,依次为键值、键名、集合本身(上例省略了该参数)。这里需要注意,Set结构的键名就是键值(两者是同一个值),因此第一个参数与第二个参数的值永远都是一样的。

Array和Set组合应用

  1. 数组的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}


2. 使用Set可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)
```javascript
let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])

// 并集
let union = new Set([...a, ...b])
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)))
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)))
// Set {1}

4.Map数据结构

基本用法

const map = new Map([
    [true, 1],[{}, 1],['y', 'h']
])
map // {true => 1, {…} => 1, "y" => "h"}

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

const m = new Map()
const o = {p: 'Hello World'} // 这里的对象一定要用声明的变量作为key,保证set和get是指向同一块内存

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

上面代码使用Map结构的set方法,将对象o当作m的一个键,然后又使用get方法读取这个键,接着使用delete方法删除了这个键。

构造函数

  1. 作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
    const map = new Map([
    ['name', 'yyy'],
    ['title', 'hhh']
    ])
    

map.size // 2
map.has(‘name’) // true
map.get(‘name’) // “yyy”
map.has(‘title’) // true
map.get(‘title’) // “hhh”


> Map构造函数接受数组作为参数,本质上还是执行了arr.forEach([key,val], index) => map.set(key, val)

2. 不仅仅是数组,任何具有Iterator接口、且每个成员都是一个双元素的数组的数据结构,都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
```javascript
const set = new Set([['yyy',1],['zzz',2],['hhh',3]])
const map = new Map([['yyy',4],['zzz',5],['hhh',6]])

const origin_map1 = new Map(set)
const origin_map2 = new Map(map)

origin_map1.get('yyy') // 1
origin_map2.get('yyy') // 4

分别使用Set对象和Map对象当作Map构造函数的参数,结果都生成了新的Map对象。

  1. 如果对同一个键多次赋值,后面的值将覆盖前面的值;不存在的键对应的值都是undefined。
    const map = new Map()
    

map
.set(1, ‘aaa’)
.set(1, ‘bbb’)

map.get(1) // “bbb”

map.get(‘asfddfsasadf’) // undefined


4. 只有对**同一个对象**的引用,Map 结构才将其视为同一个键。
```javascript
const map = new Map()

map.set(['a'], 555)
map.get(['a']) // undefined

let key = ['a'] // 赋值给变量后get和set引用的都是同一块内存了
map.set(key, 555)
map.get(key) // 555

上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,两个对象的内存地址是不一样的,因此get方法无法读取该键,返回undefined。

  1. 同样的值的两个实例,在Map结构中被视为两个键
    const map = new Map()
    

const k1 = [‘a’]
const k2 = [‘a’]

map
.set(k1, 111)
.set(k2, 222)

map.get(k1) // 111
map.get(k2) // 222


> 变量k1和k2的值是一样的,但是它们指向的是两块不同的内存,因此在Map结构中被视为两个键

综上所述,Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。  

如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等(===),Map将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。  

**虽然NaN不严格相等于自身,但Map将其视为同一个键。**
```javascript
let map = new Map()

map.set(-0, 123)
map.get(+0) // 123

map.set(true, 1)
map.set('true', 2)
map.get(true) // 1

map.set(undefined, 3)
map.set(null, 4)
map.get(undefined) // 3

map.set(NaN, 123)
map.get(NaN) // 123

属性和操作方法

Map 结构的实例有以下属性和操作方法:

  1. 属性
    • size 返回Map结构的成员总数
  2. 方法
    • set(key, value) 设置键名key对应的键值为value,然后返回整个Map结构
    • get(key) 读取key对应的键值
    • has(key) 返回一个布尔值,表示某个键是否在当前 Map 对象之中
    • delete(key) 删除某个键,返回true。如果删除失败,返回false
    • clear() 清除所有成员,没有返回值
const map = new Map()
map.set('foo', true)
map.set('bar', false)

map.size // 2
map.set('yyy', 1).set('zzz', 2) // set方法返回的是map对象,因此可以有链式写法

map.get('yyy') // 1
map.get('hhh') // undefined

map.set(undefined, 'hah')
map.get(undefined) // hah
map.has(undefined) // true

map.delete('yyy') // 成功后返回true
map.has('yyy') // fasle

map.clear()
map.has(undefined) // false

遍历方法

和Set一样,Map也提供三个遍历器生成函数和一个遍历方法。

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

需要特别注意的是,Map 的遍历顺序就是插入顺序。

const map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

for (let key of map.keys()) {  // 遍历key
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) { // 遍历value
  console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) { // 遍历Map成员
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) { // Map默认的遍历方法(Symbol.iterator属性)就是entries
  console.log(key, value);
}
// "F" "no"
// "T" "yes"
forEach()

Map还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历。

map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value)
})

const reporter = {
  report: function(key, value) {
    console.log("Key: %s, Value: %s", key, value)
  }
}
map.forEach(function(value, key, map) {
  this.report(key, value)
}, reporter)

forEach()可以接受第二个参数,可以改变this的指向

与其他数据结构的互相转换

1.Map转成数组

Map 结构转为数组结构,比较快速的方法是使用扩展运算符(…)。

const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three']
])

[...map.keys()]
// [1, 2, 3]

[...map.values()]
// ['one', 'two', 'three']

[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]

[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

从而可以结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤(Map 本身没有map和filter方法)。

const map0 = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c')

const map1 = new Map(
  [...map0].filter(([k, v]) => k < 3) // [k, v]结构赋值
)
// 产生 Map 结构 {1 => 'a', 2 => 'b'}

const map2 = new Map(
  [...map0].map(([k, v]) => [k * 2, '_' + v])
)
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
2.Map转为对象
let obj = Object.create(null)
const map = new Map([
    ['y',1],['z',2],['h',3]
])

map.forEach(function(v,k) {
    obj[k] = v
})
// or
for (let [k,v] of map) {
    obj[k] = v
}

这样无损转换的前提是key都是字符串,如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。

3.对象转为Map
let obj = {'y': 1, 'z': 2, 'h': 3}
const map = new Map()

for (let k in obj) {
    map.set(i, obj[i])
}
// or
for (let k of Object.keys(obj)) { //Object.keys(obj) => ['y','z','h']
    map.set(i, obj[i])
}
3.Map转为JSON
let obj = Object.create(null)
const map = new Map([
    ['y',1],['z',2],['h',3]
])

map.forEach(function(v,k) {
    obj[k] = v
})
// or
for (let [k,v] of map) {
    obj[k] = v
}

JSON.stringify(obj) // 在Map转换成对象的基础上JSON.stringify()

另一种情况是当Map的键是非字符串时,这时候可以选择转成JSON数组

const map = new Map([
    [true,1],[{'y': h},2],['h',3]
])

const arr = [...map] // [[true,1],[{'y': h},2],['h',3]]

JSON.stringify(arr)

5.解构

基本概念

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

// 以前赋值
let a = 1
let b = 2
let c = 3

// 用解构赋值
let [a,b,c] = [1,2,3]

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值

完全解构:

let [foo, [[bar], baz]] = [1, [[2], 3]]
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"]
third // "baz"

let [x, , y] = [1, 2, 3]
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4]
head // 1
tail // [2, 3, 4]

解构不成功:

let [foo] = []
let [bar, foo] = [1]
foo // undefined

let [x, y, ...z] = ['a']
x // "a"
y // undefined
z // []

不完全解构:
等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

解构报错:
如果等号的右边不是数组或者严格地说,不是可遍历的结构,那么将会报错。

// 报错
let [foo] = 1
let [foo] = false
let [foo] = NaN
let [foo] = undefined
let [foo] = null
let [foo] = {}

上面的语句都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口(前五个表达式),要么本身就不具备Iterator接口(最后一个表达式)。

有Iterator接口数据格式的结构
例如Set数据结构和Generator函数:

let [x, y, z] = new Set(['a', 'b', 'c'])
x // "a"

// Generator函数

function* fibs() {
  let a = 0;
  let b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5

fibs是一个Generator函数,原生具有Iterator接口。解构赋值会依次从这个接口获取值。


写完需求搬blog内容(2020/1/6 tencent)