目录
1、一个简单的响应式函数的封装
// 只针对name的变化做了封装
let reactiveFns = []
// 封装一个响应式的函数
function watchFn(fn) {
reactiveFns.push(fn)
}
// 对象的响应式
const obj = {
name: "张三",
age: 12
}
watchFn(function() {
const newName = obj.name
console.log('hello world')
console.log(obj.name)
})
watchFn(function() {
console.log(obj.name, "-----")
})
function bar() {
console.log("普通的其他函数,这个函数不需要有任何响应")
}
// 对象的某个属性发生了变化,使用了该对象的方法自动重新执行
obj.name = "李四"
reactiveFns.forEach(fn => {
fn()
})
// console.log打印值
// "hello world"
// "李四"
// "李四 -----"
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: "张三", // dep对象
age: 12, // dep对象
}
watchFn(function() {
const newName = obj.name
console.log('hello world')
console.log(obj.name)
})
watchFn(function() {
console.log(obj.name, "-----")
})
obj.name = "李四"
// 手动监听变化
depend.notify()
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)
}
// 对象的响应式
const obj = {
name: "张三", // dep对象
age: 12, // dep对象
}
// 监听对象的属性变化: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()
}
})
// 注意:原对象通过Proxy代理后,要想监听到对象变化,就不能使用原对象了,而要使用代理对象objProxy
watchFn(function() {
const newName = objProxy.name
console.log('hello world')
console.log(objProxy.name)
})
watchFn(function() {
console.log(objProxy.name, "-----")
})
objProxy.name = "李四"
objProxy.name = "王二"
// 就算是age发生变化,依赖name的方法也会重新执行
objProxy.age= 17
4、依赖收集的管理
原因:真实开发中,是有多个对象的,而且每个对象也会对应不同的属性。
注释:每个对象单独对应一个Map(是为了防止有不同对象存在相同属性的情况),再把不同对象的Map通过一个统一的WeakMap管理起来。
伪代码:
const obj = {
name: "张三", // dep对象
age: 12, // dep对象
}
const info= {
name: "张三", // dep对象
address: "四川省成都市", // dep对象
}
const objMap = new Map()
objMap.set('name', 'nameDepend')
objMap.set('age', 'ageDepend')
const infoMap = new Map()
infoMap.set('address', 'addressDepend')
objMap.set('name', 'nameDepend')
const targetMap = new WeakMap()
targetMap.set(obj, objMap)
targetMap.set(info, infoMap)
// 获取obj.name的depend
const depend = targetMap.get(obj).get('name')
depend.notify()
封装一个获取depend的函数
// 封装一个获取depend的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 根据target对象获取map的过程
let map = targetMap.get(target)
if (!map) {
targetMap.set(target, map)
}
// 根据key获取depend对象
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
...
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)
// 使用getDepend方法
const depend = getDepend(target, key)
depend.notify()
}
})
5、正确的收集依赖
重点关注watchFn方法的变化和activeReactiveFn全局变量的作用,以及Proxy中的get方法
class Depend {
constructor() {
this.reactiveFns = []
}
addDepend(reactiveFn) {
this.reactiveFns.push(reactiveFn)
}
notify() {
this.reactiveFns.forEach(fn => {
fn()
})
}
}
// 封装一个响应式的函数
// activeReactiveFn:一个全局中间变量,使得能够在Proxy的get方法中拿到需要响应的函数
let activeReactiveFn = null
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
}
// 对象的响应式
const obj = {
name: "张三", // dep对象
age: 12, // dep对象
}
// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = new Proxy(obj, {
get: function(target,key,receiver) {
// 收集对应的响应式函数
// 1、根据target,key获取对应的depend
const depend = getDepend(target, key)
// 2、给depend对象中添加响应函数
depend.addDepend(activeReactiveFn)
return Reflect.get(target,key,receiver)
},
set: function(target,key,newValue,receiver) {
Reflect.set(target,key,newValue,receiver)
// 自动监听
const depend = getDepend(target, key)
depend.notify()
}
})
// 注意:原对象通过Proxy代理后,要想监听到对象变化,就不能使用原对象了,而要使用代理对象objProxy
watchFn(function() {
console.log("--第一个name函数开始--")
const newName = objProxy.name
console.log('hello world')
console.log(objProxy.name)
console.log("--第一个name函数结束--")
})
watchFn(function() {
console.log("--第一个age函数开始--")
console.log(objProxy.age)
console.log("--第一个age函数结束--")
})
watchFn(function() {
console.log(objProxy.name, "新函数")
console.log(objProxy.age, "新函数")
})
console.log("----------------改变obj的name值----------------")
// objProxy.name = '王五'
objProxy.age = 20
6、对Depend类进行重构优化
Depend优化:
- depend方法
- 使用Set来报错依赖函数,而不是数组[]
// activeReactiveFn:一个中间变量,使得能够在Proxy的get方法中拿到需要响应的函数
let activeReactiveFn = null
class Depend {
constructor() {
// 优化2:通过Set结构收集需要响应的方法,防止同一个响应式函数被重复收集
this.reactiveFns = new Set()
}
addDepend(reactiveFn) {
this.reactiveFns.add(reactiveFn)
}
// 优化1:增加depend方法,使得在Proxy.get方法里面添加响应式函数时,不需要关心activeReactiveFn参数
depend() {
if (activeReactiveFn) {
this.addDepend(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
}
// 对象的响应式
const obj = {
name: "张三", // dep对象
age: 12, // dep对象
}
// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = new Proxy(obj, {
get: function(target,key,receiver) {
// 根据target,key获取对应的depend
const depend = getDepend(target, key)
// 给depend对象中添加响应函数
// depend.addDepend(activeReactiveFn)
// 直接调用depend方法,不需要传递activeReactiveFn参数
depend.depend()
return Reflect.get(target,key,receiver)
},
set: function(target,key,newValue,receiver) {
Reflect.set(target,key,newValue,receiver)
// 自动监听
const depend = getDepend(target, key)
depend.notify()
}
})
// 注意:原对象通过Proxy代理后,要想监听到对象变化,就不能使用原对象了,而要使用代理对象objProxy
watchFn(function() {
console.log(objProxy.name,"------")
console.log(objProxy.name,"++++++")
})
objProxy.name = "李四"
7、对象的响应式操作
7.1、vue3实现方式——Proxy
重点关注reactive方法的封装和使用
...
// 把传入的对象变成响应式对象
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方法,不需要传递activeReactiveFn参数
depend.depend()
return Reflect.get(target,key,receiver)
},
set: function(target,key,newValue,receiver) {
Reflect.set(target,key,newValue,receiver)
// 自动监听
const depend = getDepend(target, key)
depend.notify()
}
})
}
// 对象的响应式
const obj = {
name: "张三", // dep对象
age: 12, // dep对象
}
// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive(obj)
// 注意:原对象通过Proxy代理后,要想监听到对象变化,就不能使用原对象了,而要使用代理对象objProxy
watchFn(function() {
console.log(objProxy.name,"------")
console.log(objProxy.name,"++++++")
})
objProxy.name = "李四"
const info = {
address: "四川省成都市"
}
// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
const infoProxy = reactive(info)
// 注意:原对象通过Proxy代理后,要想监听到对象变化,就不能使用原对象了,而要使用代理对象objProxy
watchFn(function() {
console.log(infoProxy.address,"------")
})
infoProxy.address = "广州市"
const foo = reactive({
name: "foo"
})
watchFn(() => {
console.log(foo.name)
})
foo.name = "bar"
7.2、vue2实现方式——Object.defineProperty
function reactive(obj) {
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
}