ES6新增对象

Symbol

1.ES5的对象属性名都是字符串,容易造成命名冲突,ES6引入Symbol机制,以保证每个属性名都是独一无二的;

2.Symbol是JS的第七种原始数据类型,其他六种分别是undefined、null、布尔值、字符串、数值、对象;

3.Symbol值通过Symbol函数生成,凡是属于Symbol类型的属性名,保证不会与其他属性名产生冲突;

lets1=Symbol();

lets2=Symbol('foo');-->s2.toString();//

1.为Symbol函数添加的参数,就等于加上了描述,更容易区分,即使参数名相同,两个Symbol值也是不同的;

2.如果参数是一个对象,就会调用该对象的toString(),将其转为字符串,然后才生成一个Symbol值;

constobj={

toString() {

return'abc';

       }

   };

constsym=Symbol(obj);//Symbol(abc)

4.Symbol值不能与其他类型的值进行运算,即使是字符串拼接也不行;

1.但是,Symbol值可以显式转为字符串;

String(s2);s2.toString();// "Symbol(foo)"

2.Symbol值也可以转为布尔值,但是不能转为数值。

Boolean(s2)// true        !s2  // false

5.Symbol值作为对象的属性

letsym=Symbol();

1.第一种写法

leta={};

a[sym]='Hello!';

2.第二种写法

lets=Symble();

leta={

[sym]:'Hello!',

[s](arg) {...}

       };

3.第三种写法

leta={};

Object.defineProperty(a,sym, {value:'Hello!'});

4.获取属性值,不能通过.访问或设置

a[sym]// "Hello!"

a[s](123);

6.把Symbol作为一组不重复的常量值

constlog={};

log.levels={

DEBUG:Symbol('debug'),

INFO:Symbol('info'),

WARN:Symbol('warn')

   };

console.log(log.levels.DEBUG,'debug message');

console.log(log.levels.INFO,'info message');

constCOLOR_RED=Symbol();

constCOLOR_GREEN=Symbol();

functiongetComplement(color) {

switch(color) {

caseCOLOR_RED:

......

caseCOLOR_GREEN:

......

default:

......

       }

   }

7.Symbol作为属性名时,不会被for-in、for-of循环中

1.也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回;

2.但Symbol绝不是私有属性,Object.getOwnPropertySymbols()可以获取对象的所有Symbol属性名;

3.Object.getOwnPropertySymbols()返回一个Symbol属性名的数组

constobj={};

leta=Symbol('a');letb=Symbol('b');

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

constsyms=Object.getOwnPropertySymbols(obj);//[Symbol(a), Symbol(b)]

4.Reflect.ownKeys():可以返回所有类型的键名,包括常规键名和Symbol键名;

letobj={

[Symbol('ab')]:1,

enum:2

   };

Reflect.ownKeys(obj);//["enum", "Symbol(ab)]

5.由于Symbol作为属性名时,不会被常规方法遍历得到,可以为对象定义一些非私有的、但又希望只用于内部的方法。

8.Symbol.for(),Symbol.keyFor()

1.Symbol.for('key'):复用Symbol,如果以key作为参数的Symbol存在,则直接返回,不存在则创建;

lets1=Symbol.for('foo');//新建

lets2=Symbol.for('foo');//复用

s1===s2;// true

2.Symbol.for()会被登记在全局环境中以供搜索,如果全局环境中已经存在了同key的Symbol,则复用;

而Symbol()每次都会新建一个不同的Symbol,且不会被登记在全局环境,所以不会被搜索到;

3.Symbol.keyFor():在全局环境中搜索一个已登记的Symbol

lets1=Symbol.for("foo");

Symbol.keyFor(s1);// "foo"

lets2=Symbol("foo");//不会被登记

Symbol.keyFor(s2);// undefined

4.Symbol.for登记在全局环境中,可以在不同的iframe或serviceworker中取到同一个值。

内置的Symbol值

ES6提供了11个内置的Symbol值,指向语言内部使用的方法

