如何手写实现《双向数据绑定》

2022-07-22 14:49:03 浏览数 (2)

面试中,双向数据绑定几乎是面试必备;

不只是要求会描述出来,而且要求能手写出来

下面就分享一下,

思路:

1. 数据 -> 转为响应式数据 Object.defineProperty Proxy

即:将存放数据的对象属性,与实例化对象的属性对应

2. input -> input / keyup -> 事件处理函数的绑定 -> 改变数据

3. 绑定dom {{}} 中的属性,更新数据的同时,更新对应dom节点

目的:this.domPool[key] = 对应的dom节点

具体代码:

html文件:

代码语言:javascript复制
<div id="app">
    <input type="text" placeholder="姓名" v-model="name"/>
    <input type="text" placeholder="年龄" v-model="age"/>
    <input type="text" placeholder="年龄" v-model="sex"/>
    <input type="text" placeholder="email" v-model="email"/>
    <input type="text" placeholder="电话" v-model="tel"/>
    <p>姓名:<span>{{ name }}</span></p>
    <p>年龄:<span>{{ age }}</span></p>
    <p>年龄:<span>{{ sex }}</span></p>
    <p>email:<span>{{ email }}</span></p>
    <p>电话:<span>{{ tel }}</span></p>
    <p><button id="btn">change</button></p>
</div>

<script src="MVVM.js"></script>
<script type="text/javascript">
    const app = new MVVM('#app',{
        name:'',
        age:'',
        sex:'',
        email:'',
        tel:''
    })
    // 添加一个按钮事件,测试,数据改变时,dom也跟着改变
    var btn = document.getElementById("btn");
    btn.addEventListener("click",function () {
        app.setData('name','doubleyong');
    },false)
</script>

MVVM.js

对于双向数据绑定的封装

代码语言:javascript复制
class MVVM {
    constructor(el,data) {
        this.el = document.querySelector(el);
        // this._data = data;
        this.data =data;
        this.domPool={};
        this.init();
    }
    // 初始化
    init(){
      this.initData(); //初始数据
      //实现数据的响应
      this.initDom(); //初始dom
    }
    //目的:暴露的实例,可以直接操作data中的属性
    // 实例对象.属性名 进行获取 
    // 实例对象.属性名 = 值; 进行设置 
    initData(){
       let _this = this;
       this.data = {};
       for(let key in _this._data){ 
       // let key 与var key, 作用域不同
           Object.defineProperty(_this.data,key,{
               get() {
                   return _this._data[key];
               },
               set(v) {
                   _this._data[key] = v;
                   // 数据修改后,对应dom显示也进行更新
                   _this.domPool[key].innerText = v;
               }
           })
       }
    }
    initDom(){
        this.bindDom(this.el); //绑定dom {{}} 中的属性,与dom节点
        this.bindInput(this.el); //实现更新input的value值,数据更新
    }
    // 目的:将对应的键值,和对应显示的dom进行关联
    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){
                        //将对应{{ }} 中的值,添加到dompool中
                        const _key = _value.match(/{{(. ?)}}/)[1].trim();
                        this.domPool[_key] = item.parentNode;
                        item.parentNode.innerText = this.data[_key] || undefined;
                    }
                }
            }

            item.childNodes && this.bindDom(item);
        })
    }
    // 目的:绑定input控件,input值改后,更新对应data值
    bindInput(el){
       const _allInputs = document.querySelectorAll('input');
        _allInputs.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;
    }
    setData(key,value){
        this.data[key] = value;
    }
}

0 人点赞