vue底层原理

vue的双向绑定原理及实现

  • vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的。

  • 通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情。

  • 订阅者和发布者模式
    订阅者和发布者模式,通常用于消息队列中.一般有两种形式来实现消息队列

    • 一是使用生产者和消费者来实现,
    • 二是使用订阅者-发布者模式来实现。其中订阅者和发布者实现消息队列的方式,就会用订阅者模式。
  • 原理以及基础

    12653633-eead3f1968b694cd.png

    Object.defineProperty-详细文档

  • 小案例--在名字上加入括号


    截屏2020-03-05下午9.55.21.png
    let love = {};
    let name = '';
    Object.defineProperty(love,'name',{
        set:function(value) {
            name = value;
        },
        get:function() {
            return '《'+name+'》';
        }
    });
    love.name = '我的最爱';
    console.log(love.name);  //《我的最爱》

简单的mvvm双向绑定的demo

  • 思路分析


    12653633-fa21a82839e175a7.png

    要想实现mvvm,主要包含两个方面,视图变化更新数据,数据变化更新视图。view变化更新data其实可以通过事件监听实现,比如input标签监听input事件,所有我们着重分析data变化更新view。data变化更新view的重点是如何知道view什么时候变化了,只要知道什么时候view变化了,那么接下来的就好处理了。这个时候我们上文提到的Object.defineProperty( )就起作用了。通过Object.defineProperty( )对属性设置一个set函数,当属性变化时就会触发这个函数,所以我们只需要将一些更新的方法放在set函数中就可以实现data变化更新view了。

  • 实现过程


    12653633-d82f70569b8c385f.png

    首先要对数据进行劫持监听,所以首先要设置一个监听器Observer,用来监听所有的属性,当属性变化时,就需要通知订阅者Watcher,看是否需要更新.属性可能是多个,所以会有多个订阅者,故需要一个消息订阅器Dep来专门收集这些订阅者,并在监听器Observer和订阅者Watcher之间进行统一的管理。以为在节点元素上可能存在一些指令,所以还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令初始化成一个订阅者Watcher,并替换模板数据并绑定相应的函数,这时候当订阅者Watcher接受到相应属性的变化,就会执行相对应的更新函数,从而更新视图。

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

     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 getter() {
                     if (Dep.target) {
                         dep.addSub(Dep.target);
                     }
                     return val;
                 },
                 set: function setter(newVal) {
                     if (newVal === val) {
                         return;
                     }
                     val = newVal;
                     dep.notify();
                 }
             });
         }
     };
    
     function observe(value, vm) {
         if (!value || typeof value !== 'object') {
             return;
         }
         return new Observer(value);
     };
    
     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;
    

    2、 实现一个订阅者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;
        }
    };
    

    3、 实现一个解析器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;
            }
         }
    

    4、 index.js

     function SelfVue(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);
         options.mounted.call(this); // 所有事情处理好后执行mounted函数
     }
    
     SelfVue.prototype = {
         proxyKeys: function (key) {
             var self = this;
             Object.defineProperty(this, key, {
                 enumerable: false,
                 configurable: true,
                 get: function getter() {
                     return self.data[key];
                 },
                 set: function setter(newVal) {
                     self.data[key] = newVal;
                 }
             });
         }
     } 
    

    5、 index.html

    
    
    
        
     
    
    
    

    {{title}}

    {{name}}

你可能感兴趣的:(vue底层原理)