1.什么是响应式?
可以自动响应数据变量的代码机制,我们就称之为是响应式的
2.响应式函数设计
执行的代码中可能不止一行代码,所以我们可以将这些代码放到一个函数中
const obj = {
name: "张三",
age: 18
}
// 2.1 将需要执行的代码封装成函数
function foo() {
const newName = obj.name
console.log(newName)
console.log(obj.name)
}
// 2.2 当对象属性改变时,执行foo()
obj.name = "李四"
foo()
// 李四
// 李四
在开发中我们是有很多的函数的,我们如何区分一个函数需要响应式,还是不需要响应式呢?
3.响应式函数的封装
这个时候我们封装一个新的函数watchFn;凡是传入到watchFn的函数,就是需要响应式的;其他默认定义的函数都是不需要响应式的;
const obj = {
name: "张三",
age: 18
}
// 3.1 声明一个数组
let reactiveFns = []
// 3.2 将需要响应式的函数保存进数组
function watchFn(fn) {
reactiveFns.push(fn)
}
watchFn(function foo() {
console.log(obj.name)
})
watchFn(function bar() {
console.log(obj.age)
})
console.log(reactiveFns) // [foo(),bar()]
obj.name = "王五"
obj.age = 20
// 3.3 遍历响应式的函数,执行需要响应的函数
reactiveFns.forEach((fn) => {
fn()
})
// [foo(),bar()]
// 王五
// 王五
// 20
// 20
4.响应式依赖的收集
目前我们收集的依赖是放到一个数组中来保存的,但是这里会存在数据管理的问题:
- 我们在实际开发中需要监听很多对象的响应式;
- 这些对象需要监听的不只是一个属性,它们很多属性的变化,都会有对应的响应式函数;
- 我们不可能在全局维护一大堆的数组来保存这些响应函数;
所以我们要设计一个类,这个类用于管理某一个对象的某一个属性的所有响应式函数:
好处:
- 每个对象属性对应一个
new Depend对象,每个new Depend对象对应一个reactiveFns数组,这样就不会发生改变一个属性,所有属性的reactiveFns都会执行的问题 - 将
notify()封装进class,这样我们不需要遍历,只需要调用notify()
const obj = {
name: "张三",
age: 18
}
// 4.1 数组重写成类
class Depend {
constructor() {
this.reactiveFns = []
}
addDepend(reactiveFn) {
this.reactiveFns.push(reactiveFn)
}
notify() {
this.reactiveFns.forEach((fn) => {
fn()
})
}
}
// 4.2 重写响应式的函数
const depend = new Depend()
function watchFn(fn) {
depend.addDepend(fn)
}
watchFn(function () {
console.log(obj.name)
})
watchFn(function () {
console.log(obj.age)
})
obj.name = "赵二"
obj.age = 25
depend.notify()
问题:当属性改变时,我们只能手动调用notify()方法来实现响应式
5. 监听对象的变化
那么我们接下来就可以通过new Proxy的方式(vue3采用的方式)来监听对象的变量
const obj = {
name: "张三",
age: 18
}
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)
}
// 5.1 监听对象的属性变量: 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)
// 5.2 每次修改都要调用notify()
depend.notify()
}
})
watchFn(function () {
console.log(objProxy.name)
})
watchFn(function () {
console.log(objProxy.age)
})
objProxy.name = "赵二"
objProxy.age = 25
// 赵二
// 18
// 赵二
// 25
问题:只有一个depend对象,改变某个对象属性,所有响应式函数都会执行
6.对象的依赖管理
我们目前是创建了一个Depend对象,用来管理对于name变化需要监听的响应函数:
- 但是实际开发中我们会有不同的对象,另外会有不同的属性需要管理;
- 所以我们可以用
Map来管理一个对象的依赖,用WeakMap来管理不同对象的依赖;
也就是 objMap(WeakMap) -> obj依赖(Map)->name依赖(Depend对象)
7.对象依赖管理的实现
我们可以写一个getDepend函数专门来管理这种依赖关系
const obj = {
name: "张三",
age: 18
}
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)
}
// 7.1 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepends(target, key) {
// 7.2 根据对象获取对应的Map对象
let objMap = targetMap.get(target)
if (!objMap) {
objMap = new Map()
targetMap.set(target, objMap)
}
// 7.3 根据key获取Depend对象
let depend = objMap.get(key)
if (!depend) {
depend = new Depend()
objMap.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)
// 7.4 重写depend调用方式
const depend = getDepends(target, key)
console.log(depend)
depend.notify()
}
})
watchFn(function () {
console.log(objProxy.name)
})
watchFn(function () {
console.log(objProxy.age)
})
objProxy.name = "赵二"
objProxy.age = 25
//[]
//[]
问题:这时候获取的依赖是空的
8.正确的依赖收集
- 我们之前收集依赖的地方是在
watchFn中:- 但是这种收集依赖的方式我们根本不知道是哪一个
key的哪一个depend需要收集依赖; - 你只能针对一个单独的
depend对象来添加你的依赖对象;
- 但是这种收集依赖的方式我们根本不知道是哪一个
- 那么正确的应该是在哪里收集呢?应该在我们调用了
Proxy的get捕获器时- 因为如果一个函数中使用了某个对象的
key,那么它应该被收集依赖;
- 因为如果一个函数中使用了某个对象的
const obj = {
name: "张三",
age: 18
}
class Depend {
constructor() {
this.reactiveFns = []
}
addDepend(reactiveFn) {
this.reactiveFns.push(reactiveFn)
}
notify() {
this.reactiveFns.forEach((fn) => {
fn()
})
}
}
// 保存当前需要收集的响应式函数,在objProxy的get中使用
let activeReactiveFn = null
function watchFn(fn) {
activeReactiveFn = fn
fn()
// 防止watchFn不传fn时被乱添加
activeReactiveFn = null
}
// 8.1 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepends(target, key) {
// 根据对象获取对应的Map对象
let objMap = targetMap.get(target)
if (!objMap) {
objMap = new Map()
targetMap.set(target, objMap)
}
// 根据key获取Depend对象
let depend = objMap.get(key)
if (!depend) {
depend = new Depend()
objMap.set(key, depend)
}
return depend
}
const objProxy = new Proxy(obj, {
get: function (target, key, receiver) {
// 8.2 根据target.key获取对应的depend
const depend = getDepends(target, key)
// 给depend对象中添加响应函数
depend.addDepend(activeReactiveFn)
return Reflect.get(target, key, receiver)
},
set: function (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// 8.2 根据target.key获取对应的depend
const depend = getDepends(target, key)
depend.notify()
}
})
watchFn(function () {
console.log(objProxy.name)
})
watchFn(function () {
console.log(objProxy.age)
})
objProxy.name = "赵二"
objProxy.age = 25
// 张三
// 18
// 赵二
// 25
9.对Depend重构
但是这里有两个问题:
- 问题一:如果函数中有用到两次
key,比如name,那么这个函数会被收集两次; - 问题二:我们并不希望将添加
reactiveFn放到get中,因为它是属于Depend的行为;
所以我们需要对Depend类进行重构:
- 解决问题一的方法:不使用数组,而是使用
Set; - 解决问题二的方法:添加一个新的方法,用于收集依赖;
// 定义全局变量是为了获取需要响应的fn在objProxy的get中使用
let activeReactiveFn = null
const obj = {
name: "张三",
age: 18
}
class Depend {
constructor() {
// 9.2 将数组重写成Set防止多次添加同一依赖函数
this.reactiveFns = new Set()
}
addDepend(reactiveFn) {
this.reactiveFns.push(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 getDepends(target, key) {
// 根据对象获取对应的Map对象
let objMap = targetMap.get(target)
if (!objMap) {
objMap = new Map()
targetMap.set(target, objMap)
}
// 根据key获取Depend对象
let depend = objMap.get(key)
if (!depend) {
depend = new Depend()
objMap.set(key, depend)
}
return depend
}
const objProxy = new Proxy(obj, {
get: function (target, key, receiver) {
// 根据target.key获取对应的depend
const depend = getDepends(target, key)
depend.depend()
return Reflect.get(target, key, receiver)
},
set: function (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// 重写depend获取方式
const depend = getDepends(target, key)
depend.notify()
}
})
watchFn(function () {
console.log(objProxy.name, "--------------")
console.log(objProxy.name, "++++++++++++++")
})
watchFn(function () {
console.log(objProxy.age)
})
// 第一次执行还是会添加两次依赖函数
objProxy.name = "赵二"
objProxy.age = 25
// 第二次执行只会添加一次
objProxy.name = "王五"
10.对象的响应式操作
我们目前的响应式是针对于objProxy一个对象的,我们可以创建出来一个函数,针对所有的对象都可以变成响应式对象
// 定义全局变量是为了获取需要响应的fn在objProxy的get中使用
let activeReactiveFn = null
class Depend {
constructor() {
this.reactiveFns = new Set()
}
addDepend(reactiveFn) {
this.reactiveFns.push(reactiveFn)
}
depend() {
if (activeReactiveFn) {
this.reactiveFns.add(activeReactiveFn)
}
}
notify() {
this.reactiveFns.forEach((fn) => {
fn()
})
}
}
function watchFn(fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
const targetMap = new WeakMap()
function getDepends(target, key) {
// 根据对象获取对应的Map对象
let objMap = targetMap.get(target)
if (!objMap) {
objMap = new Map()
targetMap.set(target, objMap)
}
// 根据key获取Depend对象
let depend = objMap.get(key)
if (!depend) {
depend = new Depend()
objMap.set(key, depend)
}
return depend
}
// 10.1 将objProxy 封装成一个reactive函数
function reactive(obj) {
return new Proxy(obj, {
get: function (target, key, receiver) {
// 根据target.key获取对应的depend
const depend = getDepends(target, key)
depend.depend()
return Reflect.get(target, key, receiver)
},
set: function (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// 重写depend获取方式
const depend = getDepends(target, key)
depend.notify()
}
})
}
const obj = reactive({
name: "张三",
age: 18
})
watchFn(() => {
console.log(obj.name, "--------------")
console.log(obj.name, "++++++++++++++")
})
watchFn(() => console.log(obj.age))
// 第一次执行还是会添加两次依赖函数
obj.name = "赵二"
obj.age = 25
// 第二次执行只会添加一次
obj.name = "王五"
11.Vue2响应式原理
Vue2中通过我们前面学习过的Object.defineProerty的方式来实现对象属性的监听;
我们可以将reactive函数进行如下的重构:
- 在传入对象时,我们可以遍历所有的
key,并且通过属性存储描述符来监听属性的获取和修改; - 在
setter和getter方法中的逻辑和前面的Proxy是一致的;
// 定义全局变量是为了获取需要响应的fn在objProxy的get中使用
let activeReactiveFn = null
class Depend {
constructor() {
this.reactiveFns = new Set()
}
addDepend(reactiveFn) {
this.reactiveFns.push(reactiveFn)
}
depend() {
if (activeReactiveFn) {
this.reactiveFns.add(activeReactiveFn)
}
}
notify() {
this.reactiveFns.forEach((fn) => {
fn()
})
}
}
function watchFn(fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
const targetMap = new WeakMap()
function getDepends(target, key) {
// 根据对象获取对应的Map对象
let objMap = targetMap.get(target)
if (!objMap) {
objMap = new Map()
targetMap.set(target, objMap)
}
// 根据key获取Depend对象
let depend = objMap.get(key)
if (!depend) {
depend = new Depend()
objMap.set(key, depend)
}
return depend
}
// 11.1 将 objProxy 封装成一个reactive函数,使用Object.defineProperty
function reactive(obj) {
// ES6之前, 使用Object.defineProperty
Object.keys(obj).forEach((key) => {
let value = obj[key]
Object.defineProperty(obj, key, {
get: function () {
const depend = getDepends(obj, key)
depend.depend()
return value
},
set: function (newValue) {
value = newValue
const depend = getDepends(obj, key)
depend.notify()
}
})
})
return obj
}
const obj = reactive({
name: "张三",
age: 18
})
watchFn(() => {
console.log(obj.name, "--------------")
console.log(obj.name, "++++++++++++++")
})
watchFn(() => console.log(obj.age))
// 第一次执行还是会添加两次依赖函数
obj.name = "赵二"
obj.age = 25
// 第二次执行只会添加一次
obj.name = "王五"