您的位置:首页 > 其它

ECMAScript 6笔记(Symbol, Proxy 和 Reflect)

2016-05-11 21:10 357 查看

一、Symbol

ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

1.1 概述

Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分

var s1 = Symbol('foo');
var s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"


Symbol函数的参数只是表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不相等的。

// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();

s1 === s2 // false

// 有参数的情况
var s1 = Symbol("foo");
var s2 = Symbol("foo");

s1 === s2 // false


1.2 作为属性名的Symbol

var mySymbol = Symbol();

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

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

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

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"


注意,Symbol值作为对象属性名时,不能用点运算符。同理,在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。

var mySymbol = Symbol();
var a = {};

a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"

let s = Symbol();

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

obj[s](123);


1.3 消除魔术字符串

魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,该由含义清晰的变量代替。

function getArea(shape, options) {
var area = 0;

switch (shape) {
case 'Triangle': // 魔术字符串
area = .5 * options.width * options.height;
break;
/* ... more code ... */
}

return area;
}

getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串'Triangle'


上面代码中,字符串“Triangle”就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。

常用的消除魔术字符串的方法,就是把它写成一个变量。

var shapeType = {
triangle: 'Triangle'
};

function getArea(shape, options) {
var area = 0;
switch (shape) {
case shapeType.triangle:
area = .5 * options.width * options.height;
break;
}
return area;
}

getArea(shapeType.triangle, { width: 100, height: 100 });


上面代码中,我们把“Triangle”写成shapeType对象的triangle属性,这样就消除了强耦合。

如果仔细分析,可以发现shapeType.triangle等于哪个值并不重要,只要确保不会跟其他shapeType属性的值冲突即可。因此,这里就很适合改用Symbol值。

const shapeType = {
triangle: Symbol()
};


1.4 属性名的遍历

Symbol作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名。返回一个数组,成员是当前对象的所有用作属性名的Symbol值。

var obj = {};

var foo = Symbol("foo");

Object.defineProperty(obj, foo, {
value: "foobar",
});

for (var i in obj) {
console.log(i); // 无输出
}

Object.getOwnPropertyNames(obj)
// []

Object.getOwnPropertySymbols(obj)
// [Symbol(foo)]


另一个新的API,Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和Symbol键名。

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

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


var size = Symbol('size');

class Collection {
constructor() {
this[size] = 0;
}

add(item) {
this[this[size]] = item;
this[size]++;
}

static sizeOf(instance) {
return instance[size];
}
}

var x = new Collection();
Collection.sizeOf(x) // 0

x.add('foo');
Collection.sizeOf(x) // 1

Object.keys(x) // ['0']
Object.getOwnPropertyNames(x) // ['0']
Object.getOwnPropertySymbols(x) // [Symbol(size)]


上面代码中,对象x的size属性是一个Symbol值,所以Object.keys(x)、Object.getOwnPropertyNames(x)都无法获取它。这就造成了一种非私有的内部方法的效果。利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法

1.5 Symbol.for(),Symbol.keyFor()

Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for(“cat”)30次,每次都会返回同一个Symbol值,但是调用Symbol(“cat”)30次,会返回30个不同的Symbol值。

var s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

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


Symbol.keyFor方法返回一个已登记的Symbol类型值的key。

二、Proxy

Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

2.1 概述

var proxy = new Proxy(target, handler)


Proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});

obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  2


注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

如果handler没有设置任何拦截,那就等同于直接通向原对象。

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"


var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});

let obj = Object.create(proxy);
obj.time // 35

//proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。


同一个拦截器函数,可以设置拦截多个操作。

var handler = {
get: function(target, name) {
if (name === 'prototype') return Object.prototype;
return 'Hello, '+ name;
},
apply: function(target, thisBinding, args) { return args[0]; },
construct: function(target, args) { return args[1]; }
};

var fproxy = new Proxy(function(x,y) {
return x+y;
},  handler);

fproxy(1,2); // 1
new fproxy(1,2); // 2
fproxy.prototype; // Object.prototype
fproxy.foo; // 'Hello, foo'


2.2 Proxy方法

(1)get(target, propKey, receiver)

拦截对象属性的读取,比如proxy.foo和proxy[‘foo’],返回类型不限。最后一个参数receiver可选,当target对象设置了propKey属性的get函数时,receiver对象会绑定get函数的this对象。

(2)set(target, propKey, value, receiver)

拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。

(3)has(target, propKey)

拦截propKey in proxy的操作,返回一个布尔值。

(4)deleteProperty(target, propKey)

拦截delete proxy[propKey]的操作,返回一个布尔值。

(5)enumerate(target)

拦截for (var x in proxy),返回一个遍历器。

(6)ownKeys(target)

拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回对象所有自身的属性,而Object.keys()仅返回对象可遍历的属性。

(7)getOwnPropertyDescriptor(target, propKey)

拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。

(8)defineProperty(target, propKey, propDesc)

拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。

(9)preventExtensions(target)

拦截Object.preventExtensions(proxy),返回一个布尔值。

(10)getPrototypeOf(target)

拦截Object.getPrototypeOf(proxy),返回一个对象。

(11)isExtensible(target)

拦截Object.isExtensible(proxy),返回一个布尔值。

(12)setPrototypeOf(target, proto)

拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。

如果目标对象是函数,那么还有两种额外操作可以拦截。

(13)apply(target, object, args)

拦截Proxy实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。

(14)construct(target, args, proxy)

拦截Proxy实例作为构造函数调用的操作,比如new proxy(…args)。

实例方法查看阮一峰的ES6入门第11章Proxy和Reflect

三、Reflect

Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。Reflect对象的设计目的有这样几个。

3.1概述

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

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

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

// 老写法
'assign' in Object // true

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


(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让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);
}
});


上面代码中,每一个Proxy对象的拦截操作(get、delete、has),内部都调用对应的Reflect方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。

3.2 Reflect对象的方法

Reflect对象的方法清单如下,共14个。

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.enumerate(target)

Reflect.isExtensible(target)

Reflect.preventExtensions(target)

Reflect.getOwnPropertyDescriptor(target, name)

Reflect.getPrototypeOf(target)

Reflect.setPrototypeOf(target, prototype)

具体方法查看阮一峰的ES6入门第11章Proxy和Reflect

参考阮一峰的书籍ECMAScript 6 入门,感谢阮大神!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: