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实例的属性和方法
Set 结构的实例有以下属性。
- Set.prototype.constructor:构造函数,默认就是Set函数。
- Set.prototype.size:返回Set实例的成员总数。
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组合应用
- 数组的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方法删除了这个键。
构造函数
- 作为构造函数,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对象。
- 如果对同一个键多次赋值,后面的值将覆盖前面的值;不存在的键对应的值都是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。
- 同样的值的两个实例,在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 结构的实例有以下属性和操作方法:
- 属性
- size 返回Map结构的成员总数
- 方法
- 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接口。解构赋值会依次从这个接口获取值。