1.Symbol
ES5 的对象属性名都是字符串,这容易造成属性名的冲突。⽐如,你使⽤了⼀个他⼈提供的对象,但⼜想为这个对象添加新的⽅法(mixin 模式),新⽅法的名字就有可能与现有⽅法产⽣冲突。如果有⼀种机制,保证每个属性的名字都是独⼀⽆⼆的就好了,这样就从根本上防⽌属性名的冲突。ES6 引⼊了⼀种新的原始数据类型 Symbol
,表示独⼀⽆⼆的值。
1.1 生成Symbol值
通过调⽤Symbol函数⽣成⼀个Symbol值,这个值是独⼀⽆⼆的。注意,Symbol的值不是对象,不要给其添加属性,Symbol就是⼀个类似于字符串的值
let s = Symbol();
typeof s //symbol
1.2 生成具有标识的Symbol值
Symbol
函数可以接受⼀个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,⽐较容易区分。 通过Symbol.prototype.description可以获取该描述信息。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
1.3 消除魔术字符串
魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某⼀个具体的字符串或者数 值。⻛格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。
function getArea(shape, options) {
let area = 0;
switch (shape) {
case 'Triangle': // 魔术字符串
area = .5 * options.width * options.height;
break;
/* ... more code ... */
}
return area;
}
getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串
1.4 获取⼀个对象中Symbol属性名
Symbol 作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、 Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有⼀个 Object.getOwnPropertySymbols⽅法,可以获取指定对象的所有 Symbol 属性名。或者,使⽤ Reflflect.ownKeys⽅法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
let obj = {
[s1]:'hello',
[s2]:'world',
name:'terry',
age:12
}
Object.getOwnPropertySymbols(obj) //[ Symbol(foo), Symbol(bar) ]
Reflect.ownKeys(obj) //[ 'name', 'age', Symbol(foo), Symbol(bar) ]
1.5 Symbol值重复利用
Symbol.for()
⽅法可以做到使⽤同⼀个 Symbol 值。它接受⼀个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建⼀个以该字符串为名称的 Symbol 值,并将其注册到全局。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
Symbol.keyFor()
⽅法返回⼀个已登记的 Symbol 类型值的
key
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
1.6 内置Symbol
除了定义⾃⼰使⽤的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语⾔内部使⽤的⽅法。
Symbol.hasInstance
对象的
Symbol.hasInstance
属性,指向⼀个内部⽅法。当其他对象使⽤
instanceof
运算符,判断是否为该对象的实例时,会调⽤这个⽅法。⽐如, foo instanceof Foo
在语⾔内
部,实际调⽤的是
Foo[Symbol.hasInstance](foo)
。
class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}
[1, 2, 3] instanceof new MyClass() // true
Symbol.iterator
对象的
Symbol.iterator
属性,指向该对象的默认遍历器⽅法。
const myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
2.集合
2.1 Set
Set允许存储任何类型的唯⼀值,Set是ES6提供了新的数据结构。它类似于数组,但是成员的值都是唯⼀的,没有重复的值。
构造函数
Set
函数可以接受⼀个数组(或者具有 iterable 接⼝的其他数据结构)作为参数,⽤来初始化。
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
Set.prototype.size
获取容器中元素个数
Set.prototype.add(value)
向集合加⼊⼀个元素,不允许相同元素存在
Set.prototype.delete(value)
从集合中删除⼀个元素
Set.prototype.has(value)
判断集合中是否包含value
Set.prototype.clear()
清空set集合
Set.prototype.keys()
获取set集合key值的迭代器,由于set集合没有key所有与values⽅法返回结果⼀致
Set.prototype.values()
获取set集合的value值的迭代器
Set.prototype.entries()
获取set集合的entries迭代器
Set.prototype.forEach()
与数组类似的迭代⽅法
2.2 Map
Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是⼀种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map ⽐ Object 更合适。
const items = [
['name', '张三'],
['title', 'Author']
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
Map.prototype.size
获取集合键值对个数
Map.prototype.set(key, value)
向集合中设置⼀个键值对
Map.prototype.get(key)
从集合中通过key获取value
Map.prototype.has(key)
判断集合中是否包含key指定的键
Map.prototype.delete(key)
通过key删除⼀个键值对
Map.prototype.clear()
清空map集合
Map.prototype.keys()
获取map集合key值的迭代器
Map.prototype.values()
获取map集合value值的迭代器
Map.prototype.entries()
获取map集合entry的迭代器
Map.prototype.forEach()
迭代
3.代理
Proxy 可以理解成,在⽬标对象之前架设⼀层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了⼀种机制,可以对外界的访问进⾏过滤和改写。
ES6 原⽣提供 Proxy 构造函数,⽤来⽣成 Proxy 实例。
3.1 set/get
设置、获取属性值劫持
let obj = {
name:'terry',
age:12
}
let proxy = new Proxy(obj, {
get:function(target,key){
return target[key]
},
set:function(target,key,value){
target[key] = value;
}
})
proxy.age = 13; // 将会触发set操作
console.log(proxy.name); // 将会触发get操作
3.2 apply
函数调⽤劫持,当通过代理执⾏函数的时候会触发apply函数的执⾏
function foo(name){
console.log('hello',name);
}
let proxy = new Proxy(foo,{
apply(target,that,args){
target.apply(that,args)
}
})
proxy('terry');
proxy.call({},'terry')
proxy.apply({},['terry'])
3.3 constructor
构造函数劫持,当通过new来调⽤代理的时候会触发contructor函数的执⾏
function Foo(name){
this.name = name;
}
let pp = new Proxy(Foo,{
construct(target,args){
return new target(...args)
}
})
console.log(new pp('terry').name);
4.反射
Reflect
是⼀个内置的对象,它提供拦截 JavaScript 操作的⽅法。这些⽅法与
proxy
的⽅法相同。 Reflect
不是⼀个函数对象,因此它是不可构造的。
4.1 Reflect.apply(target, thisArg, args)
和
Function.prototype.apply()
功能类似
function foo(name){
console.log('hello',name);
}
let proxy = new Proxy(foo,{
apply(target,that,args){
Reflect.apply(target,that,args)
}
})
proxy('terry');
proxy.call({},'terry')
proxy.apply({},['terry'])
4.2 Reflect.construct(target, args)
对构造函数进⾏
new
操作,相当于执⾏
new target(...args)
。
function Foo(name){
this.name = name;
}
let proxy = new Proxy(Foo,{
construct(target,args){
return Reflect.construct(target,args)
}
})
console.log(new proxy('terry').name);
4.3 Reflect.get(target, name, receiver)
获取对象身上某个属性的值,类似于 target[name]
。
4.4 Reflect.set(target, name, value, receiver)
将值分配给属性的函数。返回⼀个 Boolean ,如果更新成功,则返回 true 。
let obj = {
name:'terry',
age:12
}
let proxy = new Proxy(obj, {
get:function(target,key){
return Reflect.get(target,key)
},
set:function(target,key,value){
return Reflect.set(target,key,value)
}
})
proxy.age = 13; // 将会触发set操作
console.log(proxy.name); // 将会触发get操作
console.log(proxy.age); // 将会触发get操作