概念

双向绑定概念其实很简略,就是视图(View)的变动能实时让数据模型(Model)发生变化,而数据的变动也能实时更新到视图层。咱们所说的单向数据绑定就是从数据到视图这一方向的关系。

剖析

1、响应式数据

应用Object.defineProperty、Proxy对数据进行监听拦挡。

//obj:必须。指标对象//prop:必须。需定义或批改的属性的名字//descriptor:必须。指标属性所领有的个性Object.defineProperty(obj, prop, descriptor)

vue3.0 开始 Proxy代替Object.defineProperty

let p = new Proxy(target, handler);

2、input事件监听

绑定事件处理函数,实时批改数据。

3、相干dom操作

将数据与相干dom节点绑定在一起,批改数据的时候对应的dom节点也应一起扭转。

实现

1、html页面

<!DOCTYPE html><html>    <head>        <meta charset="utf-8">        <title>v-model</title>    </head>    <body>        <div id="app">            <input type="text" v-model="name" placeholder="姓名"/>            <input type="text" v-model="age" placeholder="年龄"/>            <div>                <p>                    姓名:<span>{{name}}</span>                </p>                <p>                    年龄:<span>{{age}}</span>                </p>                <p>                    <p>                        年龄:<span>{{age}}</span>                    </p>                </p>            </div>            <button id="btn">批改名字</button>        </div>    </body>    <script src="./VModel.js"></script>    <script>        const app = new VModel('#app',{            name:'',            age:''        });        document.getElementById('btn').addEventListener('click',function() {            app.setData('name','名字扭转了');        })    </script></html>

2、VModel.js

(1)结构VModel

class  VModel {    constructor(el,data) {        this.el = document.querySelector(el);        //存放数据对象        this._data = data;        //寄存绑定数据的dom节点        this.domPoll = {};                this.init();    }}

(2)初始化数据对象

应用Object.defineProperty
initData () {    const _this = this;    this.data = {};    for(let key in this._data){        Object.defineProperty(this.data,key,{            get(){                console.log("获取数据",key,_this._data[key]);                return _this._data[key];            },            set(newVal){                console.log("设置数据",key,newVal);                _this.domPoll[key].innerText = newVal;                _this._data[key] = newVal;            }        });    }}
应用Proxy
initData () {    const _this = this;    this.data = {};    this.data = new Proxy(this.data,{        get(target,key){            return Reflect.get(target,key);        },        set(target,key,value){            // _this.domPoll[key].innerText = value;            _this.domPoll[key].forEach(item => {                item.innerText = value;            })            return Reflect.set(target,key,value);        }    })}

(3)绑定dom节点

bindDom(el){    const childNodes = el.childNodes;    childNodes.forEach(item => {        //nodeType为3时该dom节点为文本节点        if(item.nodeType === 3){            const _value = item.nodeValue;                    if(_value.trim().length){                //匹配是否有两个花括号包裹的数据                let  _isValid = /\{\{(.+?)\}\}/.test(_value);                if(_isValid){                    const _key = _value.match(/\{\{(.+?)\}\}/)[1].trim();                    // this.domPoll[_key] = item.parentNode;                    //一个数据能够被多个dom节点绑定,所以应该用数组来进行保留                        //未定义时先初始化                        if(!this.domPoll[_key]) this.domPoll[_key] = [];                        this.domPoll[_key].push(item.parentNode);                    //替换绑定的值                    item.parentNode.innerText = this.data[_key] || undefined;                }            }        }        //递归遍历子节点        item.childNodes && this.bindDom(item);    })}

(4)输入框数据绑定

bindInput(el){    //获取input所有元素节点    const _allInput = el.querySelectorAll('input');    _allInput.forEach(input => {        const _vModel = input.getAttribute('v-model');        //判断是否有v-model属性        if(_vModel){//监听输出事件            input.addEventListener('keyup',this.handleInput.bind(this,_vModel,input),false);        }    })}handleInput(key,input){    const _value = input.value;    //数据变动的时候会同步批改dom节点绑定的数据。    this.data[key] = _value;}

(4)残缺代码

class  VModel {    constructor(el,data) {        this.el = document.querySelector(el);        this._data = data;        this.domPoll = {};                this.init();    }        init(){        this.initData();        this.initDom();    }    initDom(){        this.bindDom(this.el);        this.bindInput(this.el);        console.log('domPoll',this.domPoll);    }        initData () {        const _this = this;        this.data = {};        // for(let key in this._data){        //     Object.defineProperty(this.data,key,{        //         get(){        //             console.log("获取数据",key,_this._data[key]);        //             return _this._data[key];        //         },        //         set(newVal){        //             console.log("设置数据",key,newVal);        //             _this.domPoll[key].innerText = newVal;        //             _this._data[key] = newVal;        //         }        //     });        // }        this.data = new Proxy(this.data,{            get(target,key){                return Reflect.get(target,key);            },            set(target,key,value){                // _this.domPoll[key].innerText = value;                _this.domPoll[key].forEach(item => {                    item.innerText = value;                })                return Reflect.set(target,key,value);            }        })    }        bindDom(el){        const childNodes = el.childNodes;        childNodes.forEach(item => {            if(item.nodeType === 3){                const _value = item.nodeValue;                            if(_value.trim().length){                    let  _isValid = /\{\{(.+?)\}\}/.test(_value);                    if(_isValid){                        const _key = _value.match(/\{\{(.+?)\}\}/)[1].trim();                        // this.domPoll[_key] = item.parentNode;                        if(!this.domPoll[_key]) this.domPoll[_key] = [];                        this.domPoll[_key].push(item.parentNode);                        item.parentNode.innerText = this.data[_key] || undefined;                    }                }            }                        item.childNodes && this.bindDom(item);        })    }        bindInput(el){        const _allInput = el.querySelectorAll('input');        _allInput.forEach(input => {            const _vModel = input.getAttribute('v-model');                        if(_vModel){                input.addEventListener('keyup',this.handleInput.bind(this,_vModel,input),false);            }        })    }        handleInput(key,input){        const _value = input.value;        this.data[key] = _value;        // console.log(this.data);    }        setData(key,value){        this.data[key] = value;    }}