// 完整代码案例详见https://gitee.com/CrystalAngelLee/handwritting/tree/master/minivue-handlewritting
分析Vue使用结构
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue使用结构</title>
</head>
<body>
<div id="app">
<h1>插值表达式</h1>
<h3>{{ msg }}</h3>
<h3>{{ count }}</h3>
<h1>v-text</h1>
<div v-text="msg"></div>
<h1>v-model</h1>
<input type="text" v-model="msg">
<input type="text" v-model="count">
</div>
<script src="./js/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: 'Hello Vue',
count: 20,
items: ['a', 'b', 'c']
}
})
</script>
</body>
</html>
Vue响应式原理整体结构分析
Vue
负责把data中的成员注入到Vue实例,并且转换成getter 和setter
会调用Observer 和 compiler
功能:
- 负责接收初始化的参数(选项) - constructor
- 负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
- 负责调用 observer 监听 data 中所有属性的变化
- 负责调用 compiler 解析指令/插值表达式
observer
数据劫持:能够监听data中的数据,如果数据更新,获取值并通知dep
功能:
- 负责把
data
选项中的属性转换成响应式数据 - data 中的某个属性也是对象,把该属性转换成响应式数据
- 数据变化发送通知
Compiler
解析每个元素中的指令/插值表达式, 并替换成相应的数据
功能:
- 负责编译模板,解析指令/插值表达式
- 负责页面的首次渲染
- 当数据变化后重新渲染视图
dep(发布者-Dependency)
添加观察者,当数据发生变化的时候通知所有的观察者
功能:
- 收集依赖,添加观察者(watcher)
- 通知所有观察者
compiler中收集依赖,发送通知
watcher(观察者)
update方法负责更新视图
功能:
- 当数据变化触发依赖, dep 通知所有的 Watcher 实例更新视图
- 自身实例化的时候往 dep 对象中添加自己
相关实现
Vue
class Vue {
constructor(options) {
// 1 通过属性保存选项的数据
this.$options = options;
this.$data = options.data || {};
this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
// 2 把data中的成员转换成getter/setter
this._proxyData(this.$data)
// 3 调用observer对象,监听数据的变化
new Observer(this.$data)
// 4 调用compiler对象,解析指令和差值表达式
new Compiler(this)
}
_proxyData(data) {
// Object.defineProperty
Object.keys(data).forEach(d => {
Object.defineProperty(this, d, {
enumerable: true,
configurable: true,
get() {
return data[d]
},
set(val) {
if (data[d] === val) return
data[d] = val
}
})
})
}
}
observer
class Observer {
constructor(data) {
this.walk(data)
}
// 遍历data对象
walk(data) {
if (!data || typeof data !== 'object') return;
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
// 定义响应式数据
defineReactive(obj, key, value) {
const that = this
// 负责收集依赖,并发送通知
const dep = new Dep();
// 如果value是对象,把value内部的属性转换成响应式数据
this.walk(value)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
Dep.target && dep.addSub(Dep.target);
return value
},
set(val) {
if (val === value) return
value = val;
// 如果val是对象,把val内部的属性转换成响应式数据
that.walk(val)
// 发送通知
dep.notify();
}
})
}
}
compiler
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)
} else if (this.isElementNode(node)) { // 元素节点
this.compileElement(node)
}
// 判断node节点,是否有子节点,如果有子节点,要递归调用compile
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
// 编译文本节点: 处理插值表达式
compileText(node) {
const reg = /\{\{(.+?)\}\}/
const val = node.textContent
if (!reg.test(val)) return;
const key = RegExp.$1.trim()
node.textContent = val.replace(reg, this.vm[key])
new Watcher(this.vm, key, (val) => {
node.textContent = val
})
}
// 编译元素节点: 处理指令
compileElement(node) {
Array.from(node.attributes).forEach(attr => {
let attrName = attr.name;
console.log(attrName)
if(this.isDirective(attrName)) {
attrName = attrName.substr(2)
const key = attr.value
this.update(node, key, attrName)
}
})
}
update (node, key, attrName) {
const updateFn = this[attrName + 'Updater']
updateFn && updateFn.call(this, node, this.vm[key] ,key)
}
// v-text
textUpdater(node, value, key) {
node.textContent = value
new Watcher(this.vm, key, (val) => {
node.textContent = val
})
}
// v-model
modelUpdater (node, value, key) {
node.value = value
new Watcher(this.vm, key, (val) => {
node.value = val
})
// 双向绑定
node.addEventListener('input', () => {
this.vm[key] = node.value
})
}
// 判断是否是指令
isDirective(name) {
return name.startsWith('v-')
}
// 判断节点是否是文本节点
isTextNode(node) {
return node.nodeType === 3
}
// 判断节点是否是元素节点
isElementNode(node) {
return node.nodeType === 1
}
}
dep
class Dep {
constructor() {
this.subs = []
}
// 添加观察者
addSub(sub) {
if (sub && sub.update) {
this.subs.push(sub)
}
}
// 发送通知
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
watcher
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key; // data中的属性名称
this.cb = cb; // 回调函数负责更新视图
// 把watcher对象记录到Dep类的静态属性target
Dep.target = this;
// 触发get方法,在get方法中会调用addSub
this.oldValue = vm[key]
Dep.target = null
}
// 当数据发生变化的时候更新视图
update() {
const value = this.vm[this.key]
if (value === this.oldValue) return;
this.cb(value)
}
}