vue-双向数据绑定原理分析二--Mr.Ember

vue-双向数据绑定原理分析二--Mr.Emberhtml

摘要:双向数据绑定是vue的核心,经过是数据劫持结合发布者-订阅者模式的模式实现的。vue

上一讲提到了怎么简单的模仿双向数据绑定,下面咱们将会分析下vue双向数据绑定源码的简单实现。node

实现原理数组

双向数据绑定是经过数据的劫持结合发布者-订阅者模式的方式实现的。浏览器

数据劫持、vue是经过Object.definedProperty()实现数据劫持,其中会有getter()和setter()方法。当读取属性值时,就会触发getter()方法,在view中若是数据发生了变化,就会经过Object.defineProperty()对属性设置一个setter函数,当数据改变了就会触发这个函数;app

 

一. 实现一个监听器observer函数

observer主要是经过Object.defineProperty()来监听属性的变更,那么将须要observer的数据对象进行递归遍历,包括子属性对象,都加上setter和getter,给这个对象的某个值赋值,
就会触发setter,从而监听数据的变化。

 

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, {
            get: function getter() {
                if(Dep.target) {
                    // 添加一个订阅者
                    console.log(Dep.target)
                    dep.addSub(Dep.target);
                }
                return val;
            },
            // 若是一个对象属性值发生改变,就会出发setter中的dep.notify(),通知watcher订阅者数据发生变动,执行对应订阅者的更新函数,来更新视图
            set: function setter(newVal) {
                if(newVal === val) {
                    return;
                }
                val = newVal;
                // 新的值是object的话,进行监听
                childObj = observe(newVal);
                dep.notify();
            }
        })
    }
}

监听数据变化以后就是怎么通知订阅者了,因此须要实现一个消息订阅器,维护数组,用来收集订阅者,数据变更触发notify,再调用订阅者的update方法性能

// 建立Observer实例
function observe(value, vm) {
    if(!value || typeof value !== 'object') {
        return;
    }
    return new Observer(value);
}

// 消息订阅器Dep()主要负责收集订阅者,而后在属性变化的时候执行对应订阅者的更新函数
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;

 

二. 实现一个Compilethis

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

由于遍历解析的过程有屡次操做DOM节点,为提升性能和效率,会先将节点el转换成文档碎片fragment进行解析编译操做,解析完成,再将fragment添加会真实的DOM节点中

 
 
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); //将绑定的DOM元素添加到fragment元素
      this.compileElement(this.fragment); //解析模板
      this.el.appendChild(this.fragment); //再把模板添加到元素上
    } else {
      console.log('DOM元素不存在')
    }
  },
  nodeToFragment: (el) => { //把要解析的元素单独拿出来解析 把数据一下拿出来解析操做减小DOM操做 减小浏览器的重绘和重排
    var fragment = document.createDocumentFragment();
    var child = el.firstChild;
    while(child) {
      //将DOM元素移入fragment
      fragment.appendChild(child);
      child = el.firstChild;
    }
    return fragment;
  },
}
 

compileElement方法将遍历全部的子节点,进行扫描编译解析,调用对应的指令渲染函数进行数据渲染,并调用对应的指令更新函数进行绑定。

Compile.prototype = {
    //.....
    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)) {
                console.log(node);

                self.compileText(node, reg.exec(text)[1]);
                console.log(reg.exec(text)[0]);
            }
            // 若是元素有子节点,再回调compileElement函数
            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) {    //解析model
        var self = this;
        var val = this.vm[exp];
        this.modelUpdater(node, val);
        new Watcher(this.vm, exp, function(value) {     //给model添加一个监听器
            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) {
        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;
    }




}

经过递归遍历保证了每一个节点和子节点会编译解析到,包括{{}}表达式里的文本节点。指令的声明规定,是经过特定前缀的节点属性来标记。监听数据和绑定更新函数是在compileUtil.bind()方法中,经过new Watcher()添加回调来接收数据变化的通知。

接下来实现一个watcher

 

三.实现一个Watcher

Watcher订阅者做为Observer和Compile之间通讯的桥梁,主要作的是:

1. 在自身实例化时往属性订阅器(dep)中添加本身

2. 自身必须有一个update()方法

3. 待属性变更dep.notice()通知时,能调用自身的uodate()方法,并触发Compile中绑定的回调

 
 
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]; //这里会触发属性的getter,从而添加订阅者
    Dep.target = null;
    return value;
  }
}
 

实例化Watcher时,调用get()方法,经过Dep.target = watcherInstance标记订阅者是当前watcher实例,强行触发属性定义的getter方法,getter方法执行的时候,就会在属性订阅的dep添加当前的watcher实例,从而属性值变化的时候watcherInstance能接收到更新的通知。

 

四. 实现一个MVVM

function SelfVue(options) {
    var self = this;
    this.data = options.data;
    this.methods = options.methods;
    

    Object.keys(this.data).forEach(function(key) {   //定义数据监听时object能够操做,不可枚举
        self.proxyKeys(key);
    })

    observe(this.data);      //监听数据
    new Compile(options.el, this);   //解析模板
    options.mounted.call(this);    //全部事情处理好后执行mounted
    console.log(this.__proto__); 


}

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;
            }
        })
    }
}

MVVM是负责安排observer,watcher,compile的工做

1. observer实现对MVVM自身model数据劫持,监听数据的属性变动,并在变更时进行notify

2. compile实现指令解析,初始化视图,并订阅数据变化,绑定好更新函数

3. watcher一方面接收observer经过Dep传递过来的数据变化,一方面通知compile进行视图的更新

到此咱们的双向数据绑定就结束了。。。

相关文章
相关标签/搜索