vue的数据双向绑定

vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的:

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

2.实现一个订阅者Watcher,每一个Watcher都绑定一个更新函数,watcher可以收到属性的变化通知并执行相应的函数,从而更新视图;

3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher);

a、Observer实现对MVVM自身model数据劫持,监听数据的属性变更,并在变动时进行notify;
b、Compile实现指令解析,初始化视图,并订阅数据变化,绑定好更新函数;
c、Watcher一方面接收Observer通过dep传递过来的数据变化,一方面通知Compile进行view update;
 
先初始化一个监听器Observer,用于监听该对象data属性的值。
然后初始化一个解析器Compile,绑定这个节点,并解析其中的v-," {{}} "指令,(每一个指令对应一个Watcher)并初始化模板数据以及初始化相应的订阅者,并把订阅者添加到订阅器中(Dep)。这样就实现双向绑定了。
 
整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁

data如何更新view

使用observer(观察者)实现数据劫持,绑定watcher(订阅者)的更新函数,watcher通知compile(模板编译)更新虚拟dom,最终挂载真实dom上

view如何更新data

使用事件监听函数绑定事件,在事件中改变值触发observer的set,再触发watcher
 

实现observer

function defineReactive(data, key, val) {
    observe(val); // 递归遍历所有子属性
    var dep = new Dep(); 
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function() {
            if (是否需要添加订阅者) {
                dep.addSub(watcher); // 在这里添加一个订阅者
            }
            return val;
        },
        set: function(newVal) {
            if (val === newVal) {
                return;
            }
            val = newVal;
            console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
            dep.notify(); // 如果数据变化,通知所有订阅者
        }
    });
}
 
function Dep () {
    this.subs = [];
}
Dep.prototype = {
    addSub: function(sub) {
        this.subs.push(sub);
    },
    notify: function() {
        this.subs.forEach(function(sub) {
            sub.update();
        });
    }
};

实现watcher

function Watcher(vm, exp, cb) {  // vm为当前dom实例,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;
    }
};

实现compile

作用:

1 解析模板指令,并替换模板数据,初始化视图;

2 将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器;

首先需要解析的dom节点存入fragment片段里再进行处理:

function compileElement (el) {
    var childNodes = el.childNodes;
    var self = this;
    [].slice.call(childNodes).forEach(function(node) {
        var reg = /{{(.*)}}/;
        var text = node.textContent;
 
        if (self.isTextNode(node) && reg.test(text)) {  // 判断是否是符合这种形式{{}}的指令
            self.compileText(node, reg.exec(text)[1]);
        }
 
        if (node.childNodes && node.childNodes.length) {
            self.compileElement(node);  // 继续递归遍历子节点
        }
    });
},
function compileText (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);
    });
},
function (node, value) {
    node.textContent = typeof value == 'undefined' ? '' : value;
}

原文: https://www.jianshu.com/p/f194619f6f26

原文地址:https://www.cnblogs.com/xjy20170907/p/12801187.html