# 双向数据绑定
极简版:
<input id="input"/>
const data = {};
const input = document.getElementById('input');
Object.defineProperty(data, 'text', {
set(value) {
input.value = value;
this.value = value;
}
});
input.onchange = function(e) {
data.text = e.target.value;
}
完整版:
// 订阅器
function Dep(){
this.subs = []
}
Dep.prototype = {
addSub(sub) {
this.subs.push(sub)
},
notify() {
this.subs.map(sub => {
sub.update()
})
}
}
Dep.target = null
// 观察者
function observer(data) {
if (!data || typeof(data) !== 'object') {
return
}
Object.keys(data).map(key => {
defineReactive(data, key, data[key])
})
}
function defineReactive(data, key, value) {
const dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
dep.addSub(Dep.target)
}
return value
},
set(newVal) {
if (value !== newVal) {
value = newVal // 这两句的顺序很关键,必须先赋新的值,才能成功notify
dep.notify()
}
}
})
if (value && typeof(value) === 'object') {
observer(value)
}
}
// Proxy 实现观察者
function observer(data) {
if (!data || typeof(data) !== 'object') return
const dep = new Dep()
return new Proxy(data, {
get(target, key, receiver) {
if (Dep.target) {
dep.addSub(Dep.target)
}
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
if (target[key] !== value) {
Reflect.set(target, key, value, receiver)
dep.notify()
}
}
})
}
// 订阅者
function Watcher(vm, prop, cb) {
this.vm = vm
this.prop = prop
this.cb = cb
this.value = this.get()
}
Watcher.prototype = {
get() {
Dep.target = this
const value = this.vm.$data[this.prop]
Dep.target = null
return value
},
update() {
let value = this.value
const newVal = this.vm.$data[this.prop]
if (value !== newVal) {
this.value = newVal
this.cb(newVal)
}
}
}
// Vue
function Vue(options) {
this.$data = options.data
this.init()
}
Vue.prototype.init = function() {
observer(this.$data)
// this.$data = observer(this.$data) // 用Proxy实现观察者的话需替换为这行
new Watcher(this, 'msg', (value) => { // 模拟编译过程中的监听
console.log('-----------------', value)
})
}
// 测试
const vm = new Vue({
data: {
msg: 'test'
}
})
setTimeout(() => {
vm.$data['msg'] = 'test2'
}, 1000)