Proxy-Reflect
1 监听对象的操作
1. 使用Object.defineProperty来监听
var obj = {
name : 'why',
age : 18,
}
Object.defineProperty(obj, 'age', {
get(){
console.log('getter ');
return this.age
},
set(newValue){
console.log('setter ' + newValue);
this.age = newValue;
},
})
console.log(obj.age);
//test1.html:60 Uncaught RangeError: Maximum call stack size exceeded
//存取属性描述符里面的模拟一个访问和设置的默认行为的时候
//必须的在里面新添一个属性不然会造成循环引用(狂call布置)
//就是不能再get函数里面直接写return this.age,
//方法1: 给obj添加一个_age属性
//方法2:把obj.age放到一个变量里面
/* obj.age --> get.call(obj)---> return this.age--->obj.age
obj.age --> get.call(obj) --->this._age */
2.循环引用的解决方法1
var obj = {
name : 'why',
age : 18,
}
Object.defineProperty(obj, 'age', {
get(){
console.log('getter ');
return this._age
},
set(newValue){
console.log('setter ' + newValue);
this._age = newValue;
},
})
console.log(obj.age);
//getter
//undefined
obj.age = 19;
console.log(obj.age);
//setter 19
//getter
//19
//obj.age --> get.call(obj) --->this._age
console.log(obj.hasOwnProperty("age")); //true
console.log(obj.hasOwnProperty("_age")); //true 只有在设置完一次obj.age=18之后obj才有_age这个属性,这个时候才是true,否则一次都没设置过的话是false
3.循环引用的解决方法2
var obj = {
name: "why",
age: 18,
};
let myage = obj.age;
Object.defineProperty(obj, "age", {
get() {
console.log("getter");
return myage;
},
set(newValue) {
console.log("setter");
myage = newValue;
},
});
console.log(obj.age); //18
obj.age = 30;
console.log(obj.age); //30
4.监视对象的所有属性的变化
var obj = {
name : 'why',
age : 18,
}
Object.keys(obj).forEach(key => {
let value = obj[key];
Object.defineProperty(obj, key, {
get: function(){
console.log(`监听到obj对象的${key}属性被访问了`)
return value
},
set: function(newValue) {
console.log(`监听到obj对象的${key}属性被设置值`)
value = newValue
}
})
})
console.log(obj.name);
obj.name = "kobe"
console.log(obj.age);
obj.age = 19
5.Object.defineProperty来监听对象属性变化的缺点
75.PNG var obj = {
name: "why", // 数据属性描述符
age: 18, // 数据属性描述符
};
console.log(Object.getOwnPropertyDescriptor(obj, "age"));
// {value: 18, writable: true, enumerable: true, configurable: true}
Object.defineProperty(obj, "age", {
get() {
console.log("getter ");
return this._age;
},
set(newValue) {
console.log("setter " + newValue);
this._age = newValue;
},
});
console.log(Object.getOwnPropertyDescriptor(obj, "age"));
//{enumerable: true, configurable: true, get: ƒ, set: ƒ}
//name个age就是数据属性,但是要监听它的变化的话,他们就变成了存取属性了。破坏了definePropery的原来的目的了。
2 Proxy
1. Proxy的基本使用
76.PNG var obj = {
name : 'why',
age : 18
};
const proxyObj = new Proxy(obj, {});
/*
1.先创建一个proxy对象,这个proxy对象就是obj对象进行代理
2.第二个参数{}里面是捕获器,里面可以捕获对代理对象的各种操作
*/
console.log(proxyObj.name);//why
console.log(proxyObj.age);//18
proxyObj.name = "coder";
proxyObj.age = 30;
console.log(obj.name);//coder
console.log(obj.age);//30
//通过修改代理对象,把原来的对象也会修改掉
//捕获器不重写的情况下,他会自动完成对原来对象的操作
//通过代理对象设置值,直接设置到原来的对象里面
77.PNG
const obj = {
name: "why",
age: 18
}
const objProxy = new Proxy(obj, {
// 获取值时的捕获器,获取的时候的自动回调
get: function(target, key) {
//target指的就是objProxy代理的元对象obj
console.log(`监听到对象的${key}属性被访问了`, target)
return target[key]
},
// 设置值时的捕获器
set: function(target, key, newValue) {
console.log(`监听到对象的${key}属性被设置值`, target)
target[key] = newValue//因为target是代理的元对象obj,所以就会直接给元对象设置值。
}
})
console.log(objProxy.name)
console.log(objProxy.age)
objProxy.name = "kobe"
objProxy.age = 30
console.log(obj.name)// "kobe"
console.log(obj.age)//30
2. Proxy的其他捕获器
const obj = {
name: "why", // 数据属性描述符
age: 18
}
// 变成一个访问属性描述符
// Object.defineProperty(obj, "name", {
// })
const objProxy = new Proxy(obj, {
// 获取值时的捕获器
get: function(target, key) {
console.log(`监听到对象的${key}属性被访问了`, target)
return target[key]
},
// 设置值时的捕获器
set: function(target, key, newValue) {
console.log(`监听到对象的${key}属性被设置值`, target)
target[key] = newValue
},
// 监听in的捕获器
has: function(target, key) {
console.log(`监听到对象的${key}属性in操作`, target)
return key in target
},
// 监听delete的捕获器
deleteProperty: function(target, key) {
console.log(`监听到对象的${key}属性in操作`, target)
delete target[key]
}
})
// in操作符
// console.log("name" in objProxy)
// delete操作
delete objProxy.name
使用defineProperty是有改掉原对象obj的属性操纵符号的,我们不应该随便去改掉他,obj的name本来是一个数据属性操作符,然后就变成了访问属性描述符了。使用Proxy的话是对代理对象进行各种各样的操作。
3.Proxy所有捕获器
78.PNG在这里,handler.apply()和handle.construct()是用于函数对象
我们使用(杠杠proto)去那对象的原型是不太好的,有浏览器的限制。我们可以用Object.getPrototypeOf去获取对象的原型。
Object.setPrototypeOf(obj, prototype)他是将prototype作为已知对象obj的原型
Object.create(prototype)是创建一个以prototype为原型的对象
4.Proxy对函数对象的监听
我们还会看到捕捉器中还有construct和apply,它们是应用于函数对象的:
function foo() {
}
const fooProxy = new Proxy(foo, {
apply: function(target, thisArg, argArray) {
console.log("对foo函数进行了apply调用")
return target.apply(thisArg, argArray)
},
construct: function(target, argArray, newTarget) {
console.log("对foo函数进行了new调用")
return new target(...argArray)
}
})
fooProxy.apply({}, ["abc", "cba"])
new fooProxy("abc", "cba")
/*
对foo函数进行了apply调用
对foo函数进行了new调用
*/
3 reflect
1.Reflect的作用
79.PNGProxy是一个类,使用的时候要new,Reflect就是一个对象,可以直接使用。
我们如何对对象本身做操作比如defineProperty, getPrototypeOf。一开始不知道放在那里,就全部放在Object的静态函数里面。Object.defineProperty =XXX之类的这么定义出来。但是Object是一个构造函数,是用来new 对象的。不符合规范,所以再es6中,新增了reflect这个对象,我们可以使用它来对对象本身做一些操作。把以前的不规范化变的规范化。
比较 Reflect 和 Object 方法
2.Reflect的常见方法
Proxy上面有哪些捕获器,Reflect上面就有对应的方法。
80.PNG3.Reflect和Proxy一起使用
Proxy的目的就是为了不对原来的对象做直接的操作,所以赋值的时候,是对代理对象proxyObj.age = 30 进行赋值,获取值的时候也是通过代理对象来获取属性console.log(proxyObj.age).但是现在事与愿违,现在通过对代理对象进行内部实现的时候,在捕获器里面,还是直接对里面的原来的对象进行操作,比如set里面的target[key] = newvalue等等。所以我们现在可以通过Reflect对元对象进行操作,而非直接对元对象进行操作。
注意;proxy的handler里面只有set和get有receiver参数。
它的作用是什么呢?
如果我们的源对象(obj)有setter、getter的访问器属性,那么可以通过receiver来改变里面的this;
var obj = {
name : 'why',
age : 18
};
const objProxy = new Proxy(obj, {
get: function(target, key, reveiver){
console.log('get---------');
// return target[key]
return Reflect.get(target, key)
},
set: function(target, key, newvalue,reveiver){
console.log('set----------');
//target[key] = newValue;
Reflect.set(target, key, newvalue)
},
has: function(target, key){
console.log('has----------');
return Reflect.has(target, key)
},
deleteProperty: function(target, key){
console.log('delete----------');
return Reflect.deleteProperty(target, key)
},
})
console.log(objProxy.age);
objProxy.age = 30;
console.log(objProxy.age);//30
console.log(obj.age);//30
/*
在这里
Reflect.set(target, key, newvalue)
和
target[key] = newValue
这两种方式在某些情况下是有区别的
(使用Object.freeze()方法可以冻结一个对象,对冻结的对象不可以再添加属性以及删除属性,不可以修改属性的值,但还可以访问到)
1.target[key] = newValue 真的新值有没有被设置进去是不知道的
Object.freeze(target)设置之后,对象被冻结,也不能设置
比如Object.freeze(obj)
然后objProxy.age=100
这个时候设置值是不成功的,设置100以后,再次获取,结果还是以前的值30.这个时候使用Reflect的话,我们是知道有没有设置成功的。
2.Reflect.set(target, key, newvalue)会返回一个布尔类型的值
成功true,失败false,
set: function (target, key, newvalue, reveiver) {
console.log("set----------");
//target[key] = newValue;
const result = Reflect.set(target, key, newValue);
if (result) {
} else {
}
},
通过if else可以选择接下来设置成功怎么操作,设置失败怎么操作之类的。
*/
4. Receiver的作用
我们发现在使用getter、setter的时候有一个receiver的参数,它的作用是什么呢?
如果我们的源对象(obj)有setter、getter的访问器属性,那么可以通过receiver来改变里面的this;
//每一个对象都有一个get和set方法。
const obj = {
_name : 'why',//默认是私有属性
get name() {
return this._name
},
set name(newvalue) {
this._name = newvalue
}
}
console.log(obj.hasOwnProperty("name")); //true
console.log(obj.hasOwnProperty("_name")); //true
console.log(Object.getOwnPropertyDescriptor(obj, "name"));
//{enumerable: true, configurable: true, get: ƒ, set: ƒ}
//obj.name = "coder"//.name调用的是set name()
//console.log(obj.name);//.name调用的是get name()
/* const objProxy = new Proxy(obj, {
get: function(target, key){
console.log("get方法被访问--------", key)
return Reflect.get(target, key)
},
set: function(target, key, newValue){
Reflect.set(target, key, newValue)
}
}) */
//console.log(objProxy.name)
//get方法被访问-------- name
//只被访问了1次
//调用捕获器里面get方法,调用Reflect.get(target, key)
//因为key是name,这个时候会访问obj里面的get方法
//然后又通过this._name来访问obj里面的_name属性(直接obj._name, 如果是objProxy._name的化,就可以通过proxy里面的handler来获取访问过程了。)
/*
问题:访问_name,这个已经绕过了proxy了,直接通过obj
如果我们希望对这个对象的所有属性操作,包括_name,
是经过代理,这就没办法实现了。比如我们想拦截_name属性的访问,设置之类的。
解决:让obj里面的return this._name这个this变成代理对象,而不是obj对象
*/
const objProxy = new Proxy(obj, {
//这里receiver就是代理对象objProxy
get: function(target, key, receiver){
console.log(objProxy === receiver);//true
console.log("get方法被访问--------", key, receiver)
return Reflect.get(target, key, receiver)
//receiver--》改变元对象的get方法里面的this
/*
objProxy.name -> 进入get函数
然后要Reflect.get(target, key, receiver);receiver--》改变元对象的get方法里面的this
就等于是return target._name变成了objProxy._name,这个时候就再次来到捕获器的get方法
再次Reflect.get(target, key, receiver);就获取target._name,因为_name属性不是存取属性,没有this,所以传入的receiver没有作用。
总结:get就会被访问两次
*/
},
set: function(target, key, newValue, receiver){
console.log("set方法被访问--------", key)
Reflect.set(target, key, newValue, receiver)//这里面receiver的作用是改变set函数中的this指向
}//在new Proxy(obj, handler)的handler里面只有get set的时候,回调的参数里面才有receiver。
})
console.log(objProxy.name)
//get方法被访问-------- name Proxy {_name: 'why'}
//get方法被访问-------- _name Proxy {_name: 'why'}
objProxy.name = "coderrrr"
//set方法被访问-------- name
//set方法被访问-------- _name
5.Reflect中construct作用
Reflect.construct(target, argumentsList[, newTarget])
对构造函数进行 new 操作,相当于执行 new target(...args)。
function Student(name, age){
this.name = name;
this.age = age
}
function Teacher(){
}
const stu = new Student("why", 18);
console.log(stu);//Student类型
console.log(stu.__proto__ === Student.prototype);//true Student类型
console.log(Object.getPrototypeOf(stu) === Student.prototype);//true Student类型
/*
我们希望new出来的stu的执行Student函数中的内容,
但是创建出来对象是Teacher对象
*/
//方法1:
const testStu = new Student("why", 18);
testStu.__proto__ = Teacher.prototype;
console.log(testStu);////Student {name: 'why', age: 18}
console.log(testStu.__proto__ === Teacher.prototype)//true
//方法二:
//指向Student这个构造函数,但是创建出来的类是Teacher类的对象
const teacher = Reflect.construct(Student, ["why", 18], Teacher)
console.log(teacher)//Teacher {name: 'why', age: 18}
console.log(teacher.__proto__ === Teacher.prototype)//true