`,
data: {
text1: 'text1',
text2: 'text2',
text3: 'text3'
}
});
```
- 封装
```html
{{msg}}
{{msg}}
{{number}}
```
## Vue响应式模拟
**整体分析**

- Vue
把 data 中的成员注入到 Vue 实例,并且把 data 中的成员转成 getter/setter
- Observer
能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知 Dep
- Compiler
解析每个元素中的指令/插值表达式,并替换成相应的数据
- Dep
添加观察者(watcher),当数据变化通知所有观察者
- Watcher
数据变化更新视图
### Vue
- 负责接收初始化的参数(选项)
- 负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
- 负责调用 Observer 监听 data 中所有属性的变化
- 负责调用 Compiler 解析指令/插值表达式
### Observe
- 负责把 data 选项中的属性转换成响应式数据
- data 中的某个属性也是对象,把该属性转换成响应式数据
- 情况1:
```js
data: {
msg: 'hello',
person: {
name: 'cc'
}
}
```
- 情况2:
```js
data.msg = { name: 'aa' }
```
- 数据变化发送通知
### Compiler
- 负责编译模板,解析指令/插值表达式
- nodeType: ` 1元素节点 2属性节点 3文本节点 8注释节点` 等
- 负责页面的首次渲染
```js
class Compiler {
constructor(vm) {
this.vm = vm
this.el = vm.$el
this.compile(this.el)
}
compile (el) {
const childNodes = el.childNodes
Array.from(childNodes).forEach(node => {
if (this.isTextNode(node)) this.compileText(node)
if (this.isElementNode(node)) this.compileElement(node)
// 判断node节点,是否有子节点,如果有子节点,要递归调用compile
if (node.childNodes && node.childNodes.length) this.compile(node)
})
}
// 处理文本节点 - 差值表达式 - {{ msg }}
compileText (node) {
// console.log(node, 15)
const reg = /\{\{(.+?)\}\}/ // 源码中: /\{\{((?:.|\r?\n)+?)\}\}/g
const value = node.textContent
if (reg.test(value)) {
const key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
}
}
// 处理元素节点 - 'v-'
compileElement (node) {
// console.log(node, 18)
// 遍历属性节点
Array.from(node.attributes).forEach(attr => {
console.dir(attr.name)
let attrName = attr.name
if (this.isDirective(attrName)) { // v-text v-model
const key = attr.value
attrName = attrName.slice(2)
this.update(attrName, key, node)
}
})
}
update (attrName, key, node) {
const updateFn = this[attrName + 'Update']
updateFn && updateFn.call(this, node, key)
}
textUpdate (node, key) { // v-text="msg"
node.textContent = this.vm[key]
}
modelUpdate (node, key) { // v-model="msg"
node.value = this.vm[key]
}
// 判断元素属性是否是指令
isDirective (attrName) {
return attrName.startsWith('v-')
}
// 判断节点是否是文本节点
isTextNode (node) {
return node.nodeType === 3
}
// 判断节点是否是元素节点
isElementNode (node) {
return node.nodeType === 1
}
}
```
- 当数据变化后重新渲染视图
### Dep

- 在compiler中收集依赖,添加观察者(watcher)
- 通知所有观察者
```js
// defineReactive中: 创建dep对象收集依赖
const dep = new Dep()
// getter中: get的过程中收集依赖
Dep.target && dep.addSub(Dep.target)
// setter中: 当数据变化之后,发送通知
dep.notify()
```
### Watcher

- 当数据变化触发依赖,dep通知所有的Watcher实例更新视图
- 自身实例化的时候往dep对象中添加自己
```js
class Watcher {
constructor(vm, key, cb) {
this.vm = vm
this.key = key
this.cb = cb
// 在Dep的静态属性上记录当前watcher对象,当访问数据时把watcher添加到dep的subs中
Dep.target = this
// 触发一次getter, 让dep为当前key记录Watcher
this.oldValue = vm[key]
// 清空target
Dep.target = null
}
update () { // 负责更新视图
console.log(this.name, ':我去更新DOM元素了')
const newValue = this.vm[this.key]
if (this.oldValue === newValue) return
this.cb(newValue)
}
}
```
### 总结
- Vue:
- 记录传入的选项,设置$data/$el
- 把data的成员注入到Vue实例
- 负责调用Observe实现数据响应式处理(数据劫持)
- 负责调用Compiler编译指令/插值表达式等
- Observe
- 数据劫持
- 负责把data中的成员转换成getter/setter
- 负责把多层属性转换成getter/setter
- 如果给属性赋值为新对象,把新对象的成员设置为getter/setter
- 添加Dep和Watcher的依赖关系
- 数据变化发送通知
- Compiler
- 负责编译模板,解析指令/插值表达式
- 负责页面的首次渲染过程
- 当数据变化后重新渲染
- Dep
- 收集依赖,添加订阅者(watcher)
- 通知所有订阅者
- Watcher
- 自身实例化的时候往dep对象中添加自己
- 当数据变化dep通知所有的Watcher实例更新视图