Symbol
1.ES5的對象屬性名都是字符串荠雕,容易造成命名沖突砍鸠,ES6引入Symbol機制扩氢,以保證每個屬性名都是獨一無二的;
2.Symbol是JS的第七種原始數(shù)據(jù)類型爷辱,其他六種分別是undefined录豺、null、布爾值饭弓、字符串双饥、數(shù)值、對象弟断;
3.Symbol值通過Symbol函數(shù)生成咏花,凡是屬于Symbol類型的屬性名,保證不會與其他屬性名產(chǎn)生沖突阀趴;
lets1=Symbol();
lets2=Symbol('foo');-->s2.toString();//
1.為Symbol函數(shù)添加的參數(shù)昏翰,就等于加上了描述,更容易區(qū)分舍咖,即使參數(shù)名相同矩父,兩個Symbol值也是不同的;
2.如果參數(shù)是一個對象排霉,就會調(diào)用該對象的toString()窍株,將其轉(zhuǎn)為字符串,然后才生成一個Symbol值;
constobj={
toString() {
return'abc';
? ? ?? }
?? };
constsym=Symbol(obj);//Symbol(abc)
4.Symbol值不能與其他類型的值進行運算攻柠,即使是字符串拼接也不行球订;
1.但是,Symbol值可以顯式轉(zhuǎn)為字符串瑰钮;
String(s2);s2.toString();// "Symbol(foo)"
2.Symbol值也可以轉(zhuǎn)為布爾值冒滩,但是不能轉(zhuǎn)為數(shù)值。
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.獲取屬性值浪谴,不能通過.訪問或設(shè)置
a[sym]// "Hello!"
a[s](123);
6.把Symbol作為一組不重復(fù)的常量值
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循環(huán)中
1.也不會被Object.keys()苟耻、Object.getOwnPropertyNames()篇恒、JSON.stringify()返回;
2.但Symbol絕不是私有屬性,Object.getOwnPropertySymbols()可以獲取對象的所有Symbol屬性名;
3.Object.getOwnPropertySymbols()返回一個Symbol屬性名的數(shù)組
constobj={};
leta=Symbol('a');letb=Symbol('b');
obj[a]='Hello';obj[b]='World';
constsyms=Object.getOwnPropertySymbols(obj);//[Symbol(a), Symbol(b)]
4.Reflect.ownKeys():可以返回所有類型的鍵名凶杖,包括常規(guī)鍵名和Symbol鍵名胁艰;
letobj={
[Symbol('ab')]:1,
enum:2
?? };
Reflect.ownKeys(obj);//["enum", "Symbol(ab)]
5.由于Symbol作為屬性名時,不會被常規(guī)方法遍歷得到,可以為對象定義一些非私有的腾么、但又希望只用于內(nèi)部的方法奈梳。
8.Symbol.for(),Symbol.keyFor()
1.Symbol.for('key'):復(fù)用Symbol解虱,如果以key作為參數(shù)的Symbol存在攘须,則直接返回,不存在則創(chuàng)建;
lets1=Symbol.for('foo');//新建
lets2=Symbol.for('foo');//復(fù)用
s1===s2;// true
2.Symbol.for()會被登記在全局環(huán)境中以供搜索饭寺,如果全局環(huán)境中已經(jīng)存在了同key的Symbol阻课,則復(fù)用;
而Symbol()每次都會新建一個不同的Symbol艰匙,且不會被登記在全局環(huán)境限煞,所以不會被搜索到;
3.Symbol.keyFor():在全局環(huán)境中搜索一個已登記的Symbol
lets1=Symbol.for("foo");
Symbol.keyFor(s1);// "foo"
?
lets2=Symbol("foo");//不會被登記
Symbol.keyFor(s2);// undefined
4.Symbol.for登記在全局環(huán)境中员凝,可以在不同的iframe或serviceworker中取到同一個值署驻。
內(nèi)置的Symbol值
ES6提供了11個內(nèi)置的Symbol值,指向語言內(nèi)部使用的方法
1.Symbol.hasInstance:對象的Symbol.hasInstance屬性健霹,指向一個內(nèi)部方法旺上;
1.當其他對象調(diào)用instanceof時,會調(diào)用此內(nèi)部方法
classTest{
[Symbol.hasInstance](foo) {
returnfooinstanceofArray;-->如果foo是數(shù)組糖埋,則返回true
? ? ?? }
?? }
[1,2,3]instanceofnewTest();// true
2.靜態(tài)方式
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遍歷對象時,會調(diào)用Symbol.iterator方法埠巨,返回該對象的默認遍歷器
constmyIter={};
myIter[Symbol.iterator]=function*() {
yield1;
yield2;
?? };
[...myIter]// [1, 2]
5.Symbol.toPrimitive历谍,Symbol.toStringTag,Symbol.unscopables
Proxy
1.Proxy:代理辣垒,在目標對象之前架設(shè)的一層攔截扮饶,外界對該對象的訪問,都必須先通過這層攔截乍构;
varproxy=newProxy(target,handler);
1.target所要攔截的目標對象
2.handler也是一個對象,用來定制攔截行為
2.get()
1.用于攔截某個屬性的讀取操作,接受三個參數(shù):目標對象哥遮、屬性名岂丘、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.用來攔截某個屬性的賦值操作寨蹋,接受四個參數(shù):目標對象、屬性名扔茅、屬性值已旧、Proxy實例本身(可選)。
letperson=newProxy({}, {
set:function(obj,prop,value) {
if(prop==='age') {
if(!Number.isInteger(value)) {-->不是數(shù)字召娜,拋出異常
thrownewTypeError('The age is not an integer');
? ? ? ? ? ? ? ? ?? }
if(value>200) {-->數(shù)字大于200运褪,拋出異常
thrownewRangeError('The age seems invalid');
? ? ? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? }
obj[prop]=value;//設(shè)置屬性
? ? ? ? ?? }
? ? ?? });
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():攔截函數(shù)的調(diào)用、call()和apply()操作
1.apply()接受三個參數(shù):目標對象殖演、目標對象的上下文對象(this)氧秘、目標對象的參數(shù)數(shù)組;
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.另外趴久,直接調(diào)用Reflect.apply()丸相,也會被攔截
Reflect.apply(p,null, [4,2]);// 60
4.has():攔截HasProperty操作,判斷對象是否具有某個屬性彼棍,典型的操作就是in運算符
1.has()接受兩個參數(shù):目標對象灭忠、需查詢的屬性名;
2.使用has()隱藏_開頭的屬性座硕,不被in運算符發(fā)現(xiàn)
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循環(huán)也用到了in運算符有鹿,但是has()攔截對for-in循環(huán)不生效。
5.construct():用于攔截new命令
1.接受三個參數(shù):目標對象谎脯、構(gòu)造函數(shù)的參數(shù)對象葱跋、new命令作用的構(gòu)造函數(shù)
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()也不得改變這兩個設(shè)置社牲。
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值的數(shù)組,否則就會報錯息罗;
4.如果目標對象自身包含不可配置的屬性掂咒,則ownKeys()必須返回該屬性,否則報錯迈喉;
5.如果目標對象是不可擴展的绍刮,ownKeys()返回的數(shù)組之中必須包含原對象的所有屬性,且不能包含多余的屬性挨摸,否則報錯孩革;
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.該方法只能返回布爾值帆啃,否則會被自動轉(zhuǎn)為布爾值;
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屬性是一個函數(shù)诽偷,可以取消Proxy實例;
2.當執(zhí)行revoke()之后坤学,再訪問Proxy實例,就會拋出一個錯誤;
3.使用場景:目標對象不允許直接訪問报慕,必須通過代理訪問深浮,一旦訪問結(jié)束,就收回代理權(quán)眠冈,不允許再次訪問飞苇。
12.this問題
1.雖然Proxy可以代理目標對象的訪問,但它不是透明代理蜗顽,即使不做任何攔截布卡,也無法保證與目標對象的行為一致,
主要是因為:使用Proxy代理后雇盖,目標對象內(nèi)部的this關(guān)鍵字會指向Proxy代理忿等;
2.有些原生對象的內(nèi)部屬性,只有通過正確的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服務(wù)的客戶端;
14.同理狸相,Proxy也可以用來實現(xiàn)數(shù)據(jù)庫的ORM層薛匪。
Reflect
1.ES6設(shè)計Reflect的目的
1.將Object對象的一些明顯屬于語言內(nèi)部的方法部署在Reflect上,如Object.defineProperty()
2.修改某些Object方法的返回結(jié)果脓鹃,讓其變得更合理逸尖;
Object.defineProperty(obj,name,desc)在無法定義屬性時,會拋出錯誤将谊;
而Reflect.defineProperty(obj,name,desc)則會返回false
3.讓Object操作都變成函數(shù)形式
nameinobj--->Reflect.has(obj,name)
deleteobj[name]--->Reflect.deleteProperty(obj,name)
4.Reflect的方法與Proxy的方法一一對應(yīng)冷溶,讓Proxy可以調(diào)用Reflect上的方法,完成默認行為尊浓;
也就是說逞频,不管Proxy怎么修改默認行為,總可以在Reflect上獲取對應(yīng)方法的默認行為栋齿。
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操作,內(nèi)部又都調(diào)用對應(yīng)的Reflect方法基协,保證原生行為能夠正常執(zhí)行歌亲。
2.Reflect.get(target,name,receiver):返回target對象的name屬性值,沒有則返回undefined
1.第一個參數(shù)target必須是對象澜驮,否則會報錯陷揪;
2.如果name屬性部署了讀取函數(shù)(getter),則讀取函數(shù)的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):設(shè)置target對象的name屬性值為value
1.如果name屬性設(shè)置了賦值函數(shù)(setter)杂穷,則賦值函數(shù)的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聯(lián)合使用時飞蚓,Proxy攔截賦值操作,Reflect完成賦值的默認行為廊蜒,而且傳入了receiver趴拧,
那么Reflect.set()會觸發(fā)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';//觸發(fā)set()、defineProperty()
1.因為Proxy.set()的receiver參數(shù)總是指向當前的Proxy實例p山叮,
一旦Reflect.set()傳入receiver著榴,就會將屬性賦值到receiver上,導(dǎo)致觸發(fā)defineProperty攔截聘芜。
2.所以兄渺,此時不要給Reflect.set()傳入receiver
4.Reflect.has(obj,name):對應(yīng)nameinobj里的in運算符,存在則返回true汰现,否則返回false
5.Reflect.deleteProperty(obj,name):等同于deleteobj[name]挂谍,返回true/false
6.Reflect.construct(target,args):等同于newtarget(...args),一種不使用new來調(diào)用構(gòu)造函數(shù)的方法瞎饲;
7.Reflect.getPrototypeOf(obj):用于讀取對象的__proto__屬性口叙,對應(yīng)Object.getPrototypeOf(obj)
constmyObj=newFancyThing();
Object.getPrototypeOf(myObj)===FancyThing.prototype;
Reflect.getPrototypeOf(myObj)===FancyThing.prototype;
1.區(qū)別:如果參數(shù)不是對象,Object.getPrototypeOf會將這個參數(shù)轉(zhuǎn)為對象嗅战,然后再運行妄田;
而Reflect.getPrototypeOf會報錯。
Object.getPrototypeOf(1)// Number{[[PrimitiveValue]]: 0}
Reflect.getPrototypeOf(1)// 報錯
8.Reflect.setPrototypeOf(obj,newProto):設(shè)置目標對象的原型驮捍,對應(yīng)Object.setPrototypeOf(obj,newProto)
1.返回一個布爾值疟呐,表示是否設(shè)置成功
constobj={};
Reflect.setPrototypeOf(obj,Array.prototype);
obj.length// 0,數(shù)組Array的原型被設(shè)置到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對象后執(zhí)行給定函數(shù);
2.一般來說鲁冯,fn.apply(obj,args)可以綁定一個函數(shù)的this對象拷沸,但如果函數(shù)定義了自己的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):對應(yīng)Object.isExtensible(),當前對象是否可擴展
13.Reflect.preventExtensions(target):讓一個對象變?yōu)椴豢蓴U展跨扮,返回true/false序无,表示是否成功。