Proxy
Object.defineProperty()
监听对象的操作->执行操作数据 可以用Object.defineProperty()其中的存储属性描述符进行监听
name: 'xsh',
age: 18,
};
Object.defineProperty(obj, 'name', {
get: function () {
console.log('name被访问了');
},
set: function () {
console.log('name被赋值了');
},
});
// 监听obj所有key进行监听
Object.keys(obj).forEach(key => {
let value = obj[key];
Object.defineProperty(obj, key, {
get: function () {
console.log('name被访问了');
return value;
},
set: function (newValue) {
console.log('name被赋值了');
value = newValue;
},
});
});
缺点
- 设计初衷并不是为了监听对象的所有属性的 而是为了去定义对象中的数据属性描述符的
- 如果新增属性 删除属性 他是监听不到的
proxy (类)
如果我们希望监听一个对象的相关操作 我们可以创建一个代理对象(Proxy对象) 之后对该对象的所有操作 都用代理对象来完成 Proxy有13种捕获器
使用Proxy来监听obj对象
如果捕获器不去设置值({}) 修改代理对象也会修改原对象
const objProxy = new Proxy(obj,{
获取值
target:原对象 obj
key:获取对象的key
get :function (target,key){
return target[key]
}
设置值
set:function(target,key,newValue){
target[key]=newValue
}
监听in的捕获器
has:function (target,key){
return key in target
}
监听删除捕获器
deleteProperty(target,key){
return delect target[key]
}
监听获取对象原型
getPrototypeOf(){}
监听设置对象原型
setPrototypeOf(){}
监听对象能不能扩展
isExtensible(){}
preventExtensions(){}
getOwnPropertyDescriptor(){}
defineProperty(){}
监听 Object.getOwnPropertynames 和 Object.getOwnpropertySymbols()
onKeys(){}
用于函数对象
apply(target,thisArg,argArray){
return target.apply(thisArg,argArray)
}
// 用于new函数对象
constuct(target,argArray){
return new target(...argArray)
}
})
objProxy.name = 'abc';
objProxy.age = 12;
console.log('name' in objProxy);
delete objProxy.name;
Object.getPrototypeOf();
Object.setPrototypeOf();
Object.isExtensible();
Reflect(对象) 字面意思是反射 经常和Proxy一起使用
提供了很多操作js对象的方法 类似于js中的Object. 操作对象的方法 意义 1.因为早期ECMA规范中没有考虑到对 对象本身的操作如何设计会更加规范 所以把这些api都放在了Object 但是Object本身就是一个构造函数 这些操作放在它身上有些不合适 另外包括一些类似于 in delect 操作符让js看上去很奇怪
- Proxy是拦截对象的操作并定义拦截时具体执行什么操作;
- 而Reflect其实是执行了对象的操作
- 按我理解,Proxy是改写了对象原有方法;而Reflect像是要抽离原来Object上的方法,它就是是调用Object原有方法的函数,目的是将数据和逻辑代码分离。这样以后可能Obejct就只是数据,想要对其操作要用Reflect。
比较Reflect 和 Object developer.mozilla.org/zhCN/docs/W… 他也跟proxy一样有13种捕获器
Reflect;
const objProxy = new Proxy(obj, {
// 获取值
// target:原对象 obj
// key:获取的对象的key
// receiver 代理对象 会对原对象中的this 从obj指向 到代理对象objProxy
get: function (target, key, receiver) {
return Reflect.get(target, key, receiver);
},
// 设置值 新赋予的值newValue
set: function (target, key, newValue) {
// Reflect 他会返回一个boolean 会判断是否设置值成功
Reflect.set(target, key, newValue, receiver);
},
// 监听in捕获器
has: function (target, key) {
return key in target;
},
// 监听删除捕获器
deleteProperty(target, key) {
return delete target[key];
},
});
响应式(vue3->vue2)
- 定义
- 响应式函数的封装 reactiveFns 响应式数据有了变化的对应的操作 函数 放在这个数组里面
const reactiveFns=[]
watchFn(fn){}
- 封装一个类 主要是用于调用类中方法 来往操作的数组添加函数 和遍历循环这个数组里面的函数
class Depend {
constructor(){
this.reactiveFns=[]
}
addDepend(reactiveFn){
this.reactiveFns.push(reactiveFn)
}
notify(){
this.reactiveFns.forEach(fn=>{
fn()
})
}
}
4.监听对象的变化(使用proxy的set捕捉器)
new proxy(obj,set:depend.notify)
5.依赖收集的数据结构 使用的WeakMap 和Map 使用的WeakMap这样可以把对象的key和依赖函数存储在一起。 使用map的可以具体吧key所对应的依赖函数存储在一起。因为用了WeakMap 所以当不需要的时候 可以直接摧毁
function getDepend(){}
6.正确收集依赖
- Proxy的get方法中收集对应的函数
- 创建全局activeReactiveFn变量
- 在get中找到depend对象,appDepend(全局的activeReactiveFn变量)
7.对Depend进行优化
- addDepend函数换成depend函数
- 直接获取到自由变量** 这样可以在监听函数的时候赋值 然后再传给收集依赖的depend类中**
- 将之前保存的数组编程Set类型 防止传入重复的依赖函数
8.对对象进行响应式操作
- 封装reactive函数
- new Proxy() vue3 因为proxy是劫持整个对象 所以当你往对象里面新增key的时候 他也会执行get方法
- Object.defineProperty() vue2 他是对对象现有的key进行劫持 这就会造成新增key 他没有重写get方法 在vue中使用$set 其实也是在用Object.defineProperty()方法进行原对象的新增key重新执行一边
const info ={
name:'xsh',
age:19
}
1. 把对象在封装的reactive函数里面 在函数里面可以使用Proxy或者Object.defineProperty来进行响应式
const objProxy = reactive(info)
watchFn(() => {
console.log('当代理对象中的name改变的时候 自动调用/也就是name的依赖');
console.log(objProxy.name);
});
watchFn(() => {
console.log('当代理对象中的age改变的时候 自动调用/也就是age的依赖');
console.log(objProxy.age);
});
将传入的对象进行封装操作 vue3
function reactive(obj){
return new Proxy(obj,{
当获取值的时候 获取到对应的依赖 当你创建了变量 他会先获取值
get(target,key,receiver){
获取到该对象所改变的key 所对应的依赖
const depend=getDepend(target,key)
depend,depend()
return Reflect.get(target,key,receiver)
}
当改变值的时候触发 触发获取到对应依赖
set(target,key,newValue,oldValue){
改变代理对象的值
Reflect.set(target,key,NewValue,receiver)
获取到该对象所改变的key所对应的依赖类
const depend = getDepend(target, key, receiver);
depend.notify();
}
})
}
// 对传入对象进行封装操作 Vue2 用的是Object.defineProperty()
function reactive(obj) {
// {name: "why", age: 18}
// ES6之前, 使用Object.defineProperty
Object.keys(obj).forEach(key => {
let value = obj[key];
Object.defineProperty(obj, key, {
get: function () {
const depend = getDepend(obj, key);
depend.depend();
return value;
},
set: function (newValue) {
value = newValue;
const depend = getDepend(obj, key);
depend.notify();
},
});
});
return obj;
}
封装一个depend函数 为了整合对象中的依赖数据格式 主要用的是weakMap和Map
使用weakMap 他是个弱引用 方便于回收
const targetWeakMap = new WeakMap()
function getDepend(target,key){
首先获取到传入的对象。获取到它内部key所有的依赖
let map = targetWeakMap.get(target)
第一次没有的话 给这个对象创建一个空的依赖进去
if(!map){
map= new Map()
map.set(target,map)
}
通过key获取到该key所有的依赖函数
let depend = map.get(key)
if(!depend){
第一次没有的话 给该对象创建一个空的依赖进去
depend= new Depend()
map.set(key,depend)
}
return depend
}
let activeReactiveFn=null
方便存储收集到的依赖 并且触发 每一个对象都有自己的类
class depend{
constructor(){
这块创建依赖数组 用Set不用Arry 因为Set有自动去重 防止加入重复的依赖函数
this.reactiveFns=new Set()
}
depend(){
这块使用一个全局变量 他的用处就是获取每一次的方法依赖
if(activeReactiveFn){
this.reactiveFns.add(activeReactiveFn)
}
}
循环遍历出发依赖中的函数
notify(){
this.reactiveFns.forEach(fn=>{
fn()
})
}
封装一个响应式函数 这个主要是获取到每一次使用的函数并且出发封装函数中的Set方法
function watchFn(fn){
activeReactiveFn=fn
fn()
activeReactiveFn=null
}