赞
踩
写在前面的话:当我们在使用vue的时候,可能会非常好奇,为什么vue能实现这种响应式的数据更新,为什么可以动态渲染。
这篇文章并不是直接对vue的源码进行阅读,而是通过一些小demo理解vue的作用原理。参考往年尤雨溪的公开课程。
我们首先要实现的就是vue的核心数据监听,如何监听对象中属性的变化并修改它。相信大家也都了解,就是Object.defineProperty
(vue2)
定义一个convert函数,实现对象的getter和setter监听
// 代码中api的用法请参考MDN
function convert(obj) {
Object.keys(obj).forEach(key => {
// 创建internalValue存储对象的值
let internalValue = obj[key]
Object.defineProperty(obj, key, {
get() {
console.log(`getting key "${key}":${internalValue}`);
return internalValue
},
set(newValue) {
console.log(`setting key "${key}"to:${newValue}`);
internalValue = newValue
}
})
})
}
测试效果
let obj = {
name: 'mkbird',
age: 20
}
convert(obj)
obj.name // should log: 'getting key "name":mkbird '
obj.age = 21 // should log:' setting key "age"to:21 '
console.log(obj) // { age:21, name:'mkbird' }
观察上面的代码就可以发现使用Object.defineProperty
,obj有多少个属性就要绑定多少次,而且还有一些限制,于是在vue3中使用了proxy
创建一个Dep
依赖跟踪类,它包含两个方法
depend
用于收集依赖项notify
用于触发依赖项执行下面是Dep
类的效果,调用dep.depend
收集依赖,当调用dep.notify
时,会再次执行依赖项
autorun
函数是接收一个函数,这个函数帮助我们创建一个响应区,当代码放在这个响应区内,就可以通过dep.depend方法注册依赖项
// 即我们期望的功能为
const dep = new Dep()
autorun(() => {
dep.depend()
console.log('updated')
})
// should log: "updated"
dep.notify()
// should log: "updated"
我们可以将上面的过程理解为发布订阅模式
整个代码分为两个部分
autorun
函数Dep
类创建订阅任务队列,添加依赖项和执行依赖项window.Dep = class Dep {
constructor() {
// 订阅任务队列
this.subscribers = new Set()
}
// 用于注册依赖项
depend() {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
// 用于发布消息,触发依赖项重新执行
notify() {
this.subscribers.forEach(subscriber => subscriber())
}
}
// 下面代码的作用
// 将当前执行的wrappedUpdate存入activeUpdate全局变量中,用于之后注册依赖
// 执行update函数本身
let activeUpdate
function autorun(update) {
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
// 上面为什么要在autorun中嵌套wrappedUpdate呢
// 因为本demo只实现了最精简的功能,实际项目中wrappedUpdate还要有依赖筛选等功能
测试效果
const dep = new Dep()
// 创建想要执行的函数,并且使用dep.depend()表明我想要订阅该事件
let myFun = () => {
dep.depend()
console.log('updated')
}
// 在autorun区域内执行并订阅
autorun(myFun) // should log: "updated"
// 使用dep.notify()发布事件
dep.notify() // should log: "updated"
前面已经实现了convert
函数用于对象的getter和setter监听,也通过发布订阅模式实现了依赖的跟踪,接下来将二者结合实现vue的数据更新系统。
我们将convert
改名为observe
在getter中调用dep.depend
订阅事件,在setter中调用dep.notify
发布事件,即可实现vue的数据更新。
// 加一层Object判断
function isObject(obj) {
return typeof obj === 'object'
&& !Array.isArray(obj)
&& obj !== null
&& obj !== undefined
}
// 监听
function observe(obj) {
if (!isObject(obj)) {
throw new TypeError()
}
// 遍历属性
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
let dep = new Dep()
Object.defineProperty(obj, key, {
get() {
// 订阅
dep.depend()
return internalValue
},
set(newValue) {
if (internalValue !== newValue) {
internalValue = newValue
// 发布
dep.notify()
}
}
})
})
}
// 创建dep
window.Dep = class Dep {
constructor() {
this.subscribes = new Set()
}
depend() {
// 订阅
if (activeUpdate) {
this.subscribes.add(activeUpdate)
}
}
notify() {
// 发布
this.subscribes.forEach(subscriber => subscriber())
}
}
// 创建事件辅助函数
let activeUpdate = null
function autorun(update) {
function wrappedUpdate() {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
测试代码
const state = {
a: 0
}
// 监听数据
observe(state)
// 创建事件
autorun(() => {
console.log(state.a); // should log: 0
})
state.a++ // should log: 1
下一节:Vue源码分析(插件编写)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。