vue实现数据双向绑定原理

实现原理

vue实现数据双向绑定主要是采用数据劫持结合发布者、订阅者的模式的方式来实现。通过Object.defineProperty()的get和set来劫持每个属性,在数据发生变化时通过发布者发消息给订阅者,触发相应的监听回掉。具体就是先把说有的数据做一个数据劫持。第一先修改数据,在input框输入值的时候会触发一个对应的方法。把对应的数据修改掉。第二修改视图,在视图中使用到的每一个数据绑定一个Watcher监听,在Object.defineProperty()中的get中把每个Watcher监听存储到订阅者的数组中,当Object.defineProperty()中的set响应到数据发生改变的时候通过发布者通知对应的Watcher,Watcher会在数据中获取对应的值通过Watcher回掉返回,通过node.textContent = 新值来改变视图。

html代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="box">
        <input type="text" v-model="val">
        <div>{{val}}
            <p>{{list.sex}}</p>
        </div>
        <p>{{list.sex}}</p>
    </div>
    <script src="./mvvm.js"></script>
    <script>
        new Mvvm({
            el:'box',
            data:{
                val:'梦亦',
                list:{
                    sex:'男',
                }
            }
        })
    </script>
</body>
</html>

js代码

class Dep{
    constructor(){
        this.watcherList = [];
    }
    subscription(obj){  //订阅者
        this.watcherList.push(obj);
    }
    issue(){    //发布者
        this.watcherList.forEach(item=>{
            item.sendVal();
        })
    }
}

Dep.target = null;
const dep = new Dep();

class Watcher{
    constructor(data, key, ck){
        Dep.target = this;
        this.data = data;
        this.key = key;
        this.ck = ck;
        this.init()
    }
    init(){
        this.value = utils.getValue(this.data,this.key);    //在数据中获取对应的值
        console.log(this.value)
        Dep.target = null;
        return this.value;
    }
    sendVal(){
        let newVal = this.init();
        this.ck(newVal)
    }
}


class Observer{
    constructor(data){
        if(!data || typeof data !== 'object'){  //判断data是否为真
            return;
        }
        this.data = data;
        this.init();
    }

    init(){
        Object.keys(this.data).forEach(key=>{   //拿到当前数据中的key值 循环
            this.observer(this.data, key, this.data[key])
        })
    }
    observer(obj, key, value){
        new Observer(obj[key])  //递归继续往里找

        Object.defineProperty(obj, key, {   //数据劫持
            get(){  //添加数据劫持后属性的获取方法
                if(Dep.target){
                    dep.subscription(Dep.target);    //执行订阅者
                }
                // console.log(value)
                return value;
            },
            set(newValue){  //添加数据劫持后属性的设置方法
                if(value === newValue){
                    return;
                }                
                value = newValue;   //重新赋值
                dep.issue();    //执行发布者
                new Observer(value); //为了兼容新值为一个对象的时候,该对象的属性也得添加劫持
                
            }
        })
    }
}


const utils = {
    setValue(node, data, key){
        node.value = this.getValue(data,key);   //修改input 中的value
    },
    getValue(data,key){ //在数据中找到对应的值
        // console.log(key)
        if(key.indexOf('.') > -1){  //判断key中是否有.
            let arr = key.split('.');   //以.分割成一个数组
            for(let i=0; i<arr.length;i++){ //循环数组
                data = data[arr[i]] //每次循环都会往里找一层,循环完了就找到需要的数据了
            }
            return data;
        }else{
            return data[key];
        }
    },
    getContent(node, data, key){
        node.textContent = this.getValue(data, key);    //修改{{}}文本
    },

    //触发input事件,视图改变,改变数据
    changeKeyVal(data, key, newVal){ 
        if(key.indexOf('.') > -1){  //判断key中是否有.
            let arr = key.split('.');   //以.分割成一个数组
            for(let i=0; i<arr.length-1;i++){ //循环数组 
                data = data[arr[i]] //每次循环都会往里找一层,循环完了就找到需要的数据了
            }
            data[arr[arr.length-1]] = newVal;
        }else{
            data[key] = newVal
        }
        // console.log(data)    数据已根据试图改变
    }
}


class Mvvm{
    constructor({el,data}){
        this.el = el;
        this.data = data;
        this.init();  //数据劫持
        this.initDom(); //替换文本中的数据为真实数据
    }

    init(){
        new Observer(this.data); //给当前数据集合的每一个属性添加劫持
    }


    initDom(){
        this.$el = document.getElementById(this.el);    //拿到真实DOM
        let newFargment = this.createFragment();
        this.compiler(newFargment);
        this.$el.appendChild(newFargment);
    }

    createFragment(){
        let newFargment = document.createDocumentFragment(); //创建一个新的空白的文档片段
        let firstChild;
       
        while(firstChild = this.$el.firstChild){    //利用while 把所有节点appendChild到一个空白文档片段中 
            newFargment.appendChild(firstChild);
        }
        return newFargment; //返回文档片段
    }

    compiler(node){
        // console.log(node.nodeType)
        if(node.nodeType === 1){    //空白节点和input的nodeType是1
            let attributes = node.attributes;
            Array.from(attributes).forEach(val =>{
                if(val.nodeName === 'v-model'){
                    // console.log(val.nodeValue)
                    node.addEventListener('input',(e)=>{    //给input绑定input事件
                        // console.log(e.target.value)
                        utils.changeKeyVal(this.data, val.nodeValue, e.target.value)
                    })

                    utils.setValue(node, this.data, val.nodeValue);
                }
            })
        }else if(node.nodeType === 3){  //div p...的nodeType是1
            let contentVal = node.textContent.indexOf('{{') > -1 && node.textContent.split('{{')[1].split('}}')[0]; //拿到文本值 判断格式是否以{{开头,先以{{分割,截取出需要的,再以}}分割,截取出剩余的。就把{{}}去掉了。

            contentVal && utils.getContent(node, this.data, contentVal);    //判断contentVal是否为真,把{{val}}修改为真实数据

            contentVal && new Watcher(this.data, contentVal, (newVal)=>{    //监听
                node.textContent = newVal;
            });
        }

        if(node.childNodes && node.childNodes.length > 0){  //判断子节点集合
            node.childNodes.forEach(item=>{ //循环子节点集合
                this.compiler(item); //递归函数
            })
        }
    }


}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值