Vue学习笔记--Vue双向绑定实现原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cc_fys/article/details/81636696

           我们知道Vue可以实现数据双向绑定,AngularVue都是采用的MVVM 模式,意思就是当M(模型层)层数据进行修改时,VM层会监测到变化,并且通知V(视图层)层进行相应的修改,反之修改V层则会通知M层数据进行修改,实现了视图与模型层的相互解耦。其中Angular是采用的脏值检测实现的,Vue是采用的发布-订阅模式+数据劫持 实现的。

          Vue是通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的。当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

那么先看看Vue对数据的包装吧!使用 Object.getOwnPropertyDescriptor 查看数据属性描述。

    var mydata={
        name:'cc',
        age:'2'
    };
    var app= new Vue({
        el: '#app',
        data: mydata
    });
    console.log(app.name===mydata.name);
    
    console.log(Object.getOwnPropertyDescriptor(app,'name'));
    console.log(Object.getOwnPropertyDescriptor(mydata,'name'));

控制台输出:

诶,可以看到确实Vue对data数据进行了处理,这个动作发生在 Vue生命周期的 beforeCreate  和 created 之间。(注意这里可以看到getter\setter的函数名不一样,这是因为将data对象进行数据劫持之后,要在vue实例对象上可访问,将根级响应式属性代理到了vue实例上

可以试着使用一下Object.defineProperty ,在数据改变的时候得到响应。比如像这样:

var mydata={
    name:'cc',
    age:'2',
    like:{
        dog:'二狗子',
        cat:'小哈'
    }
};

//递归地设置
function DataProcess(data)
{
    if (!data || typeof data !== 'object')
        return;
    Object.keys(data).forEach(function(item){
        definePro(data,item,data[item])
        }
    );
}
function definePro(data,item,val)
{
        DataProcess(val);
        Object.defineProperty(data,item,{
            enumerable: true,
            configurable: true,
            get: function getter () {
                return val;

            },
            set: function setter (newVal) {
                if(val!=newVal)
                {
                    val=newVal;
                    console.log('数据发生了改变!新值为'+newVal);
                }

            }
        });
}

DataProcess(mydata);

mydata.like.dog='eclipse';   //输出:数据发生了改变!新值为eclipse
mydata.like.dog='eclipse';   //这里无输出

实现过程:

1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

Observer是一个数据监听器,其实现核心方法就是前文所说的Object.defineProperty( )。如果要对所有属性都进行监听的话,那么可以通过递归方法遍历所有属性值,并对其进行Object.defineProperty( )处理。

2.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

Compile在vue中这个动作发生在 Vue生命周期的 created 和 mounted之间

3.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:在自身实例化时往属性订阅器(dep)里面添加自己,自身必须有一个update()方法,待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #app{
            text-align: center;
        }

    </style>
</head>
<body>
<div id="app">
     <input type="text" v-model="msg"/>
      <div>
        {{msg}}
      </div>
     <div>
        <button v-on:click="change">click it!</button>
     </div>
