“响应式”是指当数据改变后,Vue会通知到使用当前数据的代码,并且更新视图数据。接下来,简单的实现一个vue的响应式。
// 对象的响应式
const info = {
name: "chuan",
age: 20
}
// 假如 infoChange() 函数是info对象被修改时而触发的函数
function infoChange() {
console.log(info.name)
}
const infoProxy = new Proxy(info, {
get: function (target, key, reciver) {
return Reflect.get(target, key, reciver) // Reflect对象的用法
},
set: function (target, key, value, reciver) {
infoChange()
Reflect.set(target, key, value, reciver)
}
})
infoProxy.name = "hahaha"
这个时候,当我们手动去修改infoProxy(info的代理对象)时,便会触发infoChange这个函数,那当有其他函数或者其他对象时,我们该怎么办呢?
接下来我们就要实现函数里的依赖收集。
假使会有多个用到同一个响应式对象的函数,我们只需要把这些函数放到一个数组里,当响应式对象被修改时,全部执行,那么有了这个思路就好办了。
// 对象的响应式
const info = {
name: "chuan",
age: 20
}
class Depend {
constructor() {
this.reactiveFns = [] // 存放所有含有响应式依赖的函数
}
// 需要一个方法往存放函数的数组加值
addDependFns(reactiveFn) {
this.reactiveFns.push(reactiveFn)
}
// 需要一个方法区执行这里面的依赖函数
notify() {
this.reactiveFns.forEach(reactiveFn => {
reactiveFn()
})
}
}
const depend = new Depend();
//这儿需要封装一个函数,将含有依赖的函数添加进Depend里
function watchFn(fn) {
depend.addDependFns(fn)
}
// 假如 infoChange() 函数是info对象被修改时而触发的函数
function infoChange() {
console.log(info.name, '-------1')
}
function infoChange2() {
console.log(info.name, '-------2');
}
watchFn(infoChange)
watchFn(infoChange2)
const infoProxy = new Proxy(info, {
get: function (target, key, reciver) {
return Reflect.get(target, key, reciver) // Reflect对象的用法
},
set: function (target, key, value, reciver) {
Reflect.set(target, key, value, reciver)
// 当被修改时,那么将通知Depend里面的函数全部取出执行;
depend.notify()
}
})
infoProxy.name = "hahaha"
这样,所有使用了这个依赖的函数在响应式对象发生改变的时候,便为执行。为什么会用一个Depend类呢?其实就是为了方便后面,每一个对象的属性会对应一个 Depend类,收集属于自己的依赖,互不干扰。
第三步:依赖收集的管理
如上图,不同对象的每个属性分别会用一个Depend进行管理,那么当我某个对象的某个属性进行修改时,那么才能执行相关的所有函数。
这时候需要引出 map / weekMap 的使用。通过 map 的对象属性将相应的Depend存入。再通过 weekMap 通过对象作为属性存入对象的map信息。下面举个简单例子:
let objNameDepend = new Depend()
let objAgeDepend = new Depend()
let obj = {
name: "hahaha",
age: 20
}
const objMap = new Map();
objMap.set("name", objNameDepend)
objMap.set("age", objAgeDepend)
console.log(objMap.get("name"), objMap.get("age"));
通过这样,我们可以通过每个对象的属性存在Map中,当需要用的时候再取出来使用,接下来我们再封装一个获取对应Depend的函数:
const targetMap = new WeakMap()
function getDepend(targer, key) {
// 根据target对象获取map的过程
let objectMap = targetMap(target)
if (!objectMap) {
objectMap = new Map()
targetMap.set(target, objectMap)
}
// 再根据key获取depend对象
let depend = objectMap.get(key)
if (!depend) { // 那有可能是没有创建Depeng的
depend = new Depend()
objectMap.set(key, depend)
}
return depend
}
现在能够获取到每个对象每个属性自己的depend了,那么就简单了,我们只需要要往对应的depend里面加入依赖函数就可以了。那什么时候去收集这个依赖函数呢,其实就在proxy里面的get方法里,我们每次获取一次值,就会进入到get,那么在这个时候往里面添加依赖就好了。
这个时候,我们又的对收集依赖的函数 watchFn 做修改,我们知道传入的函数里面含有这个响应式对象,那当传入的这个函数传进去后立即执行,是不是就可以触发这个对象的 get 方法,通过这点,我们对 watchFn 进行改造。
// 改变 watchFn 函数
let globalFn = null; // 声明一个全局变量,是为了让 get 触发的时候,拿到我们传入的这个函数
function watchFn(fn) {
globalFn = fn
fn()
globalFn = null // 执行完后清除这个值,防止后面调用出现问题
}
我们先看下完整代码,并且执行:
// 对象的响应式
const info = {
name: "chuan",
age: 20
}
class Depend {
constructor() {
this.reactiveFns = [] // 存放所有含有响应式依赖的函数
}
// 需要一个方法往存放函数的数组加值
addDependFns(reactiveFn) {
this.reactiveFns.push(reactiveFn)
}
// 需要一个方法区执行这里面的依赖函数
notify() {
this.reactiveFns.forEach(reactiveFn => {
reactiveFn()
})
}
}
const depend = new Depend();
const targetMap = new WeakMap()
function getDepend(targer, key) {
// 根据target对象获取map的过程
let objectMap = targetMap(target)
if (!objectMap) {
objectMap = new Map()
targetMap.set(target, objectMap)
}
// 再根据key获取depend对象
let depend = objectMap.get(key)
if (!depend) { // 那有可能是没有创建Depeng的
depend = new Depend()
objectMap.set(key, depend)
}
}
//这儿需要封装一个函数,将含有依赖的函数添加进Depend里
let globalFn = null;
function watchFn(fn) {
globalFn = fn
fn()
globalFn = null
}
// 假如 infoChange() 函数是info对象被修改时而触发的函数
function infoChange() {
console.log(info.name, '-------1')
}
function infoChange2() {
console.log(info.name, '-------2');
}
watchFn(infoChange)
watchFn(infoChange2)
const infoProxy = new Proxy(info, {
get: function (target, key, reciver) {
const depend = getDepend(target, key)
depend.addDependFns(globalFn)
return Reflect.get(target, key, reciver) // Reflect对象的用法
},
set: function (target, key, value, reciver) {
Reflect.set(target, key, value, reciver)
// 当被修改时,那么将通知Depend里面的函数全部取出执行;
depend.notify()
}
})
infoProxy.name = "hahaha"
执行后发现,依然没有问题,那么接下来我们来看看哪些地方可以做优化。
在这里我们可以看到,每次都是通过手动去添加这个方法,那么其实这个事情可以交给depend去处理,所以接下来改造一下:
let globalFn = null; // 将这个存放收集依赖的函数放在顶端
// 对象的响应式
const info = {
name: "chuan",
age: 20
}
class Depend {
constructor() {
this.reactiveFns = [] // 存放所有含有响应式依赖的函数
}
// 每次收集依赖的时候调用这个函数即可
addDepend() {
if (globalFn) {
this.reactiveFns.push(globalFn)
}
}
// 需要一个方法区执行这里面的依赖函数
notify() {
this.reactiveFns.forEach(reactiveFn => {
reactiveFn()
})
}
}
const infoProxy = new Proxy(info, {
get: function (target, key, reciver) {
const depend = getDepend(target, key)
depend.addDepend() // 直接调用方法即可
return Reflect.get(target, key, reciver) // Reflect对象的用法
},
set: function (target, key, value, reciver) {
Reflect.set(target, key, value, reciver)
// 当被修改时,那么将通知Depend里面的函数全部取出执行;
depend.notify()
}
})
我们可以看到现在只有info这个对象是响应式,那么如果有其他函数怎么办?这时候应该封装成一个函数:
function reactive(obj) {
const objProxy = new Proxy(obj, {
get: function (target, key, reciver) {
const depend = getDepend(target, key)
depend.addDepend()
return Reflect.get(target, key, reciver) // Reflect对象的用法
},
set: function (target, key, value, reciver) {
Reflect.set(target, key, value, reciver)
// 当被修改时,那么将通知Depend里面的函数全部取出执行;
depend.notify()
}
})
return objProxy
}
我们通过传入一个对象,返回回来一个代理对象(proxy),在函数内部完成响应式即可,下面我们看看完整的响应式代码:
let globalFn = null;
// 对象的响应式
const info = {
name: "chuan",
age: 20
}
class Depend {
constructor() {
this.reactiveFns = [] // 存放所有含有响应式依赖的函数
}
// 需要一个方法往存放函数的数组加值
addDepend() {
if (globalFn) {
this.reactiveFns.push(globalFn)
}
}
// 需要一个方法区执行这里面的依赖函数
notify() {
this.reactiveFns.forEach(reactiveFn => {
reactiveFn()
})
}
}
const depend = new Depend();
const targetMap = new WeakMap()
function getDepend(targer, key) {
// 根据target对象获取map的过程
let objectMap = targetMap(target)
if (!objectMap) {
objectMap = new Map()
targetMap.set(target, objectMap)
}
// 再根据key获取depend对象
let depend = objectMap.get(key)
if (!depend) { // 那有可能是没有创建Depeng的
depend = new Depend()
objectMap.set(key, depend)
}
}
//这儿需要封装一个函数,将含有依赖的函数添加进Depend里
function watchFn(fn) {
globalFn = fn
fn()
globalFn = null
}
// 假如 infoChange() 函数是info对象被修改时而触发的函数
function infoChange() {
console.log(info.name, '-------1')
}
function infoChange2() {
console.log(info.name, '-------2');
}
watchFn(infoChange)
watchFn(infoChange2)
function reactive(obj) {
const objProxy = new Proxy(obj, {
get: function (target, key, reciver) {
const depend = getDepend(target, key)
depend.addDepend()
return Reflect.get(target, key, reciver) // Reflect对象的用法
},
set: function (target, key, value, reciver) {
Reflect.set(target, key, value, reciver)
// 当被修改时,那么将通知Depend里面的函数全部取出执行;
depend.notify()
}
})
return objProxy
}
const infoProxy = reactive(info)
infoProxy.name = "hahaha"
const obj = {
message: 'yixiaochuan'
}
watchFn(() => {
console.log("obj被修改了");
})
const objProxy = reactive(obj)
objProxy.message = "hahahah"
看看打印的结果:
确实使我们想要的结果,那么响应式我们就实现了!
那么Vue2中采用的Object.defineproperty是相同思路,只需要改变写法就可以了
function reactive(target) {
Object.keys(target).forEach(key => {
let value = target[key]
Object.defineProperty(target, key, {
get: function () {
const depend = getDepend(target, key)
depend.addDepend()
return value
},
set: function (newValue) {
value = newValue
// 当被修改时,那么将通知Depend里面的函数全部取出执行;
depend.notify()
}
})
})
return target
}
自己手写一遍响应式原理,加深自己的印象,也可以更加了解响应式到底是什么。