一、原理分析
- Vue:初始化参数,并把data中的成员转换成getter/setter并注入到Vue实例中(可以在vue组件中使用
this.xxx访问或者修改属性)vue中会调用Observer、Compiler。 - Observer:数据劫持,能够对data对象中的所有属性进行监听,如有变动可拿到最新值并通知Dep
- Compiler:解析每个元素中的指令/插值表达式,并替换成相应的数据
- Dep:发布者。添加观察者(watcher),当数据变化通知所有观察者
- watcher:观察者。有一个update方法复杂更新视图
以_(下划线)开始的是私有成员
二、html模板
<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/dep.js"></script>
<script src="./js/watcher.js"></script>
<script src="./js/compiler.js"></script>
<script src="./js/observer.js"></script> -->
<script src="./js/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: 'Hello Vue',
count: 100,
person: { name: 'zs' }
}
})
console.log(vm.msg)
// vm.msg = { test: 'Hello' }
vm.test = 'abc'
</script>
三、Vue实现
功能
- 负责接收初始化的参数(选项)data,data中的set是真正监视数据变化的地方。
- 调用_proxyData方法把
data中的属性转换成 getter/setter并注入到 Vue 实例,好处:在组件中可以直接使用this.xxx访问data中的属性。 - 负责调用 Observer 监听 data 中所有属性的变化
- 负责调用 Compiler 解析指令/插值表达式
代码
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并注入到vue实例中
this._proxyData(this.$data)
// 3. 调用Observer对象,监听数据变化
new Observer(this.$data)
// 4. 调用Compiler对象,解析指令和差值表达式
new Compile(this)
}
_proxyData(data){
// 遍历data中的所有属性 转化成getter setter,注入到Vue实例上
Object.keys(data).forEach(key=>{
Object.defineProperty(this,key,{ // 此处是this, 因为要往this上绑定属性
enumerable: true,
configurable: true,
get(){
return data[key]
},
set(newValue){
if(newValue == data[key]){
return
}
data[key] = newValue;
}
})
})
}
}
四、Observer
功能
- 负责把
$data选项中的属性转换成响应式数据(defineReactive) - data中的某个属性也是对象,把该对象中的属性也转换成响应式的数据(walk)
- 数据变化发送通知
代码
class Observer {
constructor(data){
this.walk(data);
}
// 如果是对象,遍历对象的所有属性
walk(data){
// 1. 判断data是否是对象
if(!data || typeof data != 'object'){
return
}
// 2. 遍历data对象的所有属性
Object.keys(data).forEach(key=>{
this.defineReactive(data,key,data[key])
})
}
// 调用Object.defineProperty把对象转换成get/set
defineReactive(obj,key,val){
// 注意2:如果值是一个对象,把对象的属性也变成响应式
this.walk(val)
const _this = this;
Object.defineProperty(obj,key,{ // 真正监听$data中的数据的变化
enumerable: true,
configurable: true,
get(){ // 注意1:
// return obj[key]
// 收集依赖
return val
},
set(newValue){
if(newValue == val){
return
}
val = newValue;
_this.walk(newValue) // 注意3:对于新赋值的值(设置为对象)设置响应式
// 发送通知
}
})
}
}
区分两处的Object.defineProperty
Vue类中的方法_proxyData的的Object.defineProperty是为了给data中的属性添加getter和setter并绑定到this,当调用this.xxx时触发。
Observe类中的方法defineReactive的Object.defineProperty是给$data的属性添加getter和setter,当调用data.xxx时触发(即当vue类中的_proxyData getter中的调用的时候触发)。
注意1:避免死循环
此处要使用传入的val,不能使用obj[key],每次使用obj[key],相当于每次都获取key,每次都会触发当前Object中的get形成死递归循环。此处val使用了闭包。
注意2:复杂类型需要转换为响应式的
-
此时只有person对象本身是响应式的,person中的属性不是,需要递归变成响应式的。
-
此时的person是响应式,person中的属性也变成了响应式
注意3:属性重新赋值为对象后仍需要为响应式的
- 对于修改旧值msg为一个对象{name: 'lyy'},此时只有msg本身是响应式的,msg中的属性不是响应式的。
- 此时修改后的值为对象,对象中的属性也变成了响应式