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,表示是否成功。