# Vue 双向绑定原理以及实现
# 原理
- Vue 数据双向绑定是通过数据劫持 和 发布者-订阅者模式的方式来实现的。
- 数据劫持==>
Object.defineProperty - 发布者-订阅者模式==> 监听器
Observer、订阅者Watcher、消息订阅器Dep、指令解析器Compile - 监听器
Observer用来监听所有属性的变化,告诉订阅者Watcher是否需要进行更新。 - 订阅者
Watcher存在多个,消息订阅器Dep则是用于专门收集这些订阅者的。 - 指令解析器
Compile用于对节点的扫描和解析
# 基本实现过程
- 实现一个监听器
Observer, 用于劫持和监听所有属性,若有变动,通知订阅者。 - 实现一个订阅者
Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。 - 实现一个解析器
Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
# Observer 监听器
// observe.js
import Dep from './dep.js'
const observer = function(data) {
if (!data || typeof data !== 'object') return
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key])
})
}
const defineReactive = function(data, key, value) {
// 递归所有的子属性
observer(value)
var dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
if (Dep.target) {
dep.addSub(Dep.target)
}
return value
},
set: function(newValue) {
if (newValue === value) return
value = newValue
console.log('属性' + key + '已经被监听到' + ':' + newValue)
dep.notify()
}
})
}
export default observer
# Watcher 订阅者
// watcher.js
import Dep from './dep.js'
class Watcher {
constructor(vm, exp, cb) {
this.cb = cb
this.vm = vm
this.exp = exp
this.value = this.get()
}
get() {
Dep.target = this
// 强制执行监听器里的get函数
var value = this.vm.data[this.exp]
Dep.target = null
return value
}
update() {
this.run()
}
run() {
var value = this.vm.data[this.exp]
var oldValue = this.value
if (oldValue === value) return
this.value = value
this.cb.call(this.vm, value, oldValue)
}
}
export default Watcher
# Dep 订阅器
// dep.js
// 订阅者
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
/**
* 通知是有的订阅者,去执行更新函数
*/
notify() {
this.subs.forEach(function(sub) {
sub.update()
})
}
}
export default Dep
# 构造器
// myVue.js
import observer from './observer.js'
import Watcher from './watcher.js'
class MyVue {
constructor(data, el, exp) {
var self = this
this.data = data
Object.keys(data).forEach(function(key) {
self.proxyKey(key)
})
observer(data)
el.innerHTML = this.data[exp]
new Watcher(this, exp, function(value, oldValue) {
el.innerHTML = value
})
return this
}
// 代理属性
proxyKey(key) {
const self = this
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return self.data[key]
},
set(newValue) {
self.data[key] = newValue
}
})
}
}
export default MyVue
# 使用例子
<div id="testId"></div>
import MyVue from '.myVue.js'
let data = {
name1: '1'
}
let el = document.getElementById('testId')
let myVm = new MyVue(data, el, 'name1')
setTimeout(_ => {
myVm.name1 = '2'
}, 3000)