1.Symbol.hasInstance:对象的Symbol.hasInstance属性,指向一个内部方法;

1.当其他对象调用instanceof时,会调用此内部方法

classTest{

[Symbol.hasInstance](foo) {

returnfooinstanceofArray;-->如果foo是数组,则返回true

       }

   }

[1,2,3]instanceofnewTest();// true

2.静态方式

classEven{

static[Symbol.hasInstance](obj) {

returnNumber(obj)%2===0;

       }

   }

// 等同于:

constEven={

[Symbol.hasInstance](obj) {

returnNumber(obj)%2===0;

       }

   };

1instanceofEven// false

2instanceofEven// true

2.Symbol.isConcatSpreadable:对象的布尔值属性,表示该对象用于Array.prototype.concat()时,是否可以展开;

3.Symbol.species,Symbol.match,Symbol.replace,Symbol.search,Symbol.split,

4.Symbol.iterator:对象的Symbol.iterator属性,指向该对象的默认遍历器方法;

1.for-of遍历对象时,会调用Symbol.iterator方法,返回该对象的默认遍历器

constmyIter={};

myIter[Symbol.iterator]=function*() {

yield1;

yield2;

   };

[...myIter]// [1, 2]

5.Symbol.toPrimitive,Symbol.toStringTag,Symbol.unscopables

Proxy

1.Proxy:代理,在目标对象之前架设的一层拦截,外界对该对象的访问,都必须先通过这层拦截;

varproxy=newProxy(target,handler);

1.target所要拦截的目标对象

2.handler也是一个对象,用来定制拦截行为

2.get()

1.用于拦截某个属性的读取操作,接受三个参数:目标对象、属性名、proxy实例本身(可选)

varperson={name:"张三"};

varproxy=newProxy(person, {

get:function(target,property) {

if(propertyintarget) {--->访问的属性存在,则返回属性值

returntarget[property];

}else{----->访问的属性不存在,则抛出一个异常

thrownewReferenceError("Property \""+property+"\" does not exist.");

               }

           }

       });

proxy.name// "张三"

proxy.age// 抛出一个错误

2.get方法可以继承,拦截操作定义在Prototype对象上面

letobj=Object.create(proxy);

obj.age//抛出一个错误

3.如果一个属性不可配置且不可写,则Proxy不能修改该属性,否则通过Proxy对象访问该属性会报错

consttarget=Object.defineProperties({}, {

foo: {

value:123,

writable:false,//不可写

configurable:false//不可配置

           },

       });

constproxy=newProxy(target, {

get(target,propKey) {

return'abc';--->修改属性值

           }

       });

proxy.foo//访问报错

2.set()

1.用来拦截某个属性的赋值操作,接受四个参数:目标对象、属性名、属性值、Proxy实例本身(可选)。

letperson=newProxy({}, {

set:function(obj,prop,value) {

if(prop==='age') {

if(!Number.isInteger(value)) {-->不是数字,抛出异常

thrownewTypeError('The age is not an integer');

                   }

if(value>200) {-->数字大于200,抛出异常

thrownewRangeError('The age seems invalid');

                   }

               }

obj[prop]=value;//设置属性

           }

       });

person.age=100;//赋值成功

person.age='young';// 报错

person.age=300;// 报错

2.在set()和get()中判断访问的属性名是否以_开头,如果是,则抛出异常,从而禁止访问"私有"属性;

3.如果目标对象自身的某个属性,不可写且不可配置,那么set()将不起作用;

4.另外,严格模式下,set代理必须显式返回true,否则就会报错

'use strict';

constproxy=newProxy({}, {

set:function(obj,prop,value,receiver) {

obj[prop]=receiver;

returnfalse;// 无论有没有下面这一行,都会报错

       }

   });

proxy.foo='bar';//TypeError

3.apply():拦截函数的调用、call()和apply()操作

1.apply()接受三个参数:目标对象、目标对象的上下文对象(this)、目标对象的参数数组;

