Vue 的双向数据绑定采用defineProperty(3.0以前) 以及 发布订阅模式来实现的。
defineProperty 劫持 set 与get,在set 时 通过Dep.target 判断是否要监听,在set时通知所有订阅者。订阅者判断新值与旧值是否一致,若不一致就调用callback 。
defineProperty 劫持 set get
let app = document.getElementById('app')
let input = document.createElement('input')
app.appendChild(input)
let span = document.createElement('span')
app.appendChild(span)
let object = {}
Object.defineProperty(object,'a',{
get:function(){
return this._a //返回a
},
set:function(value){ // set方法更新视图
span.innerHTML = value
this._a = value
}
})
input.oninput=function(e){
object.a = e.target.value // 触发set方法
console.log(object.a) // 触发get方法
}
发布订阅模式
let app = document.getElementById('app')
function init(name) { // 初始化创建视图
this._p = document.createElement('p')
this._p.innerHTML = name
app.appendChild(this._p)
this._input = document.createElement('input')
app.appendChild(this._input)
}
function Rmb() { // 发布者
this._registers = [] // 存放订阅者数组
this._input = null
init.call(this, '¥')
this.bindEvent() // 绑定方法
}
Rmb.prototype.regs = function (reg) { // 订阅方法,将订阅者存入
this._registers.push(reg)
}
Rmb.prototype.bindEvent = function () {
let self = this
self._input.oninput = function () {
// 通过 发布者数据改变调用 订阅者change 方法
self._registers.forEach(item => {
item.change(self._input.value)
})
}
}
let rmb = new Rmb()
function FM(name, rate) { //订阅者
this._rate = rate
this._input = null
init.call(this, name)
rmb.regs(this) // 注册订阅
}
FM.prototype.change = function (value) {
this._input.value = value * this._rate //计算
}
let waibi1 = new FM('$', 0.3)
let waibi2 = new FM('日元', 10)
双向数据绑定
首先需要设置一个Observer,用来监听所有属性,属性发生变化,就告诉Watcher,Watcher判定是否需要更新,需要一个消息订阅中心Dep来实现统一管理。
- 实现一个监听器 Observer ,用来劫持并监听所有属性,如果有变动就通知订阅者
2.实现一个订阅者Watcher,可以接收到属性变化并通知相应函数,从而更新试图
实现Observer
function defineRective(data, key, val) { // 监听 data 的key
observer(val) // 递归 监听
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
return val
},
set: function (newVal) {
console.log('val: '+val+' newVal: '+newVal)
val = newVal
}
})
}
function observer(data){
if(!data || typeof data !== 'object'){
return
}
//遍历对象
Object.keys(data).forEach((key)=>{
defineRective(data,key,data[key])
})
}
let obj={
name:'123',
array:[1,2,3,4],
oj:{
'1':'xiaoming',
'2':'xiaohua'
}
}
observer(obj)
obj.name = '456'
obj.array=['1','2','4']
obj.oj['1']='ddd'
实现一个watcher
// 设置watcher
function Watcher(vm,exp,cb){
this.vm = vm, // 实例
this.exp = exp, //属性
this.cb = cb // 回调
this.value = this.get()
}
Watcher.prototype.get = function(){
// 将target指向自己
Dep.target = this
let value = this.vm.data[this.exp]
// 释放 traget
Dep.target = null
return value
}
// 更新数据的状态
Watcher.prototype.update = function(){
this.run()
}
Watcher.prototype.run = function(){
let value = this.vm.data[this.exp]
let oldVal = this.value
if(value !== oldVal){
this.value = value
this.cb.call(this.vm,value,oldVal)
}
}
完整代码
function defineRective(data, key, val) { // 监听 data 的key
observer(val) // 递归 监听
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
// 在这里判断是否添加一个订阅者
if(Dep.target){
dep.addSub(Dep.target)
}
return val
},
set: function (newVal) {
val = newVal
dep.notify() // 有更新 就发布
}
})
}
function observer(data){
if(!data || typeof data !== 'object'){
return
}
//遍历对象
Object.keys(data).forEach((key)=>{
defineRective(data,key,data[key])
})
}
function Dep(){
this.subs = [] // 维护一个订阅者数组
}
Dep.prototype.addSub = function(sub){ //
this.subs.push(sub)
}
// 发布方法
Dep.prototype.notify = function(){
this.subs.forEach((sub)=>{
sub.update()
}) // 收到消息更新sub
}
Dep.target = null
// 设置watcher
function Watcher(vm,exp,cb){
this.vm = vm, // 实例
this.exp = exp, //属性
this.cb = cb // 回调
this.value = this.get()
}
Watcher.prototype.get = function(){
// 将target指向自己
Dep.target = this
let value = this.vm.data[this.exp]
// 释放 traget
Dep.target = null
return value
}
// 更新数据的状态
Watcher.prototype.update = function(){
this.run()
}
Watcher.prototype.run = function(){
let value = this.vm.data[this.exp]
let oldVal = this.value
if(value !== oldVal){
this.value = value
this.cb.call(this.vm,value,oldVal)
}
}
// 将observer 与watcher 关联
function SelfVue(data,el,exp){
this.data = data
observer(data)
el.innerHTML = this.data[exp]
new Watcher(this,exp,function(value){
el.innerHTML = value
})
}
let ele = document.getElementById('app')
let selfVue = new SelfVue({name:'myVue',},ele,'name')
window.setTimeout(function(){
selfVue.data.name='Hello World!'
},2000)