Vue双向数据绑定

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来实现统一管理。

  1. 实现一个监听器 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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容