varfn=function(left,right) {returnleft+right; };

varp=newProxy(fn, {

apply:function(target,ctx,args) {

returntarget.call(ctx,...args)*10;// Reflect.apply(...arguments)*10;

       }

   });

p(1,2);//30

p.call(null,2,2)//40

p.apply(null, [3,2]);//50

2.另外,直接调用Reflect.apply(),也会被拦截

Reflect.apply(p,null, [4,2]);// 60

4.has():拦截HasProperty操作,判断对象是否具有某个属性,典型的操作就是in运算符

1.has()接受两个参数:目标对象、需查询的属性名;

2.使用has()隐藏_开头的属性,不被in运算符发现

vartarget={_prop:'foo',prop:'foo'};

varproxy=newProxy(target, {

has(target,key) {

if(key[0]==='_') {

returnfalse;//隐藏 _ 开头的属性

           }

returnkeyintarget;

       }

   });

'_prop'inproxy// false

3.如果原对象不可配置或者禁止扩展,has()拦截会报错;

4.注意:has方法拦截的是HasProperty操作,而不是HasOwnProperty操作,

也就是说,has()不判断一个属性是对象自身的属性,还是继承的属性;

5.另外,虽然for-in循环也用到了in运算符,但是has()拦截对for-in循环不生效。

5.construct():用于拦截new命令

1.接受三个参数:目标对象、构造函数的参数对象、new命令作用的构造函数

varp=newProxy(function(){}, {

construct:function(target,args) {

return{value:args[0]*10};

       }

   });

(newp(1)).value//10

2.construct()返回的必须是一个对象,否则会报错

6.deleteProperty():拦截delete操作;

1.若返回false或抛出异常,则属性会删除失败;

2.目标对象自身的不可配置的属性,不能被删除,否则报错.

7.defineProperty():拦截Object.defineProperty()操作

1.返回false时,新属性添加无效;

2.如果目标对象不可扩展,defineProperty()不能增加目标对象上不存在属性,否则会报错;

3.如果目标对象的某个属性不可写或不可配置,defineProperty()也不得改变这两个设置。

8.ownKeys():拦截对象自身属性的读取操作

1.拦截操作

Object.getOwnPropertyNames()、Object.keys()

Object.getOwnPropertySymbols()、for-in

2.拦截Object.keys()时,会自动过滤三类属性:

目标对象上不存在的属性、属性名为Symbol值、不可遍历(enumerable)的属性

lettarget={a:1,b:2,c:3, [Symbol.for('secret')]:'4'};

Object.defineProperty(target,'key', {--->新添加一个属性,

enumerable:false,----->属性不可遍历

configurable:true,

writable:true,

value:'static'----->属性值为'static'

   });

letproxy=newProxy(target, {

ownKeys(target) {

return['a','d',Symbol.for('secret'),'key'];

       }

   });

Object.keys(proxy)// ['a'],自动过滤了

3.ownKeys()返回的只能是字符串或Symbol值的数组,否则就会报错;

4.如果目标对象自身包含不可配置的属性,则ownKeys()必须返回该属性,否则报错;

5.如果目标对象是不可扩展的,ownKeys()返回的数组之中必须包含原对象的所有属性,且不能包含多余的属性,否则报错;

varobj={a:1};

Object.preventExtensions(obj);//配置不可扩展

varp=newProxy(obj, {

ownKeys:function(target) {

return['a','b'];//返回多余属性

       }

   });

Object.getOwnPropertyNames(p);//报错Uncaught TypeError

9.getPrototypeOf():主要用来拦截获取对象原型

1.拦截操作

Object.prototype.__proto__、Object.prototype.isPrototypeOf()

Object.getPrototypeOf()、Reflect.getPrototypeOf()、instanceof

2.getPrototypeOf()的返回值必须是对象或者null,否则报错;

3.如果目标对象不可扩展,getPrototypeOf()必须返回目标对象的原型对象。

