通过 Proxy 了解 v-model 原理

947 阅读1分钟

vue 2.x 版本使用 defineProperty 来实现双向数据绑定,而 3.0 之后换成了 proxy。

事实上 defineProperty 本意并不是用来做双向数据绑定的,而是进行对象属性的定义,由于其中的访问器可以对属性进行拦截,所以尤大用这个特性来实现双向数据绑定。

现在有如图所示的界面:

img-01

想要实现双向数据绑定的效果,事实上只需要两步:

  • 数据更新通知数据容器
  • 数据容器接到通知更新视图

img-02

而 es6 中新增的 proxy 对象可以很好的帮我们实现这个容器。

那么首先我们需要创建一个容器:

class View{
    constructor(){
        const proxy = new Proxy({},{
            get(obj,props){},
            set(obj,props,value){}
        })
    }
}

我们通过 proxy 代理这个容器对象,然后为输入源增加事件:

class View{
    constructor(){
        const proxy = new Proxy({},{
            get(obj,props){},
            set(obj,props,value){}
        })
        
        this.init(proxy)
    }
    
    init(proxy){
        const els = document.querySelectorAll("[v-model]")
        els.forEach(el =>{
            el.addEventListener('keyup',() =>{})
        })
    }
}

当我们点击的时候,将输入源中的值存入容器中:

class View{
    constructor(){
        const proxy = new Proxy({},{
            get(obj,props){},
            set(obj,props,value){}
        })
        
        this.init(proxy)
    }
    
    init(proxy){
        const els = document.querySelectorAll("[v-model]")
        els.forEach(el =>{
            el.addEventListener('keyup',() =>{
                proxy[el.getAttribute("v-model")] = el.value;
            })
        })
    }
}

到这里我们完成了一半,接下来就是当容器收到消息之后进行 render 渲染页面:

class View{
    constructor(){
        const proxy = new Proxy({},{
            get(obj,props){},
            set(obj,props,value){
                this.render(prop,value)
                // set 成功需要返回一个 bool,否则在严格模式下会抛出错误
                return true;
            }
        })
        
        this.init(proxy)
    }
    
    init(proxy){
        const els = document.querySelectorAll("[v-model]")
        els.forEach(el =>{
            el.addEventListener('keyup',() =>{
                proxy[el.getAttribute("v-model")] = el.value;
            })
        })
    }
    
    render(prop,value){
        document.querySelectorAll(`[v-model="${prop}"]`).forEach(el => {
                el.value = value;
            });
        document.querySelectorAll(`[v-bind="${prop}"]`).forEach(el => {
            el.innerHTML = value;
        });
    }
}

到这里就实现了双向数据绑定的基本原理:

img-03