响应式原理
首先我们有一个对象
const obj = {
name: 'zlk',
age: 18
}
这个对象可能在别处被用到
比如是这样的
function foo() {
const newValue = obj.name
console.log('hello world');
console.log(obj.name);
}
我们来改变obj对象中的name的值
obj.name = 'zlk'
这时候foo()应该被重新执行
那么我们如何让所以类似foo()的响应式重新执行一次呢
响应式函数封装
watchFn(function foo() {
const newValue = obj.name
console.log('hello world');
console.log(obj.name);
})
我们再声明个数组
const reactiveFn = []
将响应式的函数push到数组中
function watchFn(fn) {
reactiveFn.push(fn)
}
循环执行这个数组中的函数
reactiveFn.forEach(fn => {
fn()
})
完整代码
const reactiveFn = []
function watchFn(fn) {
reactiveFn.push(fn)
}
const obj = {
name: 'zlk',
age: 18
}
watchFn(function foo() {
const newValue = obj.name
console.log('hello world');
console.log(obj.name);
})
watchFn(function demo() {
console.log('响应式--2--');
})
function bar() {
console.log('普通函数,无响应式');
}
reactiveFn.forEach(fn => {
fn()
})
obj.name = 'zlk'
二,依赖收集类的封装
上面我们将需要响应的函数放进了数组里面,上面name发生改变需要重新执行函数,可是如果是age呢,如果还有别的响应式对象呢,频繁建数组?那很不方便管理
优点1:每一个属性对应一个类,每个类对应一个数组
优点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: 'zlk',
age: 18
}
watchFn(function foo() {
console.log('hello world');
console.log(obj.name);
})
watchFn(function demo() {
console.log('响应式--2--');
})
function bar() {
console.log('普通函数,无响应式', obj.name);
}
obj.name = 'zlk'
depend.notify()
objProxy.name = 'zlk'
depend.notify()
自动监听对象变化
可是我们不能改一个对象中的值 depend.notify()一下
我们要让值被修改后,自动depend.notify(),执行响应式函数
所以我们用到了Proxy代理,当值被修改自动执行响应式函数
完整代码
<script>
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: 'zlk',
age: 18
}
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
depend.notify()
}
})
watchFn(function foo() {
console.log('hello world');
console.log(objProxy.name);
})
watchFn(function demo() {
console.log('响应式--2--');
})
watchFn(function demo2() {
console.log('响应式--2--', objProxy.age);
})
function bar() {
console.log('普通函数,无响应式', objProxy.name);
}
objProxy.name = 'zlk'
objProxy.name = 'aaa'
objProxy.name = 'bbb'
// depend.notify()
</script>
依赖收集如何管理
可是我们又发现无论你修改name 还是age,所有响应式函数都会被执行
这不是我们想要的,我们的目的是修改name 执行用到name的函数自动执行,修改age执行用到age的响应函数,为什么呢,因为他们只有一个depend对象,没有进行区分。我们使用map wekmap来区分
我们封装一个获取depend函数,这里的数据结构是这样的
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) {
let map = targetMap.get(target)
if (!map) {
map = new Map()
targetMap.set(target, map)
}
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
const obj = {
name: 'zlk',
age: 18
}
//自动监听收集依赖
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
console.log(target, key);
const depend = getDepend(target, key)
console.log(depend.reactiveFns);
depend.notify()
}
})
watchFn(function foo() {
console.log('hello world');
console.log(objProxy.name);
})
watchFn(function demo() {
console.log('响应式--2--');
})
watchFn(function demo2() {
console.log('响应式--3--', objProxy.age);
})
function bar() {
console.log('普通函数,无响应式', objProxy.name);
}
objProxy.name = 'zlk'
objProxy.name = 'aaa'
objProxy.name = 'bbb'
map 和weakMap 响应式 知识补充,方便理解上面代码,如果上面不理解,先把下面这段搞懂再去理解上面代码
const obj1 = {
name: "why",
age: 18
}
const obj2 = {
name: "why",
age: 18
}
// 1.创建WeakMap
const weakMap = new WeakMap();
// 2. 收集依赖结构
// 2.1 使用map来收集
const obj1Map = new Map();
obj1Map.set("name", [obj1GetName, obj1SetName])
obj1Map.set("age", [obj1GetAge, obj1SetAge])
weakMap.set(obj1, obj1Map)
// 3.如果obj1.name发生改变
// Proxy/Object.defineProperty
obj1.name = "test";
const targetMap = weakMap.get(obj1);
const fns = targetMap.get("name")
fns.forEach(item => item())
function obj1GetName() {
console.log("obj1GetName")
}
function obj1SetName() {
console.log("obj1SetName")
}
function obj1GetAge() {
console.log("obj1GetAge")
}
function obj1SetAge() {
console.log("obj1SetAge")
}
最终完整收集依赖
你发现上面depend.reactiveFns打印出来为[ ]了吗?
我们现在想如何把响应函数自动放到数组里,然后去执行
proxy的get里面写
const depend = getDepend(target, key)
depend.addDepend(dependFN)
一步步debugger,你会了解整个流程,我这边简单说一下帮助理解
你先执行watchFn函数,将响应式函数传入,途中将函数赋值给全局变量,传入后执行响应函数代码块,因为里面用到代理监听对象,所以它会去往proxy中的get中,将去创建自己的depend对象,创建好后,将全局变量中的响应函数代码块放入创建类里面数组中,之后如果你设置修改了值,将去往proxy中set方法重新执行数组中响应函数。
class Depend {
constructor() {
this.reactiveFns = []
}
addDepend(reactiveFn) {
debugger
this.reactiveFns.push(reactiveFn)
}
notify() {
debugger
this.reactiveFns.forEach(fn => {
fn()
})
}
}
let dependFN = null //全局变量
function watchFn(fn) {
debugger
dependFN = fn //将响应函数放到全局变量里,让proxy中的get中可以获取到
fn() //执行响应函数
}
//获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
debugger
let map = targetMap.get(target)
if (!map) {
map = new Map()
targetMap.set(target, map)
}
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
const obj = {
name: 'zlk',
age: 18
}
//自动监听收集依赖
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
debugger
const depend = getDepend(target, key)
console.log(depend);
depend.addDepend(dependFN)
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
debugger
Reflect.set(target, key, newValue, receiver)
const depend = getDepend(target, key)
depend.notify()
}
})
watchFn(function foo() {
debugger
console.log('响应式1name', objProxy.name, objProxy.age);
})
watchFn(function demo2() {
console.log('响应式2age', objProxy.age);
})
// function bar() {
// console.log('普通函数,无响应式', objProxy.name);
// }
objProxy.name = 'zhf'
console.log('--------------------------');
objProxy.age = 24
vue3响应式原理
1,因为上面我们只有一个对象obj ,可是如果有多个呢,你将要写多个代理?我们还是封装个函数吧,把代理封装到reactive函数中,这时候你是不是发现和vue3中的reactive一样了。
2,创建数组reactiveFns时我们改成了set,set和数组一样,只不过多了去重的功能。是为了如果有这样的代码执行,代码会被执行两次
watchFn(function foo() {
debugger
console.log(‘响应式1name–1’, objProxy.name);
console.log(‘响应式1name–2’, objProxy.name);
})
class Depend {
constructor() {
this.reactiveFns = new Set()
}
addDepend(reactiveFn) {
debugger
this.reactiveFns.add(reactiveFn)
}
notify() {
debugger
this.reactiveFns.forEach(fn => {
fn()
})
}
}
let dependFN = null
function watchFn(fn) {
debugger
dependFN = fn
fn()
}
//获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
debugger
let map = targetMap.get(target)
if (!map) {
map = new Map()
targetMap.set(target, map)
}
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
const objProxy = reactive({
name: 'zlk',
age: 18
})
//自动监听收集依赖
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
debugger
const depend = getDepend(target, key)
console.log(depend);
depend.addDepend(dependFN)
return Reflect.get(target, key, receiver)
},
set(target, key, newValue, receiver) {
debugger
Reflect.set(target, key, newValue, receiver)
const depend = getDepend(target, key)
depend.notify()
}
})
}
watchFn(function foo() {
debugger
console.log('响应式1name', objProxy.name);
})
watchFn(function demo2() {
console.log('响应式2age', objProxy.age);
})
console.log('--------------------------改变后');
// function bar() {
// console.log('普通函数,无响应式', objProxy.name);
// }
objProxy.name = 'zhf'
vue2响应式原理
proxy代理替换为object.defineProperty
次文章需要理解的api
proxy-Reflect
map和weakmap
class类封装构造函数