10.setPrototypeOf():主要用来拦截Object.setPrototypeOf()

1.该方法只能返回布尔值,否则会被自动转为布尔值;

2.如果目标对象不可扩展,setPrototypeOf()不得改变目标对象的原型.

11.Proxy.revocable():返回一个可取消的Proxy实例

let{proxy,revoke}=Proxy.revocable({}, {});

proxy.foo=123;

proxy.foo// 123

revoke();

proxy.foo// TypeError: Revoked

1.此方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例;

2.当执行revoke()之后,再访问Proxy实例,就会抛出一个错误;

3.使用场景:目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

12.this问题

1.虽然Proxy可以代理目标对象的访问,但它不是透明代理,即使不做任何拦截,也无法保证与目标对象的行为一致,

主要是因为:使用Proxy代理后,目标对象内部的this关键字会指向Proxy代理;

2.有些原生对象的内部属性,只有通过正确的this才能拿到,所以Proxy也无法代理这些原生对象的属性

consttarget=newDate();

constproxy=newProxy(target, {});

proxy.getDate();// TypeError

3.让this绑定原始对象,以访问Date的getDate()

consttarget=newDate('2015-01-01');

constproxy=newProxy(target, {

get(target,prop) {

if(prop==='getDate') {

returntarget.getDate.bind(target);

               }

returnReflect.get(target,prop);

           }

       });

proxy.getDate()// 1

13.Proxy可以拦截目标对象的任意属性,这使得它很合适用于Web服务的客户端;

14.同理,Proxy也可以用来实现数据库的ORM层。

Reflect

1.ES6设计Reflect的目的

1.将Object对象的一些明显属于语言内部的方法部署在Reflect上,如Object.defineProperty()

2.修改某些Object方法的返回结果,让其变得更合理;

Object.defineProperty(obj,name,desc)在无法定义属性时,会抛出错误;

而Reflect.defineProperty(obj,name,desc)则会返回false

3.让Object操作都变成函数形式

nameinobj--->Reflect.has(obj,name)

deleteobj[name]--->Reflect.deleteProperty(obj,name)

4.Reflect的方法与Proxy的方法一一对应,让Proxy可以调用Reflect上的方法,完成默认行为;

也就是说,不管Proxy怎么修改默认行为,总可以在Reflect上获取对应方法的默认行为。

varproxy=newProxy(obj, {

get(target,name) {

console.log('get',target,name);

returnReflect.get(target,name);

           },

deleteProperty(target,name) {

console.log('delete'+name);

returnReflect.deleteProperty(target,name);

           },

has(target,name) {

console.log('has'+name);

returnReflect.has(target,name);

           }

       });

5.Proxy对象的拦截get、delete、has操作,内部又都调用对应的Reflect方法,保证原生行为能够正常执行。

2.Reflect.get(target,name,receiver):返回target对象的name属性值,没有则返回undefined

1.第一个参数target必须是对象,否则会报错;

2.如果name属性部署了读取函数(getter),则读取函数的this绑定receiver

vartarget={

foo:1,bar:2,

getbaz() {

returnthis.foo+this.bar;

       },

   };

varreceiver={foo:4,bar:4};

Reflect.get(target,'baz');// 3

Reflect.get(target,'baz',receiver);// 8

3.Reflect.set(target,name,value,receiver):设置target对象的name属性值为value

1.如果name属性设置了赋值函数(setter),则赋值函数的this绑定receiver

vartarget={

foo:4,

setbar(value) {

returnthis.foo=value;

           },

       };

varreceiver={foo:0};

Reflect.set(target,'bar',1,receiver);

target.foo// 4,target对象不受影响

receiver.foo// 1,显式绑定了receiver,只影响receiver

Reflect.set(target,'foo',2);

target.foo// 2

2.Proxy和Reflect联合使用时,Proxy拦截赋值操作,Reflect完成赋值的默认行为,而且传入了receiver,

那么Reflect.set()会触发Proxy.defineProperty()拦截

letobj={a:'a'};

