欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

理解VUE双向数据绑定原理和实现

程序员文章站 2022-07-12 21:54:06
...
<!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="app">
        <input type="text" v-model='text'>
        {{text}}
    </div>
    <script>
        function nodeToFragment(node,vm) {
            let fragment = document.createDocumentFragment();
            let child;
            while (child = node.firstChild) {
                compile(child,vm)
                fragment.appendChild(child);
            }
            return fragment;
        }
        // let dom = nodeToFragment(document.getElementById('app'));
        function compile(node, vm) {
            let reg = /\{\{(.*)\}\}/; //用来匹配{{xxx}}中的xxx
            //如果是元素节点
            if(node.nodeType === 1){
                let attr = node.attributes;
                let attrLen = attr.length;
                //解析元素节点的所有属性
                for(let i = 0; i < attrLen; i++){
                    if(attr[i].nodeName == 'v-model'){
                        let name = attr[i].nodeValue;//看看是与哪一个数据有关
                       node.addEventListener('input',function(e){
                           vm[name] = e.target.value;
                       })
                        node.value = vm[name];//将data的值赋给该node
                        node.removeAttribute('v-model');
                    }
                }
            }
            //如果是文本节点
            if(node.nodeType === 3){
                if(reg.test(node.nodeValue)){
                    let name = RegExp.$1;//匹配到获取的字符串
                    name = name.trim();
                   // node.nodeValue = vm[name];
					new Watcher(vm, node, name)
                }
            }

        }
        //创建Vue的实例化函数
        function Vue(options){
            this.data = options.data;
            let data = this.data;
            observe(data,this);
            let id = options.el;
            let dom = nodeToFragment(document.getElementById(id),this);
            //处理完所有dom节点后,重新将内容添加回去;
            document.getElementById(id).appendChild(dom);
        };
        //响应式监听属性的函数
        function defineReactive(obj,key,val){
            let dep = new Dep();
            Object.defineProperty(obj,key,{
                get:function(){
                    // console.log(Dep.target)
                    if(Dep.target){
                        dep.addSub(Dep.target);
                    }
                    return val;
                },
                set:function(newVal){
                    if(newVal === val){
                        return;
                    }
                    val = newVal;
                    //console.log('新值:'+val);
                    //一旦更新立马通知
                    dep.notify();
                }
            })
        };
        //实现一个观察者,对于一个实例 每一个属性值都进行观察。
        function observe(obj,vm){
            for(let key of Object.keys(obj)){
                defineReactive(vm,key,obj[key])
            }
        }
        //Watcher构造函数
        function Watcher(vm,node,name){
            Dep.target = this;
            this.vm = vm;
            this.node = node;
            this.name = name;
            this.update();
            Dep.target = null;
        }
        Watcher.prototype = {
            update(){
                this.get();
                console.log(this.value)
                this.node.nodeValue = this.value; //注意。这是更改节点内容的关键
            },
            get(){
                this.value = this.vm[this.name];
            }
        }
        //dep构造函数
        function Dep(){
            this.subs = [];
        }
        Dep.prototype = {
            addSub(sub){
             
                this.subs.push(sub);
            },
            notify(){
                this.subs.forEach(sub=>{
                  //  console.log(sub)
                    sub.update();
                })
            }
        }
        let vm = new Vue({
            el:'app',
            data:{
                text:''
            }
        })
    </script>
</body>

</html>