携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
学习Vue内容的时候总是觉得自己掌握了,实际使用起来又发现自己有很多不会的地方。内容零零散散,七零八落,学过了很容易又忘记,面试的时候无法给面试官表述清楚。学会可能并不是掌握这个技能的终点,更重要的是能清晰的传达给别人,面试的时候就能完整体现。故此,想做一个重学Vue的系列,逐个核心模块击破,脑子里能串成一张图,方便记忆。
响应式概述
响应式是Vue的数据驱动核心模块,深入学习后就能理解Vue的一些响应式API设计背景,可以去避免去写一些不利于优化的代码。
Vue 2.x 响应式
Vue2的响应式使用的核心API是 Object.defineProperty,该API可以对单个属性进行拦截,代码如下
Object.defineProperty(obj, 'name', {
get() {
console.log('get');
return this.name
},
set(newVal) {
console.log('set');
this.name = newVal
}
})
当obj对象的name属性被读取或者修改时就会触发get、set,我们就可以在get/set发生时做一些特殊操作。比如name被读取时做一个读取点收集,修改时更新读取源(视图)。
为了方便使用,我们稍加封装一下
const reactive = (obj, key, val) => {
Object.defineProperty(obj, key, {
get() {
console.log('get', key);
return val
},
set(newVal) {
console.log('set', key);
val = newVal
}
})
}
这样就封装完成一个可拦截单个属性的reactive的函数,实现对象拦截时,需要遍历对象将逐个属性传入该函数,如
const Observer = (data) => {
if (!data || typeof data !== 'object') return
if (Array.isArray(data)) {
// arrat
} else {
// object
for (const key in data) reactive(data, key, data[key])
}
}
实现一个Dep类,用于收集管理依赖,当触发get时收集,触发set是通知更新
class Dep {
constructor() {
this.deps = []
}
addDep(dep) {
this.deps.push(dep)
}
notify() {
this.deps.forEach(w => {
w.run()
})
}
}
在实现一个Watcher类,用于管理更新函数,如让dom更新等操作
class Watcher {
constructor(vm, updater) {
this.vm = vm
this.getter = updater
this.get()
}
get() {
Dep.target = this
this.getter()
Dep.target = null
}
run() {
this.getter()
}
}
将实现的两个类使用到reactive函数中
const reactive = (obj, key, val) => {
const dep = new Dep()
Observer(val)
Object.defineProperty(obj, key, {
get() {
console.log('get', key);
// update 视图更新函数
new Watcher(this, update)
Dep.target && dep.addDep(Dep.target)
return val
},
set(newVal) {
console.log('set', key);
Observer(newVal)
val = newVal
dep.notify()
}
})
}
总结
Vue2响应式使用了Object.defineProperty,该APi只能对当个属性进行拦截,对象做响应式时需要对整个对象进行遍历递归,因此初始化响应式时需要耗费较多的性能,并且还存在诸多弊端,如无法灵活新增对象属性,对数组的支持度不够等。