es6高级特性

Reflect、Proxy、Symbol

Symbol

介绍

引入:从根本上防止对象名属性名的冲突的机制。

Symboljavascript的第七种语言

1
2
typeof Symbol("foo")
// symbol

参数为字符串

1
2
3
4
let s1 = Symbol('foo');
s1.toString() // "Symbol(foo)"

s1.description // "foo"

参数是对象

1
2
3
4
5
6
7
8
// 调用对象的toString()
const obj = {
toString() {
return 'abc';
}
};
const sym = Symbol(obj);
sym // Symbol(abc)

注意

1
2
3
4
5
6
7
8
9
10
11
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false

可以转化为Boolean值

1
2
3
4
5
6
7
let sym = Symbol();
Boolean(sym) // true
!sym // false

if (sym) {
// ...
}

作为属性名

每一个 Symbol 值都是不相等的,保证不同名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 为对象属性名时,不能用点运算符
const mySymbol = Symbol();
const a = {};
// 点运算符后面总解析为字符串, 实际上读取的是字符串"mySymbol"
a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"

// 同理
// 在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
let s = Symbol();

let obj = {
[s](arg) {...}
};

obj[s](123);

定义一组常量

1
2
3
4
5
6
7
const log = {};

log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
};

遍历

属性名的遍历

Symbol 作为属性名,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。

Object.getOwnPropertySymbols

1
2
3
4
5
6
7
8
9
10
11
12
// 返回值为数组
const obj = {};
let a = Symbol('a');
let b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

const objectSymbols = Object.getOwnPropertySymbols(obj);

objectSymbols
// [Symbol(a), Symbol(b)]

Reflect.ownKeys

1
2
3
4
5
6
7
8
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};

Reflect.ownKeys(obj)
// ["enum", "nonEnum", Symbol(my_key)]

Symbol.for()

1
2
3
4
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

s1 === s2 // true

Symbol()的区别

都会生成新的 Symbol。

区别:前者有全局登记特性

1
2
3
4
5
6
// Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

即使for()不在全局环境

1
2
3
4
5
6
7
function foo() {
return Symbol.for('bar');
}

const x = foo();
const y = Symbol.for('bar');
console.log(x === y); // true

实例

消除魔术字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 }); // 魔术字符串

// 消除强耦合
const shapeType = {
triangle: 'Triangle'
};

// 仔细发现shapeType.triangle等于哪个值并不重要,只要确保不会跟其他shapeType属性的值冲突即可
const shapeType = {
triangle: Symbol()
};

模块的 Singleton 模式

Singleton 模式指的是调用一个类,任何时候返回的都是同一个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// global顶层对象实现
// mod.js
function A() {
this.foo = 'hello';
}

if (!global._foo) {
global._foo = new A();
}

module.exports = global._foo;

// index.js
const a = require("./mod.js");
console.log(a.foo);

// 问题 global._foo是可写的 如何模块都可一修改
// 脚本mod.js就会失真
global._foo = { foo: 'world' };

const a = require('./mod.js');
console.log(a.foo);

// Symbol
// mod.js
const FOO_KEY = Symbol('foo');

function A() {
this.foo = 'hello';
}

if (!global[FOO_KEY]) {
global[FOO_KEY] = new A();
}

module.exports = global[FOO_KEY];

// 外部文件无法通过键名来改写了(无for())
// 注意:虽然 Node 会将脚本的执行结果缓存,一般情况下,不会多次执行同一个脚本,但是用户可以手动清除缓存,所以也不是绝对可靠。
-------------要说再见啦感谢大佬的光临~-------------