Vue 的雙向數(shù)據(jù)綁定采用defineProperty(3.0以前) 以及 發(fā)布訂閱模式來實(shí)現(xiàn)的披坏。
defineProperty 劫持 set 與get揖闸,在set 時(shí) 通過Dep.target 判斷是否要監(jiān)聽鸠蚪,在set時(shí)通知所有訂閱者孙咪。訂閱者判斷新值與舊值是否一致宁改,若不一致就調(diào)用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 // 觸發(fā)set方法
console.log(object.a) // 觸發(fā)get方法
}
發(fā)布訂閱模式
let app = document.getElementById('app')
function init(name) { // 初始化創(chuàng)建視圖
this._p = document.createElement('p')
this._p.innerHTML = name
app.appendChild(this._p)
this._input = document.createElement('input')
app.appendChild(this._input)
}
function Rmb() { // 發(fā)布者
this._registers = [] // 存放訂閱者數(shù)組
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 () {
// 通過 發(fā)布者數(shù)據(jù)改變調(diào)用 訂閱者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 //計(jì)算
}
let waibi1 = new FM('$', 0.3)
let waibi2 = new FM('日元', 10)
雙向數(shù)據(jù)綁定
首先需要設(shè)置一個(gè)Observer唆铐,用來監(jiān)聽所有屬性,屬性發(fā)生變化奔滑,就告訴Watcher艾岂,Watcher判定是否需要更新,需要一個(gè)消息訂閱中心Dep來實(shí)現(xiàn)統(tǒng)一管理朋其。
- 實(shí)現(xiàn)一個(gè)監(jiān)聽器 Observer 王浴,用來劫持并監(jiān)聽所有屬性,如果有變動(dòng)就通知訂閱者
2.實(shí)現(xiàn)一個(gè)訂閱者Watcher梅猿,可以接收到屬性變化并通知相應(yīng)函數(shù)氓辣,從而更新試圖
實(shí)現(xiàn)Observer
function defineRective(data, key, val) { // 監(jiān)聽 data 的key
observer(val) // 遞歸 監(jiān)聽
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'
實(shí)現(xiàn)一個(gè)watcher
// 設(shè)置watcher
function Watcher(vm,exp,cb){
this.vm = vm, // 實(shí)例
this.exp = exp, //屬性
this.cb = cb // 回調(diào)
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
}
// 更新數(shù)據(jù)的狀態(tài)
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) { // 監(jiān)聽 data 的key
observer(val) // 遞歸 監(jiān)聽
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
// 在這里判斷是否添加一個(gè)訂閱者
if(Dep.target){
dep.addSub(Dep.target)
}
return val
},
set: function (newVal) {
val = newVal
dep.notify() // 有更新 就發(fā)布
}
})
}
function observer(data){
if(!data || typeof data !== 'object'){
return
}
//遍歷對象
Object.keys(data).forEach((key)=>{
defineRective(data,key,data[key])
})
}
function Dep(){
this.subs = [] // 維護(hù)一個(gè)訂閱者數(shù)組
}
Dep.prototype.addSub = function(sub){ //
this.subs.push(sub)
}
// 發(fā)布方法
Dep.prototype.notify = function(){
this.subs.forEach((sub)=>{
sub.update()
}) // 收到消息更新sub
}
Dep.target = null
// 設(shè)置watcher
function Watcher(vm,exp,cb){
this.vm = vm, // 實(shí)例
this.exp = exp, //屬性
this.cb = cb // 回調(diào)
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
}
// 更新數(shù)據(jù)的狀態(tài)
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 關(guān)聯(lián)
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)