Vue雙向數(shù)據(jù)綁定

Vue 的雙向數(shù)據(jù)綁定采用defineProperty(3.0以前) 以及 發(fā)布訂閱模式來實(shí)現(xiàn)的。
defineProperty 劫持 set 與get,在set 時 通過Dep.target 判斷是否要監(jiān)聽,在set時通知所有訂閱者。訂閱者判斷新值與舊值是否一致,若不一致就調(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è)置一個Observer,用來監(jiān)聽所有屬性,屬性發(fā)生變化,就告訴Watcher,Watcher判定是否需要更新,需要一個消息訂閱中心Dep來實(shí)現(xiàn)統(tǒng)一管理。

  1. 實(shí)現(xiàn)一個監(jiān)聽器 Observer ,用來劫持并監(jiān)聽所有屬性,如果有變動就通知訂閱者
    2.實(shí)現(xiàn)一個訂閱者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)一個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 () {
                // 在這里判斷是否添加一個訂閱者
                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ù)一個訂閱者數(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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容