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

vue双向数据绑定是通过数据劫持结合订阅者发布者模式(观察者)来实现的
关键在于data如何去更新view,因为view更新model可以同过监听input事件来进行更新
data更新view主要是通过Object.definePropety劫持各个属性的setter和getter方法,在数据变动时发布消息通知所有的订阅者。

简单的双向数据绑定原理

<!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" id="a">
        <span id="b"></span>
    </div>
</body>
<script>
    var obj = {};  //定义一个空对象
    var val = 'val';  //赋予初始值
    Object.defineProperty(obj, 'val', {//定义要修改对象的属性
        get: function () {
            return val;
        },
        set: function (newVal) { 
             val = newVal;//定义val等于修改后的内容
            document.getElementById('a').value = val;//让文本框的内容等于val        
            document.getElementById('b').innerHTML = val;//让span的内容等于val
        }
    });
    document.addEventListener('keyup', function (e) {//当在文本框输入内容时让对象里你定义的val等于文本框的值
        obj.val = e.target.value;
    })
    console.log(obj.val)
    setTimeout(function(){
        obj.val='startvalue'
        console.log(obj.val)
    },5000)
   
</script>
</html>

进阶版

<!DOCTYPE html>
 <head>
    <meta charset="UTF-8">
    <title>双向绑定</title>
 </head>
 <body>

    <!-- 实现vue -->
    <div id="app">
        <input type="text" v-model="text">
        {
   
   { text }}
        <br>
        <input type="text" v-model="text2">
        {
   
   { text2 }}
        <p>{
   
   {text2}}</p>
       
    </div>

  <script type="text/javascript">

    function defineReactive(obj, key, val){
        var dep = new Dep();
        Object.defineProperty(obj, key, {
            get: function(){
                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]);
        }
    }


    function nodeToFragment(node,vm){
        var fragment = document.createDocumentFragment();
        var child;
        while(child = node.firstChild){
            compile(child, vm);
            fragment.appendChild(child);
        }
        return fragment
    }


    /*编译函数*/
    function compile(node, vm){
        var reg = /\{\{(.*)\}\}/; // 来匹配 {
   
   { xxx }} 中的xxx
        // 如果是元素节点
        if(node.nodeType === 1){
            var attr = node.attributes;
            // 解析元素节点的所有属性
            for(let i=0;i<attr.length;i++){
                if(attr[i].nodeName == 'v-model'){
                    var name = attr[i].nodeValue; // 看看是与哪一个数据相关
                    node.addEventListener('input', function(e){
                        vm[name] = e.target.value; // 将实例的text 修改为最新值
                    });
                    node.value = vm[name]; // 将data的值赋给该node
                    node.removeAttribute('v-model');
                }
            };
        }
        // 如果是文本节点
        if(node.nodeType === 3){
            if(reg.test(node.nodeValue)){
                var name = RegExp.$1; // 获取到匹配的字符串
                name = name.trim(); 
                // node.nodeValue = vm[name];  // 将data的值赋给该node

                new Watcher(vm, node, name); // 不直接通过赋值的操作,而是通过绑定一个订阅者
            }
        }
    }

    /*Watcher构造函数*/
    function Watcher(vm, node, name){
        Dep.target = this; // Dep.target 是一个全局变量
        this.vm = vm;
        this.node= node;
        this.name = name;
        this.update();
        Dep.target = null;
    }
    Watcher.prototype = {
        update(){
            this.get();
            this.node.nodeValue = this.value; // 注意,这是更改节点内容的关键
        },
        get(){
            this.value = this.vm[this.name]; // 触发相应的get
        }
    }

    /*dep构造函数*/
    function Dep(){
        this.subs = [];
    }
    Dep.prototype = {
        addSub(sub){
            this.subs.push(sub);
        },
        notify(){
            this.subs.forEach(function(sub){
                sub.update();
            })
        }
    }

    /*Vue构造函数*/
    function Vue(options){
        this.data = options.data;
        var data = this.data;

        observe(data, this);

        var id = options.el;
        var dom = nodeToFragment(document.getElementById(id), this);
        // 处理完所有dom节点后,重新将内容添加回去
        document.getElementById(id).appendChild(dom);
    }

    var vm = new Vue({
        el: 'app',
        data: {
            text: 'hello world',
            text2: 'hello world',
        }
    });

   </script>
  </body>
</html>

猜你喜欢

转载自blog.csdn.net/qq_41988554/article/details/104056656