前言
了解vue3源码的应该都知道内置对象Reflect
,vue3底层是利用ES6的一些语法来进行构建的,核心的就包括但不限于Proxy
,Reflect
等方法。
平常大多情况下,我们很少使用Reflect
这个内置对象,因为Object
的方法很多都够我们用的,但是当使用Proxy
这个构造函数方法时,配合Reflect
会令人很意外舒畅,原因很简单,Reflect
的方法与Proxy
的方法数量相同,并且方法名一模一样。在相同方法的情况下,Reflect
的方法所返回的值,正是Proxy
方法所需要的值。
由于Reflect
都是对对象的操作,了解Reflect
的时候,会对比着或搭配着Object
的方法来。
正文
Reflect总共有13个方法:
get
set
has
ownKeys
apply
deleteProperty
definProperty
getOwnPropertyDescriptor
getPrototypeOf
setPrototypeOf
preventExtensions
isExtensible
construct
Reflect.get
查找对象中属性的值 ,这个方法接收三个参数,第一个为目标对象,第二个为要查找的key,第三个可以理解为this指向(默认为目标对象,可选),返回所查找到的值,如果没有,则返回undefined
js
//这个方法可以理解为是getter方法,比较简单,也好理解,只是查找一个对象中是否有这个属性
const obj = {
name: "iceCode",
age: 24,
};
//这里第三个参数写不写都一样,可写可不写
const ref = Reflect.get(obj, "age", obj);
console.log(ref);//24
const reft = Reflect.get(obj, "tel", obj);
console.log(reft);//undefined
Reflect.set
修改对象中属性的值,接收四个参数,第一个为目标对象(也可以是数组),第二个要修改的key,第三个要修改的值,第四个可以理解为this指向(默认为目标对象,可选),返回一个布尔值,修改成功返回true,修改失败返回false
js
//这个方法可以理解为是setter方法,修改对象中属性的值,没有就新增,有就修改
const obj = {
name: "iceCode",
age: 24,
};
const ref = Reflect.set(obj, "age", 18, obj);
const ref1 = Reflect.set(obj, "tel", 138338, obj);
console.log(ref,ref1);//true
console.log(obj.age);//18
console.log(obj.tel);//138338
//对数组修改
const arr=[]
//如果目标对象是一个数组,第二个传的就是索引
const ref = Reflect.set(arr, 2, 18);
console.log(arr[2])//18
//现在对数据进行劫持一下
const new_obj = Object.defineProperty(obj, "age", {
//是否可更改
writable: false,
});
//这种情况下就修改失败了
const ref = Reflect.set(new_obj, "age", 18);
console.log(ref);//false
console.log(new_obj.age);//24
Reflect.has
查找对象中是否存在某一个值,与in操作符一致,接收三个参数,第一个为目标对象,第二个是查找的key,第三个可以理解为this指向(默认为目标对象,可选),返回一个布尔值,可以找到就返回true,找不到就返回false
js
//与in操作符相同
const obj = {
name: "iceCode",
age: 24,
};
const ref = Reflect.has(obj, "age");
const ref1 = Reflect.has(obj, "tel");
console.log(ref, ref1);//true false
Reflect.ownKeys
返回一个数组,包含目标对象所有的key,包括不可遍历的key ,接收一个参数目标对象,返回值一个数组。它包括但不限于Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))
。这个方法在可以说正常写代码的时候使用的频次是Reflect
方法中较多的,因为返回值可以是不可遍历的key,所以代码量也会比正常使用Object的方法少很多
js
const sTar = Symbol("tar");
const sTal = Symbol("tal");
const obj = {
name: "iceCode",
age: 24,
[sTar]: 1,
[sTal]: 18888888,
};
const ref = Reflect.ownKeys(obj);
//在这里可以看到Symbol也是可以被读出来的
console.log(ref);//[ 'name', 'age', Symbol(tar), Symbol(tal) ]
//上面也说了,包括但不限于Symbol
//使用劫持,对对象进行劫持
const new_obj = Object.defineProperty(obj, "name", {
//是否可枚举
enumerable: false,
});
//劫持过后,我们再打印obj或者new_obj,发现被劫持的name不会再显示了
console.log(obj)//{ age: 24, [Symbol(tar)]: 1, [Symbol(tal)]: 18888888 }
//但是使用ownKeys方法后,还是可以得到name这个key
const ref = Reflect.ownKeys(obj);
console.log(ref);//[ 'name', 'age', Symbol(tar), Symbol(tal) ]
Reflect.apply
这里可以理解为改变this指向的一个方法 ,等同于Function.prototype.apply()
,接收三个参数,第一个是目标函数,第二个是this指向的对象,第三个参数列表,应为数组,返回值是调用完带着指定参数和 this 值的给定的函数后返回的结果
js
//可以理解为改变this指向的方法 第一个参数放一个可执行的方法,然后将指向改变为第二个参数,参数三就是方内的参数
const ref = Reflect.apply((1.2).toFixed, 12.234, [2]);
console.log(ref);//12.23
//为了更好的理解,这里自己写一个函数来表达一下
当Reflect.apply方法执行的时候,这个函数就会被调用
const t = function (value) {
//这里的value就是Reflect.apply的第三个参数,只能接收数组中的第一个参数,this指向的就是12
console.log(value, this);//2 12
return this;
};
const f = (v) => {
//因为箭头函数没有this,所以没办法改变他的this指向,this的结果也就是undefined
console.log(v, this);//2 undefined
return v;
};
const ref = Reflect.apply(t, 12, [2]);
const ref1 = Reflect.apply(t, 12, [2]);
//返回值就是可以执行函数返回的结果
console.log(ref,ref1);//12 2
Reflect.deleteProperty
用于删除对象中的属性 ,接收两个参数,第一个为目标对象,第二个为要删除key
等同于delete obj.key
js
const obj = {
name: "iceCode",
age: 24,
};
const ref = Reflect.deleteProperty(obj, "age");
console.log(ref, obj);//true { name: 'iceCode' }
//当对象为空时,删除任何属性都为true
const ref = Reflect.deleteProperty({}, "age");
console.log(ref);//true
//当对象属性不可配置时,是无法删除的,返回false
const ref = Reflect.deleteProperty(Object.freeze(obj), "age");
console.log(ref, obj);//false { name: 'iceCode', age: 24 }
Reflect.definProperty
此方法与Object.definProperty
的作用一样,唯一不同的就是它返回一个布尔值,而Object.definProperty
返回的是一个劫持之后的对象,详细看Object.defineProperty()
方法
接收三个参数,第一个目标对象,第二个修改或者说是劫持的key,第三个参数是一个对象,是一些定义或修改的描述
对象里的参数为:
configurable
是否能被修改 接收一个布尔值
enumerable
能否被找到或遍历 接收一个布尔值
value
赋值 可以是任意类型
writable
是否可以被赋值 接收一个布尔值
get
()方法 描述的对象的值被访问时触发
set
()方法 描述的对象的值被修改时触发
js
const obj = {
name: "iceCode",
age: 24,
};
//成功定义一个对象
const ref = Reflect.defineProperty(obj, "age", { value: 18 });
console.log(ref, obj);//true { name: 'iceCode', age: 18 }
Reflect.getOwnPropertyDescriptor
返回给定属性的描述 ,描述的内容就是definProperty
方法中可定义的属性,找不到则返回undefined
,与Object.getOwnPropertyDescriptor
类似,唯一不同的是Object.getOwnPropertyDescriptor
接受的如果不是一个对象则会强制转换成一个对象处理,Reflect.getOwnPropertyDescriptor
如果接受的不是一个对象,则会报错
js
const obj = {
name: "iceCode",
age: 24,
};
const ref = Reflect.getOwnPropertyDescriptor(obj, "age");
//在没有做任何成立的时候,描述的内容全为true
console.log(ref);//{ value: 24, writable: true, enumerable: true, configurable: true }
//如果不是对象,会报错接收的不是一个对象
const ref = Reflect.getOwnPropertyDescriptor("obj", 0);
//Reflect.getOwnPropertyDescriptor called on non-object
//Object的这个方法则会强制转换成对象处理
const objD = Object.getOwnPropertyDescriptor("obj", 0);
console.log(objD);//{ value: 'o', writable: false, enumerable: true, configurable: false }
Reflect.getPrototypeOf
返回对象的原型 ,与Object.getPropotypeOf
是一致的,只有在个别情况下两者的返回结果会有不同,接收一个参数,目标对象
js
//在ES6之后,Reflect.getPrototypeOf只接收对象类型,Object则可以接收任意类型
const obj = {
name: "iceCode",
age: 24,
};
Reflect.getPrototypeOf(obj);//Object.prototype
Reflect.getPrototypeOf('obj');//Reflect.getPrototypeOf called on non-object
//解决这个问题需要强制转换成Object类型
Reflect.getPrototypeOf(Objetc('obj'));//String.prototype
Reflect.getPrototypeOf(Objetc(12));//Number.prototype
//Object.getPrototypeOf省去了转换成对象类型的操作,任意类型都可以查出
Object.getPrototypeOf(obj);//Object.prototype
Object.getPrototypeOf('obj');//String.prototype
Object.getPrototypeOf(12);//Number.prototype
Reflect.setPrototypeOf
设置对象的原型 ,接收两个参数,第一个是目标对象(设置原型的对象),第二个为对象的原型对象(类型必须是对象或null,否则报错),返回一个布尔值,表明原型是否被设置成功。与Object.setPrototypeOf
一致,只有返回值不同
js
const obj = {
name: "iceCode",
age: 24,
};
const ref = Reflect.setPrototypeOf(obj, { tel: "18888888888" });
//表明设置成功,并且能被访问到
console.log(ref, obj.tel);//true 18888888888
//当我们定义不可被扩展以后
Reflect.preventExtensions(obj);
const ref = Reflect.setPrototypeOf(obj, { tel: "18888888888" });
//这里不可被设置成功,所写入的值也没有
console.log(ref, obj.tel);//false undefined
//Object.setPrototypeOf则会返回原有的对象
const ref = Object.setPrototypeOf(obj, { tel: "18888888888" });
console.log(ref, obj.tel);//{ name: 'iceCode', age: 24 } 18888888888
//在定义了不可扩展之后,Object.setPrototypeOf则会报错
Object.preventExtensions(obj);
const ref = Object.setPrototypeOf(obj, { tel: "18888888888" });//#<Object> is not extensible
Reflect.preventExtensions
阻止对象扩展(禁止添加新的属性) ,接收一个参数,目标对象,返回一个布尔值,表明是否设置成不可被扩展成功。 Object.preventExtensions
类似,唯一的差别是Object.preventExtensions
接受的目标对象如果不是对象类型则会强制转换成对象类型,而Reflect.preventExtensions
则会报错
js
const obj = {
name: "iceCode",
age: 24,
};
//定义不可被扩展
Reflect.preventExtensions(obj);
//当添加新的属性时会报错
obj.tel = 1223;//Cannot add property tel, object is not extensible
//但是这个只针对当前访问对象禁止扩展,如果你想,也可以在原型上添加新的属性
obj.__proto__.tel = 123;
//这时可以正常访问的
console.log(obj.tel); //123
Reflect.isExtensible
判断一个对象是否可扩展(即是否能添加属性) ,接收一个参数,目标对象,返回一个布尔值。与Object.isExtensible
类似,唯一的差别是Object.isExtensible
接受的目标对象如果不是对象类型则会强制转换成对象类型,而Reflect.isExtensible
则会报错
js
const obj = {
name: "iceCode",
age: 24,
};
const ref = Reflect.isExtensible(obj);
console.log(ref);//true
//当对象被定义不可扩展的时候,就会返回false
Reflect.preventExtensions(obj);
const ref = Reflect.isExtensible(obj);
console.log(ref);//false
Reflect.construct
类似于new操作符创建一个构造函数,接收三个参数,第一个构造函数,第二个为参数列表(数组),第三个参数为this指向(可选),返回值是传入的构造函数(或第三个参数的this指向)实例化的实例对象
js
function Foo(v, t) {
this.v = v;
this.t = t;
}
function Bar(v, t) {
this.f = v;
this.b = t;
this.name = "Bar";
}
//正常情况下 看着还没new 操作符简便呢
const ref = Reflect.construct(Foo, [1, 2], Foo);
console.log(ref);//Foo { v: 1, t: 2 }
//等同于
const nFoo = new Foo(1, 2);
console.log(nFoo);//Foo { v: 1, t: 2 }
//当要改变this指向的时候,可以看出Reflect.construct更为简洁一些
const ref = Reflect.construct(Foo, [1, 2], Bar);
console.log(ref instanceof Foo);//false
console.log(ref instanceof Bar);//true
//等价于
const nFoo = Object.create(Bar.prototype);
Foo.apply(nFoo, [1, 2]);
console.log(nFoo, ref);//Bar { v: 1, t: 2, name: 'Foo' } Bar { v: 1, t: 2, name: 'Foo' }
结尾
一般情况下,我们很少单独使用Reflect
中的一些方法,但是如果要使用构造函数Proxy
的时候,就可以大胆为所欲为的使用了,因为相同的方法名,相同的参数,让我们可以不用知道Proxy
的各个属性需要return
什么的参数,直接return Reflect
对象的相同参数就可以了,记住了Reflect
的方法即使用也就记住了Proxy
的方法,只要玩懂了Reflect
也可以轻松玩转Proxy