letp=newProxy(obj, {

set(target,key,value,receiver) {

console.log('set');

Reflect.set(target,key,value,receiver)

           },

defineProperty(target,key,attribute) {

console.log('defineProperty');

Reflect.defineProperty(target,key,attribute);

           }

       });

p.a='A';//触发set()、defineProperty()

1.因为Proxy.set()的receiver参数总是指向当前的Proxy实例p,

一旦Reflect.set()传入receiver,就会将属性赋值到receiver上,导致触发defineProperty拦截。

2.所以,此时不要给Reflect.set()传入receiver

4.Reflect.has(obj,name):对应nameinobj里的in运算符,存在则返回true,否则返回false

5.Reflect.deleteProperty(obj,name):等同于deleteobj[name],返回true/false

6.Reflect.construct(target,args):等同于newtarget(...args),一种不使用new来调用构造函数的方法;

7.Reflect.getPrototypeOf(obj):用于读取对象的__proto__属性,对应Object.getPrototypeOf(obj)

constmyObj=newFancyThing();

Object.getPrototypeOf(myObj)===FancyThing.prototype;

Reflect.getPrototypeOf(myObj)===FancyThing.prototype;

1.区别:如果参数不是对象,Object.getPrototypeOf会将这个参数转为对象,然后再运行;

而Reflect.getPrototypeOf会报错。

Object.getPrototypeOf(1)// Number{[[PrimitiveValue]]: 0}

Reflect.getPrototypeOf(1)// 报错

8.Reflect.setPrototypeOf(obj,newProto):设置目标对象的原型,对应Object.setPrototypeOf(obj,newProto)

1.返回一个布尔值,表示是否设置成功

constobj={};

Reflect.setPrototypeOf(obj,Array.prototype);

obj.length// 0,数组Array的原型被设置到obj对象上

2.如果目标对象禁止扩展,Reflect.setPrototypeOf()返回false

3.如果obj不是对象,Object.setPrototypeOf()会返回obj,而Reflect.setPrototypeOf会报错;

9.Reflect.apply(func,thisArg,args)

1.等同于Function.prototype.apply.call(func,thisArg,args),用于绑定this对象后执行给定函数;

2.一般来说,fn.apply(obj,args)可以绑定一个函数的this对象,但如果函数定义了自己的apply(),

就只能写成Function.prototype.apply.call(fn,obj,args),Reflect则可以简化

constages=[11,33,12,54,18,96];

// 旧写法

constyoungest=Math.min.apply(Math,ages);

constoldest=Math.max.apply(Math,ages);

consttype=Object.prototype.toString.call(youngest);

// 新写法

constyoungest=Reflect.apply(Math.min,Math,ages);

constoldest=Reflect.apply(Math.max,Math,ages);

consttype=Reflect.apply(Object.prototype.toString,youngest, []);

10.Reflect.defineProperty(target,propertyKey,attributes)

1.用于替代Object.defineProperty(),用于为对象定义属性

2.可以与Proxy.defineProperty()配合使用

constp=newProxy({}, {

defineProperty(target,prop,descriptor) {

console.log(descriptor);

returnReflect.defineProperty(target,prop,descriptor);

       }

   });

p.foo='bar';// {value: "bar", writable: true, enumerable: true, configurable: true}

p.foo// "bar"

11.Reflect.ownKeys(target):用于返回对象的所有属性名

1.基本等同于Object.getOwnPropertyNames()与Object.getOwnPropertySymbols()之和

varmyObject={

foo:1,bar:2, [Symbol.for('baz')]:3, [Symbol.for('bing')]:4

       };

Reflect.ownKeys(myObject)// ['foo', 'bar', Symbol(baz), Symbol(bing)]

12.Reflect.isExtensible(target):对应Object.isExtensible(),当前对象是否可扩展

13.Reflect.preventExtensions(target):让一个对象变为不可扩展,返回true/false,表示是否成功。

你可能感兴趣的:(ES6新增对象)