摘要:记录跟随开课吧老师学习vue3的全过程
第二天的学习目标是,认识什么是数据响应式,vue2和vue3实现数据响应式的区别,自己实现响应式
什么是响应式
数据变化可侦测,和数据相关的内容可以更新
vue2 vs vue3
来看段vue2实现响应式的源码
function observer(obj){
if(typeof obj !=='object' || obj == null){
return
}
const keys = Object.keys(obj)
for(let i = 0;i < keys.length;i++){
const key = keys[i]
defineReactive(obj,key,obj[key])
}
}
从这段代码可以看出在vue2中对于obj对象vue2需要遍历对象obj所有key,然后通过defineReactive方法,通过defineProperties给每个key设置get和set方法。这会影响初始化速度。
再来看段代码
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
;['push','pop','shift','unshift','splice','reverse','sort'].forEach(method=>{
arrayProto[method] = function() {
originalProto[method] = function(){
originalProto[method].apply(this,arguments)
dep.notify()
}
}
})
从这段代码可以看出vue2中对于数组拦截其push,pop,shift,unshift,splice,reverse,sort操作先执行其对应方法,再执行dep.notify()手动触发更新通知
在vue2中动态添加或删除对象属性需要使用额外API:Vue.set()/delete()
Vue.set(obj,'bar','barfffff')
Vue.delete(obj,'bar')
手写响应式
第一步:响应式实现:reactive
function reactive(obj) {
// 返回代理过的对象
return new Proxy(obj, {
get(target, key) {
const res = Reflect.get(target, key)
// 依赖收集
track(target, key)
return (typeof res === 'object') ? reactive(res) : res
},
set(target, key, val) {
const res = Reflect.set(target, key, val)
// 触发副作用函数
trigger(target, key)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
trigger(target, key)
return res
}
})
}
const obj = reactive({
foo: 'foo',
n: {
a: 1
}
})
effect(() => {
console.log('effect1', obj.foo)
})
effect(() => {
console.log('effect2', obj.foo, obj.n.a)
})
obj.n.a = 10
上面的代码我们已经通过Proxy给对象做了代理拦截,获取,更新,删除都会触发对应方法,就可以添加对应的更新页面方法。但是为了避免更新页面方法全局污染,我们采用发布订阅的模式,订阅更新页面的方法,在对应操作上发布更新页面方法。
第二步:依赖收集和触发:effect()/track()/trigger()
effect添加副作用函数,相当于发布订阅模式,cb回调函数传入effect,cb传入effect后会有两个线路,1.effectStack暂存cb回调函数。2.cb回调函数会执行一次,执行过后触发getter函数的拦截=>依赖收集函数stack,收集依赖的核心目标是为了创建WeakMap这样的数据结构,WeakMap是一个target,key和cb的映射对象。将来如果数据发生变化,setter函数会被触发,setter函数触发我们可以根据传入的target,key,从WeakMap映射关系中取出cb回调数组,执行遍历把所有的响应函数执行。
代码实现
// 保存副作用函数
const effectStack = []
// 添加副作用函数
function effect(fn) {
const e = createReactiveEffect(fn)
// 立即执行
e()
return e;
}
function createReactiveEffect(fn) {
// 封装fn,处理其错误,执行之,存放到stack
const effect = () => {
try {
// 0.入栈
effectStack.push(effect)
// 1.执行fn
return fn()
} finally {
// 2.出栈
effectStack.pop()
}
}
return effect
}
// 保存映射关系的数据结构
const target
// 当副作用函数触发响应式数据之后,执行track,进行依赖收集工作
// 目标是将target,key和effectStack中的副作用函数之间建立映射关系
function stack() {
// 1.拿出响应函数
const effect = effectStack[effectStack.length - 1]
if (effect) {
// 获取target对应map
let depMap = targetMap.get(target)
if (!depMap) {
// 初始化的时候,depMap不存在,需要创建一次
depMap = new Map()
targetMap.set(target, depMap)
}
// 从depMap中获取对应的Set
let deps = depMap.get(key)
if (!deps) {
// 初始化创建Set
deps = new Set()
depMap.set(key, deps)
}
// 将副作用函数放入集合
deps.add(effect)
}
}
// 触发响应式函数
function trigger(target, key) {
// 从targetMap中获取对应的副作用函数
// 1.获取target对应的map
const depMap = targetMap.get(target)
if (!depMap) {
return
}
// 2.根据key获取对应的deps
const deps = depMap.get(key)
if (deps) {
// 遍历执行他们
deps.forEach(dep => dep())
}
}
注:
- 副作用函数:你做一件事,他有一些其他额外的事要做,函数内的响应式数据发生变化,副作用函数要重新执行
- WeakMap:弱引用,就算key是对象也不会导致垃圾回收机制数据不被清理