</div>
<script>

    // myVue
    function  MyVue(options) {
        var self = this;
        this.data = options.data;
        this.methods = options.methods;
        //设置代理
        Object.keys(this.data).forEach(function(key) {
            self.proxyKeys(key);
        });

        observe(this.data);
        new Compile(options.el, this);
    }
    MyVue.prototype = {
        proxyKeys: function (key) {
            var self = this;
            Object.defineProperty(this, key, {
                enumerable: false,
                configurable: true,
                get: function proxyGetter () {
                    return self.data[key];
                },
                set: function proxySetter (newVal) {
                    self.data[key] = newVal;
                }
            });
        }
    };
    //observer
    function observe(value, vm) {
        if (!value || typeof value !== 'object') {
            return;
        }
        return new Observer(value);
    };

    function Observer(data) {
        this.data = data;
        this.walk(data);
    }
    Observer.prototype = {
        walk: function(data) {
            var self = this;
            Object.keys(data).forEach(function(key) {
                self.defineReactive(data, key, data[key]);
            });
        },
        defineReactive: function(data, key, val) {
            var dep = new Dep();
            var childObj = observe(val);
            Object.defineProperty(data, key, {
                enumerable: true,
                configurable: true,
                get: function reactiveGetter () {
                    if (Dep.target) {
                        dep.addSub(Dep.target);
                    }
                    return val;
                },
                set: function reactiveSetter (newVal) {
                    if (newVal === val) {
                        return;
                    }
                    val = newVal;
                    dep.notify();
                }
            });
        }
    };
    function Dep () {
        this.subs = [];
    }
    Dep.prototype = {
        addSub: function(sub) {
            this.subs.push(sub);
        },
        notify: function() {
            this.subs.forEach(function(sub) {
                sub.update();
            });
        }
    };
    Dep.target = null;

    //compile
    function Compile(el, vm) {
        this.vm = vm;
        this.el = document.querySelector(el);
        this.fragment = null;
        this.init();
    }
    Compile.prototype = {
        init: function () {
            if (this.el) {
                this.fragment = this.nodeToFragment(this.el);
                this.compileElement(this.fragment);
                this.el.appendChild(this.fragment);
            } else {
                console.log('Dom元素不存在');
            }
        },
        nodeToFragment: function (el) {
            var fragment = document.createDocumentFragment();
            var child = el.firstChild;
            while (child) {
                // 将Dom元素移入fragment中
                fragment.appendChild(child);
                child = el.firstChild
            }
            return fragment;
        },
        compileElement: function (el) {
            var childNodes = el.childNodes;
            var self = this;
            [].slice.call(childNodes).forEach(function(node) {
                var reg = /\{\{(.*)\}\}/;
                var text = node.textContent;

                if (self.isElementNode(node)) {
                    self.compile(node);
                } else if (self.isTextNode(node) && reg.test(text)) {
                    self.compileText(node, reg.exec(text)[1]);
                }

                if (node.childNodes && node.childNodes.length) {
                    self.compileElement(node);
                }
            });
        },
        compile: function(node) {
            var nodeAttrs = node.attributes;
            var self = this;
            Array.prototype.forEach.call(nodeAttrs, function(attr) {
                var attrName = attr.name;
                if (self.isDirective(attrName)) {
                    var exp = attr.value;
                    var dir = attrName.substring(2);
                    if (self.isEventDirective(dir)) {  // 事件指令
                        self.compileEvent(node, self.vm, exp, dir);
                    } else {  // v-model 指令
                        self.compileModel(node, self.vm, exp, dir);
                    }
                    node.removeAttribute(attrName);
                }
            });
        },
        compileText: function(node, exp) {
            var self = this;
            var initText = this.vm[exp];
            this.updateText(node, initText);
            new Watcher(this.vm, exp, function (value) {
                self.updateText(node, value);
            });
        },
        compileEvent: function (node, vm, exp, dir) {
            var eventType = dir.split(':')[1];
            var cb = vm.methods && vm.methods[exp];

            if (eventType && cb) {
                node.addEventListener(eventType, cb.bind(vm), false);
            }
        },
        compileModel: function (node, vm, exp, dir) {
            var self = this;
            var val = this.vm[exp];
            this.modelUpdater(node, val);
            new Watcher(this.vm, exp, function (value) {
                self.modelUpdater(node, value);
            });

            node.addEventListener('input', function(e) {
                var newValue = e.target.value;
                if (val === newValue) {
                    return;
                }
                self.vm[exp] = newValue;
                val = newValue;
            });
        },
        updateText: function (node, value) {
            node.textContent = typeof value == 'undefined' ? '' : value;
        },
        modelUpdater: function(node, value, oldValue) {
            node.value = typeof value == 'undefined' ? '' : value;
        },
        isDirective: function(attr) {
            return attr.indexOf('v-') == 0;
        },
        isEventDirective: function(dir) {
            return dir.indexOf('on:') === 0;
        },
        isElementNode: function (node) {
            return node.nodeType == 1;
        },
        isTextNode: function(node) {
            return node.nodeType == 3;
        }
    }

    //watcher
    function Watcher(vm, exp, cb) {
        this.cb = cb;
        this.vm = vm;
        this.exp = exp;
        this.value = this.get();  // 将自己添加到订阅器的操作
    }
    Watcher.prototype = {
        update: function() {
            this.run();
        },
        run: function() {
            var value = this.vm.data[this.exp];
            var oldVal = this.value;
            if (value !== oldVal) {
                this.value = value;
                this.cb.call(this.vm, value, oldVal);
            }
        },
        get: function() {
            Dep.target = this;  // 缓存自己
            var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
            Dep.target = null;  // 释放自己
            return value;
        }
    };


    //调用
    var app= new MyVue({
        el: '#app',
        data:{
            msg:'happy'
        },
        methods:{
            change:function () {
                this.msg='you click it!';
            }
        }
    });


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

具体可以参考下面几篇:

vue的双向绑定原理及实现

剖析Vue原理&实现双向绑定MVVM

其他参考:

关于Vue的MVVM

详解vue生命周期

深入响应式原理

猜你喜欢

转载自blog.csdn.net/cc_fys/article/details/81636696
今日推荐