前言:
记录一下学习过程中的心得,让自己理解的更加透彻,简简单单开始,浇给~~
原理描述:
众所周知,vue3的响应式原理是基于Proxy来实现的,这解决了vue2中通过Object.defineProperty()来实现的原理中不能劫持监听数组相关的操作,以及劫持嵌套对象时需要递归操作等问题。Proxy直接可以劫持整个对象,并返回一个新对象。并且可以监听到数组的相关操作,Proxy有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has等等,这是 Object.defineProperty 不具备的。废话不多说,直接上代码吧。
reactive
这里只做了一些简单的判断,baseHandler是传给Proxy所定义劫持数据的规则。
function reactive(obj) {
if (Object.prototype.toString.call(obj) !== '[object Object]') {
return obj
}
return new Proxy(obj, baseHandler)
}
baseHandler
Reflect是一个内置的对象,它提供了拦截JavaScript操作的方法。它不是一个函数对象,,因此不可构造。Reflect对象提供了一些静态方法来操作对象,例如Reflect.get、Reflect.set、Reflect.deleteProperty等。这些方法可以用于读取、修改或删除对象的属性,并返回相应的结果。
const baseHandler = {
get(target, key) {
const res = Reflect.get(target, key)
console.log('get', key)
// 收集依赖
track(target, key)
return isObject(res) ? reactive(res) : res
},
set(target, key, value) {
const res = Reflect.set(target, key, value)
console.log('set', key)
trigger(target, key)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log('deleteProperty', key)
trigger(target, key)
return res
}
}
track-收集依赖函数
主要是通过WeakMap来存储所收集依赖的对象,再通过Set来存储该依赖对象所能触发的所有响应式函数。
// 创建一个存储响应函数的数组
const effectStack = []
// 创建一个数据结构可以保存依赖和响应函数之间映射关系
const targetMap = new WeakMap()
function track(target, key) {
// 获取响应函数
const effect = effectStack[effectStack.length - 1]
if (effect) {
// 获取一下target值,不存在就创建
let depMap = targetMap.get(target)
if (!depMap) {
depMap = new Map()
targetMap.set(target, depMap)
}
// 获取depMap对应的依赖集合,不存在则创建
let deps = depMap.get(key)
if (!deps) {
deps = new Set()
depMap.set(key, deps)
}
// 响应函数放入deps
deps.add(effect)
}
}
trigger-执行所有响应式函数
// 把依赖相关的响应函数集合拿出来全部执行一遍
function trigger(target, key) {
// 从依赖关系中获取响应函数集合
const depMap = targetMap.get(target)
if (!depMap) {
return
}
const deps = depMap.get(key)
if (deps) {
deps.forEach(dep => dep())
}
}
effect函数
通过上述几个函数,数据响应式的劫持及触发功能已经基本实现,但是还是需要实现一个收集副作用(响应函数)的方法,其作用是在对某个值进行响应式处理后,可以监听这个值变化后处理一些事情。
function effect(fn) {
// 获取响应函数
const e = createReactiveEffect(fn)
// 立刻执行它,触发依赖收集过程
e()
return e
}
function createReactiveEffect(fn) {
const effect = function() {
try {
// 1.保存fn
effectStack.push(effect)
// 2.执行fn
return fn()
} finally {
// 3.取消fn保存
effectStack.pop()
}
}
return effect
}
试验一下
//响应式处理
const state = reactive({ foo: 'foo', bar: { n: 1 } })
//收集每个响应数据的副作用函数
effect(() => {
console.log('effect1', state.foo)
})
effect(() => {
console.log('effect2', state.foo, state.bar.n)
})
// state.foo
// state.foo = 'fooooo'
// state.bar = 'bar'
// state.bar
// delete state.bar
state.bar.n = 10
完整代码
// vue3 Proxy ref
const isObject = v => typeof v === 'object' && v !== null
const baseHandler = {
get(target, key) {
const res = Reflect.get(target, key)
console.log('get', key)
// 收集依赖
track(target, key)
return isObject(res) ? reactive(res) : res
},
set(target, key, value) {
const res = Reflect.set(target, key, value)
console.log('set', key)
trigger(target, key)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log('deleteProperty', key)
trigger(target, key)
return res
}
}
function reactive(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj
}
return new Proxy(obj, baseHandler)
}
const effectStack = []
function effect(fn) {
// 获取响应函数
const e = createReactiveEffect(fn)
// 立刻执行它,触发依赖收集过程
e()
return e
}
function createReactiveEffect(fn) {
const effect = function() {
try {
// 1.保存fn
effectStack.push(effect)
// 2.执行fn
return fn()
} finally {
// 3.取消fn保存
effectStack.pop()
}
}
return effect
}
// 创建一个数据结构可以保存依赖和响应函数之间映射关系
const targetMap = new WeakMap()
function track(target, key) {
// 获取响应函数
const effect = effectStack[effectStack.length - 1]
if (effect) {
// 获取一下target值,不存在就创建
let depMap = targetMap.get(target)
if (!depMap) {
depMap = new Map()
targetMap.set(target, depMap)
}
// 获取depMap对应的依赖集合,不存在则创建
let deps = depMap.get(key)
if (!deps) {
deps = new Set()
depMap.set(key, deps)
}
// 响应函数放入deps
deps.add(effect)
}
}
// 把依赖相关的响应函数集合拿出来全部执行一遍
function trigger(target, key) {
// 从依赖关系中获取响应函数集合
const depMap = targetMap.get(target)
if (!depMap) {
return
}
const deps = depMap.get(key)
if (deps) {
deps.forEach(dep => dep())
}
}
const state = reactive({ foo: 'foo', bar: { n: 1 } })
effect(() => {
console.log('effect1', state.foo)
})
effect(() => {
console.log('effect2', state.foo, state.bar.n)
})
// state.foo
// state.foo = 'fooooo'
// state.bar = 'bar'
// state.bar
// delete state.bar
state.bar.n = 10