ECMAScript 6笔画速记(Symbol, Proxy 和 Reflect)

一、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
入门,感谢阮大神!