Vue源码学习之响应式是如何实现的
响应式是Vue的核心特性之一,它使得数据和视图之间能够自动同步更新。在Vue中,我们只需要修改数据,视图就会自动更新,这大大提高了开发效率。那么,响应式是如何实现的呢?
响应式实现原理
Vue通过Object.defineProperty()方法对数据对象进行劫持,当数据被修改时,会触发setter方法通知所有依赖于该数据的视图进行更新。具体实现步骤如下:
-
定义Observer类。遍历数据对象的所有属性,为每个属性添加getter和setter方法,getter方法用于收集依赖(Watcher),setter用于通知依赖进行更新。
-
定义Dep类。Dep类用于管理依赖(Watcher),每个响应式数据都会对应一个Dep实例,当数据发生变化时,Dep实例会通知依赖进行更新。
-
定义Watcher类。Watcher类用于存储依赖(Dep实例)和更新函数,更新函数会在数据发生变化时被调用。
-
在解析模板过程中,对模板中所有需要响应式更新的数据进行依赖收集,即创建Watcher实例,并把Watcher实例加入对应的Dep实例的依赖列表中。
-
当数据发生变化时,触发setter方法,setter会通知所有依赖的Watcher进行更新。
示例说明
下面通过两个示例,分别介绍响应式是如何实现的。
示例1
假设我们有一个数据对象:
const data = {name: 'Alice', age: 18}
我们对name属性进行依赖收集,代码如下:
class Dep {
constructor() {
this.subs = []
}
depend() {
if (Dep.target) {
this.subs.push(Dep.target)
}
}
notify() {
this.subs.forEach(sub => sub.update())
}
}
class Observer {
constructor(value) {
this.value = value
if (!Array.isArray(value)) {
this.walk(value)
}
}
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
function defineReactive(obj, key, val) {
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
dep.depend()
return val
},
set(newVal) {
if (val === newVal) {
return
}
val = newVal
dep.notify()
}
})
}
const watcher = {
update() {
console.log('更新啦')
}
}
function observe(value) {
return new Observer(value)
}
observe(data)
Dep.target = watcher
data.name
Dep.target = null
在上面的代码中,我们对name属性进行了依赖收集,即创建了一个Dep实例,并把Watcher实例加入到其依赖列表中。接下来获取name属性的值,会触发getter方法并执行dep.depend(),此时会将Watcher实例加入到dep的依赖列表中。当name属性发生变化时,会触发setter方法并执行dep.notify(),此时会遍历dep的依赖列表,调用每个Watcher实例的update方法。
示例2
假设我们有一个模板:
<div>
<p>姓名: {{name}}</p>
<p>年龄: {{age}}</p>
<p>性别: {{gender}}</p>
</div>
我们需要解析模板,并对其中的name、age、gender属性进行依赖收集,以实现响应式更新。代码如下:
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm
this.exp = exp
this.cb = cb
Dep.target = this
this.value = this.get()
Dep.target = null
}
get() {
const value = this.vm
? this.vm.$data[this.exp]
: undefined
return value
}
update() {
const value = this.vm
? this.vm.$data[this.exp]
: undefined
const oldValue = this.value
if (value !== oldValue) {
this.value = value
this.cb && this.cb.call(this.vm, value, oldValue)
}
}
}
class Dep {
constructor() {
this.subs = []
}
depend() {
if (Dep.target) {
this.subs.push(Dep.target)
}
}
notify() {
this.subs.forEach(sub => sub.update())
}
}
class Observer {
constructor(value) {
this.value = value
if (!Array.isArray(value)) {
this.walk(value)
}
}
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
function defineReactive(obj, key, val) {
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
dep.depend()
return val
},
set(newVal) {
if (val === newVal) {
return
}
val = newVal
dep.notify()
}
})
}
function observe(value) {
return new Observer(value)
}
function compile(el) {
const node = document.querySelector(el)
const vm = new Vue({
el,
data: {
name: 'Alice',
age: 18,
gender: '女'
}
})
const watcherStore = []
node.childNodes.forEach(child => {
const reg = /\{\{(.*)\}\}/
const text = child.textContent.trim()
if (child.nodeType === 3 && reg.test(text)) {
const exp = reg.exec(text)[1]
watcherStore.push(new Watcher(vm, exp, function(value, oldValue) {
child.textContent = text.replace(reg, value)
}))
}
})
return watcherStore
}
const watchers = compile('#app')
在上面的代码中,我们首先创建了Vue实例,并在模板中对name、age、gender属性进行了依赖收集。接下来我们遍历模板中的所有节点,对包含{{}}的文本节点进行Watcher实例的创建,并把Watcher实例加入到对应的Dep实例的依赖列表中。当name、age、gender属性发生变化时,会触发对应Dep实例的notify方法,并执行Watcher实例的update方法,从而实现对文本节点的更新。
总结:
响应式是Vue的核心特性,其实现原理是通过Object.defineProperty()方法对数据对象进行劫持,当数据被修改时,会触发setter方法通知所有依赖于该数据的视图进行更新。在解析模板过程中,需要对模板中所有需要响应式更新的数据进行依赖收集,即创建Watcher实例,并把Watcher实例加入对应的Dep实例的依赖列表中。当数据发生变化时,会触发setter方法并执行依赖列表中所有Watcher实例的update方法,从而实现对数据的更新以及视图的更新。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Vue源码学习之响应式是如何实现的 - Python技术站