首先附上一篇好文
Vue的双向数据绑定的原理是什么呢,首先讲一下ES5的这个修改属性的方法Object.defineProperty():
这个方法接收三个参数,属性所在的对象obj,属性的名字key,一个描述符对象option;
- 当修改的是数据属性时,option包含:configurable(能否修改、删除delete)、enumerable(能否通过for-in遍历属性)、writable(能否修改属性的值)、value(属性的数据值)
- 当修改的是访问器属性时,option包含:configurable、enumerable、get(读取属性时调用的函数)、set(写入属性时调用的函数)
这里用的当然是访问器属性啦
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
接下来通过代码说说双向数据绑定是如何实现的吧?
import Watcher from '../watcher'
import {observe} from "../observer"
export default class Vue {
constructor (options={}) {
//这里简化了。。其实要merge
this.$options=options
//这里简化了。。其实要区分的
let data = this._data=this.$options.data
Object.keys(data).forEach(key=>this._proxy(key))
observe(data,this)
}
$watch(expOrFn, cb, options){
new Watcher(this, expOrFn, cb)
}
_proxy(key) {
var self = this
Object.defineProperty(self, key, {
configurable: true,
enumerable: true,
get: function proxyGetter () {
return self._data[key]
},
set: function proxySetter (val) {
self._data[key] = val
}
})
}
}
- observe的对象:通过递归,将它所有的属性,包括子属性的属性,都给加上set和get,
这样的话,给这个对象的某个属性赋值,就会触发set。
//observer.js
export default class Observer{
constructor(value) {
this.value = value
this.walk(value)
}
//递归。。让每个字属性可以observe
walk(value){
Object.keys(value).forEach(key=>this.convert(key,value[key]))
}
convert(key, val){
defineReactive(this.value, key, val)
}
}
export function defineReactive (obj, key, val) {
var dep = new Dep()
var childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
// 说明这是watch 引起的
if(Dep.target){
dep.addSub(Dep.target)
}
return val
},
set:newVal=> {
var value = val
if (newVal === value) {
return
}
val = newVal
childOb = observe(newVal)//如果新赋值的值是个复杂类型。再递归它,加上set/get。
dep.notify()
}
})
}
export function observe (value, vm) {
if (!value || typeof value !== 'object') {
return
}
return new Observer(value)
}
export default class Dep {
constructor() {
this.subs = []
}
addSub(sub){
this.subs.push(sub)
}
notify(){
this.subs.forEach(sub=>sub.update())
}
}
//watcher.js
export default class Watcher {
constructor(vm, expOrFn, cb) {
this.cb = cb
this.vm = vm
//此处简化.要区分fuction还是expression,只考虑最简单的expression
this.expOrFn = expOrFn
this.value = this.get()
}
update(){
this.run()
}
run(){
const value = this.get()
if(value !==this.value){
this.value = value
this.cb.call(this.vm)
}
}
get(){
//此处简化。。要区分fuction还是expression
const value = this.vm._data[this.expOrFn]
Dep.target = null
return value
}
}