vue双向绑定原理解析

通过简单的实例解析vue双向绑定的原理。

let vm=new Vue({
  el:'#app',
  data:{
    text:'test'
  }
})

 

总体实现步骤:

第一步:输入框及文本节点与data中的属性进行关联绑定

第二步:输入框内容变化时,data中的数据同步变化,即view-->model

第三步:data中的数据变化时,文本节点同步变化,即data-->view

 

实现一:

输入框与文本节点与data中的属性进行关联绑定,用到了es5中的Object.defineProperty方法,将data中的属性变成vm对象的访问器属性。

function defineReactive(obj,key,val){
Object.defineProperty(obj,key,{
set:function(newValue){
  if(val!==newValue){
    val=newValue;
  }
},
get:function(){
  return val;
}
});
}


 完整代码:

<!DOCTYPE html>
<html>

<head>
    <title>vue</title>
    <style>
    input {
        border: 1px solid #ddd;
        padding: 8px 10px;
        border-radius: 5px;
        margin-right: 10px;
    }
    </style>
</head>

<body>
    <div id="app">
        <input type="text" v-model='text'> {{text}}
    </div>
    <script type="text/javascript">
    //任务分解
    //1,输入框及文件节点与data中的数据绑定
    //2,输入框数据变化时,data中的数据同步变化,即view=>model
    //3,data中的数据变化时,文本节点同步变化,即model=>view

    //要实现第一步,需要对dom进行编译,这里用到了documentFragment

    //将data中的数据设置为vm的访问器属性
    function defineReactive(obj, key, val) {
        var dep = new Dep();
        Object.defineProperty(obj, key, {
            set: function(newValue) {
                if (newValue === val) return;
                val = newValue;
                //作为发布者发出通知
                dep.notify();
            },
            get: function() {
                //添加订阅者watcher到主题对象Dep
                if (Dep.target) {
                    dep.addSub(Dep.target);
                }

                return val;
            }
        });
    }

    function observe(obj, vm) {
        Object.keys(obj).forEach(function(key) {
            defineReactive(vm, key, obj[key]);
        });
    }

    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返回到app中
        document.getElementById(id).appendChild(dom);
    }

    function nodeToFragment(node, vm) {
        var frag = document.createDocumentFragment();
        var child;
        while (child = node.firstChild) {
            compile(child, vm);
            frag.appendChild(child);
        }
        return frag;
    }
    //输入框和文本节点与data进行绑定
    function compile(node, vm) {
        var reg = /\{\{(.*)\}\}/;
        //节点类型为元素
        if (node.nodeType == 1) {
            var attrs = node.attributes;
            for (var i = 0, len = attrs.length; i < len; i++) {
                if (attrs[i].nodeName == 'v-model') {
                    //获得v-model绑定的属性名
                    var name = attrs[i].nodeValue;
                    node.addEventListener('input', function() {
                        vm[name] = this.value;
                    });
                    node.value = vm[name];
                    node.removeAttribute('v-model');
                }

            }
            new Watcher(vm, node, name, 'input');
        } else if (node.nodeType == 3) { //节点类型为text
            if (reg.test(node.nodeValue)) {
                var name = RegExp.$1; //获取匹配到的字符串
                name = name.trim();
                // node.nodeValue = vm[name];
                new Watcher(vm, node, name);
            }

        }
    }
    //订阅发布模式(观察者模式)
    //发布者发布消息=>主题对象收到消息并推送给订阅者=>订阅者执行相应操作
    //为每个与数据相关的节点生成一个订阅者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: function() {
            this.get();
            this.node.nodeValue = this.value;
        },
        get: function() {
            this.value = this.vm[this.name];
        }
    };

    //主题对象
    function Dep() {
        this.subs = [];
    }
    Dep.prototype = {
        notify: function() {
            this.subs.forEach(function(sub) {
                sub.update();
            });
        },
        addSub: function(sub) {
            this.subs.push(sub);
        }
    };

    var vm = new Vue({
        el: 'app',
        data: {
            text: 'hello world',
        }
    });
    </script>
</body>

</html>

 

已标记关键词 清除标记
表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符 “速评一下”
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页