我们都知道Vue是响应式的,Vue就是基于数据劫持+发布者-订阅者模式
实现的数据响应式,数据变化驱动视图更新,而通过操作视图也能改变数据。
数据劫持是通过Object.defineProperty
把Vue中的data转化成getter
和setter
实现,ES6中也有Proxy
对数据进行拦截处理。
首先来着一个图,大概总结一个流程。
代码开始
1 | class MyVue{ |
- 数据响应式:
对data的所有数据进行劫持,转化为getter
和setter
。 - 模板编译
模板解析,处理指令和事件绑定,比如,
v-text="msg"
,v-on:click="handler"
- 数据双向绑定与数据代理
v-model
实现;数据代理实现数据响应式
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69class Observer{
constructor(data){
this.observe(data)
}
observe(data){
if(data && typeof data === 'object'){
Object.keys(data).forEach(key=>{
this.defineReactive(data,key,data[key])
})
}
}
defineReactive(obj,key,value){
//递归遍历
this.observe(value)
//创建依赖
const dep = new Dep()
Object.defineProperty(obj,key,{
configurable:true,
enumerbale:true,
get(){
//模板编译的时候会取数据时会创建Watcher 然后收集进dep中
Dep.target && dep.addSub(Dep.target)
return value
},
set:(newVal)=>{
//重新监听新值,解决对属性赋值时监听不到的问题 如 this.$data.person = {a:1}
this.observe(newVal)
if(newVal !== value){
value = newVal
}
//数据修改时,通知订阅者,更新视图
dep.notify()
}
})
}
}
class Dep{
constructor() {
this.subs = []
}
addSub(watcher){
this.subs.push(watcher)
}
notify(){
this.subs.forEach(w=>w.update())
}
}
class Watcher{
constructor(vm,expr,cb) {
this.vm = vm
this.expr = expr
this.cb = cb
this.oldVal = this.getOldVal()
}
getOldVal(){
//依赖
Dep.target = this
const oldVal = compileUtil.getVal(this.expr,this.vm)
Dep.target = null
return oldVal
}
update(){
const newVal = compileUtil.getVal(this.expr,this.vm)
if(this.oldVal !== newVal){
//数据变化,回调更新视图
this.cb(newVal)
}
}
} - 数据响应式实现了啥?
- 对数据进行劫持
- 对依赖收集
- 数据变化,回调更新
模板编译
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161class Compile{
constructor(el,vm){
//判断是否为元素节点,否则获取
this.el = this.isElementNode(el) ? el : document.querySelector(el)
this.vm = vm
//获取文档碎片对象,减少页面的回流和重绘
const fragment = this.node2Fragment(this.el)
//编译模板
this.compile(fragment)
//追加到#app上
this.el.appendChild(fragment)
}
isElementNode(node){
return node.nodeType === 1
}
node2Fragment(el){
const f = document.createDocumentFragment()
let firstChild
while(firstChild = el.firstChild){
f.appendChild(firstChild)
}
return f
}
compile(fragment){
const childNodes = fragment.childNodes
;[...childNodes].forEach(child=>{
if(this.isElementNode(child)){
//元素节点
this.compileElement(child)
}else{
//文本节点
this.compileText(child)
}
//递归遍历子节点
if(child.childNodes && child.childNodes.length){
this.compile(child)
}
})
}
compileElement(node){
const attrs = node.attributes
;[ ...attrs].forEach(attr=>{
//取出名和其值,v-html = 'htmlStr' , v-on:click='handler'
const {name,value} = attr
//是否为指令
if(this.isDirective(name)){
const [ ,directive] = name.split('-') //分离出指令名,text,html,on,model
const [dirName,eventName] = directive.split(':') //分离出事件名[html,undefined] [on,click]
// console.log(dirName,eventName)
//value 为 expr 也就是 htmlStr/person.name/person.age
compileUtil[dirName](node,value,this.vm,eventName)
//删除标签上的指令属性
node.removeAttribute('v-'+directive)
}else if(this.isShortOn(name)){
let [,eventName] = name.split('@')
compileUtil['on'](node,value,this.vm,eventName)
node.removeAttribute('@'+eventName)
}else if(this.isShortBind(name)){
let [,eventName] = name.split(':')
compileUtil['bind'](node,value,this.vm,eventName)
node.removeAttribute(':'+eventName)
}
})
}
compileText(node){
const content = node.textContent
//检测是否有 {{msg}}此类指令
if(/\{\{(.+?)\}\}/.test(content)){
compileUtil['text'](node,content,this.vm)
}
}
isDirective(name){
//是否以 v-开头
return name.startsWith('v-')
}
isShortOn(name){
//是否以 @开头
return name.startsWith('@')
}
isShortBind(name){
//是否以 :开头
return name.startsWith(':')
}
}
//编译工具对象
const compileUtil = {
text(node,expr,vm){
let value
if(expr.indexOf('{{') !== -1){
value = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
new Watcher(vm,args[1],(newVal)=>{
this.updater.textUpdate(node,this.getContentVal(expr,vm))
})
return this.getVal(args[1],vm)
})
}else{
value = this.getVal(expr,vm)
}
this.updater.textUpdate(node,value)
},
html(node,expr,vm){
const value = this.getVal(expr,vm)
new Watcher(vm,expr,(newVal)=>{
this.updater.htmlUpdate(node,newVal)
})
this.updater.htmlUpdate(node,value)
},
model(node,expr,vm){
const value = this.getVal(expr,vm)
new Watcher(vm,expr,(newVal)=>{
this.updater.modelUpdate(node,newVal)
})
node.addEventListener('input',(e)=>{
this.setVal(vm,expr,e.target.value)
})
this.updater.modelUpdate(node,value)
},
on(node,expr,vm,eventName){
let fn = vm.$options.methods && vm.$options.methods[expr];
//绑定事件
node.addEventListener(eventName,fn.bind(vm),false)
},
bind(node,expr,vm,propertyName){
const value = this.getVal(expr,vm)
new Watcher(vm,expr,(newVal)=>{
this.updater.bindUpdate(node,propertyName,newVal)
})
this.updater.bindUpdate(node,propertyName,value)
},
updater:{
textUpdate(node,value){
node.textContent = value
},
htmlUpdate(node,value){
node.innerHTML = value
},
modelUpdate(node,value){
node.value = value
},
bindUpdate(node,propertyName,value){
node.setAttribute(propertyName,value)
}
},
getVal(expr,vm){
return expr.split('.').reduce((data,currentVal)=>data[currentVal],vm.$data)
},
getContentVal(expr,vm){
return value = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getVal(args[1],vm)
})
},
setVal(vm,expr,inputVal){
return expr.split('.').reduce((data,currentVal)=>{
data[currentVal] = inputVal
},vm.$data)
}
}
- 模板编译实现了啥?
- 对模板中的指令、语法进行解析赋值,绑定事件
- 对每个使用了
data
中的数据添加一个Watcher
(观察者) - 在模板编译赋值的过程中会取值,就会触发
data
中的getter
,这时也就会把Watcher
push进dep(收集依赖的容器)中。 - 一个旦有数据变化,就会触发
data
中的setter
,从而会触发dep.notify
通知所有watcher
更新,从而驱动视图更新。 - 双向数据绑定,无非就是对表单添加事件
input
,onchange
等完整代码
完整代码