1、监听对象的操作
我们先来看一个需求:有一个对象,我们希望监听这个对象的属性被设置或获取的过程
- 通过我们前面所学的知识,能不能做到这一点呢?
- 其实是可以的,我们可以通过之前的属性描述符中的存储属性描述符来做到。
但是这样做有什么缺点呢?
- 首先,Object.defineProperty设计的初衷,不是为了去监听截止一个对象中的所有属性。
- 我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了存取属性描述符
- 其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么Object.defineProperty是无能为力的。
- 所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象。
const obj={
name:"wjy",
age:20
}
// * 只监听了obj的name属性
// Object.defineProperty(obj,"name",{
// get(){
// console.log("name被访问了");
// },
// set(value){
// console.log("name的值被修改了");
// }
// })
// * 如果想监听obj的所有属性被监听
let objKeys=Object.keys(obj);
objKeys.forEach((key)=>{
let value=obj[key];
Object.defineProperty(obj,key,{
get(){
console.log(`obj的${key}被获取了`);
return value;
},
set(v){
value=v;
console.log(`obj的${key}被修改了`);
}
})
})
obj.name="hyz";
console.log(obj.name);
console.log(obj.age);
obj.age=19;
2.Proxy基本使用
在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的。
- 也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象)
- 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作。
2.1 Proxy的set和get捕获器
如果我们想要侦听某些具体的操作,那么就可以在handler中添加对应的捕捉器(Trap)
set和get分别对应的是函数类型:
- set函数有四个参数
- target:目标对象(侦听的对象)
- key:被设置的属性key
- newValue:新属性值
- receiver:调用的代理对象
- get函数有三个参数
- target:目标对象(侦听的对象)
- key:被获取的属性key
- receiver:调用的代理对象
const obj={
name:"wjy",
age:18
}
const objProxy=new Proxy(obj,{
// * 有13种捕获器
get:function(target,key){
// target:侦听的对象
console.log(`obj的${key}被获取了`,target);
return target[key];
},
set:function(target,key,newValue){
target[key]= newValue;
console.log(`obj的${key}被修改了`,target);
}
});//第一个参数是指要监听的哪个对象 第二个参数是捕获器
console.log(objProxy.name);
console.log(objProxy.age);
objProxy.name="hyz";
objProxy.age=20;
console.log(obj.name);
console.log(obj.age);
2.2 Proxy的所有捕获器
-
handler.getPrototypeOf()
-
Object.getPrototypeOf()方法的捕捉器
obj.__proto__//这个存在浏览器兼容性
-
-
handler.setPrototypeOf()
- Object.setPrototype()方法的捕捉器
-
handler.isExtensible()【是否能扩展】
-
Object.isExtensible()方法的捕捉器
//阻止一个对象扩展是,阻止一个对象添加新属性 密封、冻结后 都是不能进行扩展的。 Object.preventExtensions(obj)
-
-
handler.preventExtensions()
- Object.preventExtensions()的捕捉器
-
handler.getOwnPropertyDescriptor()
- Object.getOwnPropertyDescriptor()的捕捉器
-
handler.defineProperty()
- Object.defineProperty()的捕捉器
-
handler.ownKeys()
- Object.getOwnPropertyNames方法和Object.getOwnPropertySymbols方法的捕捉器。
-
handler.has()
- in操作符的捕捉器
-
handler.get()
- 属性读取操作的捕捉器
-
hanlder.set()
- 属性设置操作的捕捉
-
handler.deleteProperty()
- delete操作符的捕捉器
- handler.apply() 【用于函数对象】
- 函数调用的捕捉器
- handler.constructor() 【用于函数对象】
- new操作符的捕捉器
2.3 Proxy的apply和construct捕捉器
function foo(){
}
// * 调用方式一
foo();
// * 调用方式二
foo.apply({},[])
// * 调用方式三
new foo()
const fooProxy=new Proxy(foo,{
apply:function(target,thisArg,argArray){
console.log("执行了apply操作");
return target.apply(thisArg,argArray);
},
construct:function(target,argArray){
console.log("执行了new调用");
return new target(...argArray);
}
})
fooProxy.apply({},["abc","def"])
new fooProxy("123")
3. Reflect
Reflect是ES6新增的一个API,它是一个对象,字面的意思是 反射。
那么这个Reflect有什么用呢?
- 它主要提供了很多 操作JavaScript对象的方法,有点像Object中操作对象的方法。
- 比如Reflect.getPrototypeOf(target)类似于Object.getPrototypeOf()
- 比如Reflect.difineProperty(target,propertyKey,attributes)类似于Object.defineProperty()
如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?
- 这是因为在早期的ECMA规范中没有考虑到这种 对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面。
- 但是Object作为一个构造函数,这些操作实际上放到它身上并不合适。
- 另外还包含一些类似于in、delete操作符,让JS看起来是会有些奇怪的。
- 所以在ES6中新增了Reflect,让我们把这些操作都集中到了Reflect对象上。
那么Object和Reflect对象之间的API关系,可以参考 MDN文档:
3.1 Reflect的常用的方法
Reflect常用的方法是和Proxy的捕获器一一对应的
-
Reflect.getPrototypeOf(target)
- 类似于Object.getPrototypeOf()
-
Reflect.setPrototypeOf(target,prototype)
- 类似于Object.setPrototypeOf(),设置对象原型的函数,返回一个Boolean,如果更新成功,则返回true。
-
Reflect.isExtensible()
- 类似于Object.isExtensible()
-
Reflect.preventExtensions()
- 类似于Object.preventExtensions()
-
Reflect.getOwnPropertyDescriptor(target,propertyKey)
- 类似于Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符,否则返回undefined.
-
Reflect.defineProperty(target,propertyKey,attributes)
- 和Object.defineProperty()类似,如果设置成功就会返回true。
-
Reflect.ownKeys(target)
- 返回一个包含所有自身属性(不包含继承属性)的属性。(类似于Object.keys(),但不会受enumerable影响)。
-
Reflect.has(target,propertyKey)
- 判断对象上是否存在某个属性,和in 运算符的功能完全相同。
-
Reflect.get(target,propertyKey,receiver)
- 获取对象上某个属性的值,类似于target[name]
-
Reflect.set(target,propertyKey,newValue,receiver)
- 将值分配给属性的函数,返回一个Boolean,如果更新成功,则返回true.
-
Reflect.deleteProperty(target,propertyKey)
- 作为函数的delete操作符,相当于执行 delete target[name]
-
Reflect.apply(target,thisArg,argArray)
- 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和Function.prototype.apply()功能类似
-
Reflect.construct(target,argArray)
- 相当于对构造函数执行new操作,相当于执行new target(...args)
[图片上传失败...(image-f59118-1652335687662)]
const obj={
name:"wjy",
age:18
}
// * 创建代理对象的目的,不再对原来的对象进行直接的操作。
const objProxy=new Proxy(obj,{
get:function(target,key,receiver){
return Reflect.get(target,key);
},
set:function(target,key,newValue,receiver){
const result=Reflect.set(target,key,newValue);
if(result){
}else{
}
}
})
objProxy.name="kobe";
console.log(objProxy.name);
- Reflect是在语言层面上修改,避免对原来对象的直接操作。
3.2 Receiver的作用
我们发现在使用getter、setter的时候有一个receiver的参数,它的作用是什么呢?
- 如果我们的源对象(obj)有setter、getter的访问器属性,那么可以通过receiver来改变里面的this
const obj={
_name:"wjy",
get name(){
return this._name;
},
set name(value){
this._name=value
}
}
const objProxy=new Proxy(obj,{
get:function(target,key,receiver){
// * receiver其实是创建出来的代理对象,它就是上面objProxy
console.log("get方法被访问-----------",key,receiver);
// console.log(receiver==objProxy);//true
return Reflect.get(target,key,receiver);
},
set:function(target,key,newValue,receiver){
console.log("set方法被访问---------",key,receiver);
Reflect.set(target,key,newValue,receiver)
}
})
console.log(objProxy.name);//wjy
objProxy.name="hyz";
// console.log(objProxy.name);
3.3 Reflect的constructor()方法
Reflect.constructor(super,arguments,newTarget)
- 调用的是super的构造方法
- 创建出来的对象是newTarget类型
- arguments,是super的构造方法的参数
function Student(name,age){
this.name=name;
this.age=age;
}
function Teacher(){
}
const stu=new Student();
console.log(stu);
console.log(stu.__proto__==Student.prototype);//true
// * 要求: new 出来的Student,调用的依然是Student的构造函数,但是希望最后创建的类型是 Teacher类型,并且Student的this也是指向Teacher
// * 执行Student函数中的内容,但是创建出来对象是Teacher对象
const teacher=Reflect.construct(Student,["wjy",18],Teacher);
console.log(teacher);//Teacher { name: 'wjy', age: 18 }
console.log(teacher.__proto__==Teacher.prototype);//true
4. 响应式
我们先看一下响应式意味着什么?我们先来看一段代码
- m有一个初始化的值,有一段代码使用了这个值
- 那么在m有一个新的值时,这段代码可以自动重新执行
let m=100;
// * 当m发生变化时,这段代码会自动重新执行
// 一段代码
console.log(m);
console.log(m*2);
console.log(m**2);
m=200;
4.1 响应式函数的封装
// * 封装一个响应式的函数
let reactiveFns=[];
function watchFn(Fn){
reactiveFns.push(Fn)
}
const obj={
name:"wjy",
age:18
}
watchFn(function (){
const newName=obj.name;
console.log("你好~何沅洲");
console.log("Hello World");
console.log(obj.name);
})
watchFn(function (){
console.log(obj.name);
})
function bar(){
console.log("普通的其他的函数");
console.log("这个函数不需要有任何响应式");
}
obj.name="hyz";
reactiveFns.forEach(fn=>fn())
4.2 响应式依赖的收集
前面我们收集的依赖是放到一个数组中保存的,但是这里会存在数据管理的问题:
- 我们在实际开发中需要监听很多对象的响应式
- 这些对象需要监听的不只是一个属性,它们有很多属性的变化,都会有对应的响应式函数。
- 我们不可能在全局维护一大堆的数组来保存这些响应式函数
所以我们要设计了一个类,这个类用来管理某一个对象的某一个顺序的所有响应式函数。
// * 每个属性对应一个类
class Depend{
constructor() {
this.reactiveFns=[];
}
addDepend(reactiveFn){
this.reactiveFns.push(reactiveFn)
}
notify(){
this.reactiveFns.forEach(fn=>fn())
}
}
// * 封装一个响应式的函数
const depend=new Depend();
function watchFn(Fn){
depend.addDepend(Fn)
}
const obj={
name:"wjy",
age:18
}
watchFn(function (){
const newName=obj.name;
console.log("你好~何沅洲");
console.log("Hello World");
console.log(obj.name);
})
watchFn(function (){
console.log(obj.name);
})
function bar(){
console.log("普通的其他的函数");
console.log("这个函数不需要有任何响应式");
}
obj.name="hyz";
depend.notify()
4.3 对象的依赖管理的实现
// * 每个属性对应一个类
class Depend{
constructor() {
this.reactiveFns=[];
}
addDepend(reactiveFn){
this.reactiveFns.push(reactiveFn)
}
notify(){
this.reactiveFns.forEach(fn=>fn())
}
}
// * 封装一个响应式的函数
const depend=new Depend();
function watchFn(Fn){
depend.addDepend(Fn)
}
// * 封装一个获取depend的函数
const targetMap=new WeakMap();
function getDepend(target,key){
// * 根据target对象获取map的过程
const map=targetMap.get(target);
if(!map){//第一次在使用的需要去创建一个
map=new Map();
targetMap.set(target,map);
}
// * 根据key获取depend对象
const depend=map.get(key);
if(!depend){ //* 第一次可能没有depend
depend=new Depend();
map.set(key,depend);
}
return depend;
}
const obj={
name:"wjy",
age:18
}
// * 监听对象的属性变化 : Proxy(vue3)和Object.defineProperty(vue2)
const objProxy=new Proxy(obj,{
get:function(target,key,receiver){
return Reflect.get(target,key,receiver);
},
set:function(target,key,newValue,receiver){
Reflect.set(target,key,newValue,receiver);
// depend.notify();
const depend=getDepend(target,key);
depend.notify();
}
})
watchFn(function (){
const newName=obj.name;
console.log("你好~何沅洲");
console.log("Hello World");
console.log(obj.name);
})
watchFn(function (){
console.log(obj.name);
})
function bar(){
console.log("普通的其他的函数");
console.log("这个函数不需要有任何响应式");
}
objProxy.name="hyz";
objProxy.name="tqy";
objProxy.name="lzy"
// depend.notify()
// * 什么时候去收集封装依赖最合适呢
const info={
address:"怀化"
}
// obj对象
// name depend
// age depend
const objMap=new Map();
objMap.set("name","nameDepend");
objMap.set("age","ageDepend");
// info对象
// address depend
// const infoMap=new Map();
// infoMap.set("address","addressDepend")
// const targetMap=new WeakMap();
// targetMap.set(obj,objMap);
// targetMap.set(info,infoMap);
// // * 如果obj.name发生变化了
// targetMap.get(obj).get("name");//* 去找到name的depend
// depend.notify();//* 然后去执行 name的depend
网友评论