前言
Vue.js 一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。
DOM 变成了数据的映射,我们所有的逻辑都是对数据的修改,而不用碰触 DOM,这样的代码非常利于维护。
概述
Vue.js 是通过数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的监听回调。
第一步 - 实现一个订阅器(Dep)
target
指向当前正在评估的目标观察者,一个Dep
通常有一个可观察的对象(subs
),可以有多个指向它的指令
1 | // observe.js |
第二步 - 实现一个Observe
Observe 劫持目标对象的 getter / setter 收集依赖关系并调度更新,参考文档: Object.defineProperty() - JavaScript | MDN
1 | export class Observe { |
第三步 - 实现一个观察者(Watcher)
观察者解析表达式,收集依赖项,并在表达式值更改时触发回调。
1 | export class Watcher { |
添加数据观察者,先劫持对象再解析,最后再代理数据
1 | // vue.js |
简单测试下,以v-text
为例,后续会补充模版字符串({{}}`)和`v-model`的识别,先理解原理比较重要
完善指令绑定逻辑,添加观察者并注册回调函数
)和嵌套属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// compileUtils.js
import { Watcher } from './observe.js';
const compileUtils = {
text (node, expr, vm) {
let value;
if (expr.indexOf('{{') !== -1) {
...
} else {
new Watcher (vm, expr, (newValue) => {
this.upDater.textUpDater(node, newValue)
})
value = this.getValue(expr, vm)
}
}
}1
2
3
4
5
6
7
8
9
10
11
12// <h1 v-text="msg"></h1>
// <button v-on:click="btnClick">Click</button>
new Vue({
data: {
msg: 'hello world'
}
methods: {
btnClick() {
this.msg = 'hello Watcher'
}
}
})1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27const compileUtils = {
...
model (node, expr, vm) {
const value = this.getValue(expr, vm)
new Watcher (vm, expr, (newValue) => {
this.upDater.modelUpDater(node, newValue)
})
node.addEventListener('input', (e) => {
this.setValue(expr, vm, e.target.value)
})
this.upDater.modelUpDater(node, value)
},
// input双向数据绑定
setValue(expr, vm, inputVal) {
return vm.$data[expr] = inputVal
},
upDater: {
...
modelUpDater (node value) {
node.value = value
}
}
}
// <input v-model="msg" />obj.msg
1 | const compileUtils = { |
第四步 测试验证
实现的功能比较少,但包括基础的双向数据绑定、指令解析器
1 | <body> |
总结
通过整合Observe
,Compile
和Watcher
三者、 从而实现一个简单的MVVM
来阐述了双向绑定的原理和实现。
单纯的去分析工业级别的源码实在过于牵强,如果能熟悉其中的原理先简单实现,出现问题带着疑问再去参考源码中的解决方案,不失为一种折中的学习方式~