学过Vue2的哥哥姐姐应该知道,Vue2的响应式原理是用了对象属性描述符Object.defineProperty来监听对象进行存取操作,,这样做有个缺点就是无法监听到新增和删除的对象以及数组的length,所以我们知道数据描述符的初衷并不是为了去监听一个完整的对象
一.proxy简单用法
在ES6中新增了一个Proxy类,如果我们希望一个对象的相关操作,我们可以创建一个代理对象,之后对对象的所有操作,都通过代理对象来完成。下面是一个小例子:
const obj = {
name:'lina',
age:18
}
const objProxy = new Proxy(obj,{
get: function(target,key){
console.log(`监听到访问了${key}值`);
return target[key]
},
set:function(target,key,newValue){
console.log(`监听到设置${key}值为${newValue}`);
target[key] = newValue
}
})
console.log(objProxy.name);
objProxy.age = 20
注意:我们操作的应该代理的对象objProxy,而不是obj
Proxy捕获器,主要是用来对对象进行操作,下面是常用的捕获器:
![](https://img-blog.csdnimg.cn/img_convert/7c0ffe00aa5d5091d189e9cfe9a56494.png)
apply() 和construct是用来监听函数对象调用和new操作符调用的,例如:
function foo(arge){
console.log("foo函数被调用",arge, this.end);
return 'foo'
}
const fooProxy = new Proxy(foo,{
apply:function(target,thisArg,otherArgs){
console.log(thisArg,otherArgs);
console.log("函数的apply监听");
return target.apply(thisArg,otherArgs)
},
construct(target,argArray,newTarget){
console.log(target,argArray,newTarget);
return new target()
}
})
fooProxy(123)
Reflact的作用
是ES6新增的一个API,它是一个对象,字面的意思就是反射
主要提供了很多操作js对象的方法,和Object中操作对象的方法类似
是因为在早期的ECMA规范中没有考虑到对对象本身的操作如何设计会更加规范,所以将这些API放到了Object上面,ES6新增了Reflact,让我们这些操作集中到了Reflact对象上,更加规范
常用方法和上面类似
Reflect - JavaScript | MDN (mozilla.org) 也可以去MDN进行查阅
基本使用,如以下代码:
const obj = {
name:'lina',
age:18
}
const objProxy = new Proxy(obj,{
get: function(target,key){
console.log(`监听到访问了${key}值`);
return Reflect.get(target,key)
},
set:function(target,key,newValue){
console.log(`监听到设置${key}值为${newValue}`);
return Reflect.set(target,key,newValue)
},
deleteProperty: function(target,key){
return Reflect.defineProperty(target,key)
},
has:function(target,key){
return Reflect.has(target,key)
}
})
另外还有一个参数 receiver,这个参数的作用就是如果源对象中存在getter和setter和改变this的指向问题
二.Vue3响应式原理
使用reactive函数将数据对象转换成响应式对象
const data = {count:0}
const state = reactive(data)
在reactive函数内部使用Proxy对象对数据对象进行代理,拦截响应式对象的读取和设置操作
function reactive(obj){
return new Proxy(obj,{
get(target,key,receiver){
//... 收集依赖
const depend = getDepend(target,key)
depend.depend()
},
set(target,key,value,receiver){
//...
const depend = getDepend(obj, key)
depend.notify()
}
})
}
在get拦截器中通过track函数收集依赖,封装一个depend方法收集 ,创建有个weackMap,用于存储每个属性的map 在创建对应的map 用于存储对应的属性值
const targetMap = new WeakMap()
function getDepend(target,key){
// 根据target对象获取map对象
let map = targetMap.get(target)
if(!map){
map = new Map()
targetMap.set(target,map)
}
// 根据key获取depend对象
let depend = map.get(key)
if(!depend){
depend = new Depend()
map.set(key,depend)
}
return depend
}
创建depend类用于存放更新函数
class Depend{
constructor(){
//这里用set是防止重复的更新函数执行,要求重复更新的函数依次执行
this.reactiveFns = new Set()
}
depend(){
if(activeReactiveFn){
this.reactiveFns.add(activeReactiveFn)
}
}
notify(){
this.reactiveFns.forEach(fn=>{
fn()
})
}
}
注意reactiveFns用set的目的是为了防止重复的更新的函数一次执行,造成不必要的多次渲染,因此用
set去除重复的类
分装一个响应式函数
function watchFn(fn){
activeReactiveFn =fn
fn()
activeReactiveFn = null
}
声明一个全局自由变量 activeReactiveFn来收集函数的依赖
完整代码
// 保存当前需要收集的响应式函数
let activeReactiveFn = null
/**
* Depend优化:
* 1> depend方法
* 2> 使用Set来保存依赖函数, 而不是数组[]
*/
class Depend {
constructor() {
this.reactiveFns = new Set()
}
// addDepend(reactiveFn) {
// this.reactiveFns.add(reactiveFn)
// }
depend() {
if (activeReactiveFn) {
this.reactiveFns.add(activeReactiveFn)
}
}
notify() {
this.reactiveFns.forEach(fn => {
fn()
})
}
}
// 封装一个响应式的函数
function watchFn(fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
// 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 根据target对象获取map的过程
let map = targetMap.get(target)
if (!map) {
map = new Map()
targetMap.set(target, map)
}
// 根据key获取depend对象
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
function reactive(obj) {
return new Proxy(obj, {
get: function(target, key, receiver) {
// 根据target.key获取对应的depend
const depend = getDepend(target, key)
// 给depend对象中添加响应函数
// depend.addDepend(activeReactiveFn)
depend.depend()
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()
}
})
}
// 监听对象的属性变量: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive({
name: "why", // depend对象
age: 18 // depend对象
})
const infoProxy = reactive({
address: "广州市",
height: 1.88
})
watchFn(() => {
console.log(infoProxy.address)
})
infoProxy.address = "北京市"
const foo = reactive({
name: "foo"
})
watchFn(() => {
console.log(foo.name)
})
foo.name = "bar"