Vue 的数据响应式原理是怎么实现的

Vue 数据响应式原理

什么是数据响应式

数据响应式即数据双向绑定,将Model绑定到view, 当我们用 Javascript代码更新Model时,View就会自动更新;如果用户更新了View, Model也会自动更新。

数据响应式原理

数据响应式实现

  1. 根据订阅发布者模式实现一个整体架构,包括 MVVM 类,Vue类, Watcher类。
    //发布者
    class Vue {
        constructor (options) {
            this.options = options;
            this.$data = options.data; //获取数据
            this.$el = document.querySelector(options.el); //获取挂载对象
        }
        //劫持监听所有属性
        Observer () {

        }
        //解析指令
        Compile () {

        }
    }
  1. 实现 MVVM 中的由 M 到 V,将模型中的数据绑定到视图。
    //发布者
    class Vue {
        constructor (options) {
            this.options = options;
            this.$data = options.data; //获取数据
            this.$el = document.querySelector(options.el); //获取挂载对象
            this._directive = {}; //创建容器存放订阅者
            this.Observer(this.$data);
            this.Compile(this.$el);
        }
        //劫持监听所有属性
        Observer (data) {
            for (let key in data) {
                this._directive[key] = [];
            }
        }
        //解析指令
        Compile (el) {
            let Vnodes = el.children;
            for (Vnode of Vnodes) {
                //递归查找子元素
                if (Vnode.children.length) {
                    this.Compile(Vnode);
                }
                //判断是否含有对应的属性,做出相应的处理
                if (Vnode.hasAttribute('v-text')) {
                    let attrVal = node.getAttribute('v-text');
                    //向订阅者容器中添加对应的订阅者
                    this._directive[attrVal].push(new Watcher(node, this, attrVal, 'innerHTML'));
                }
                if (Vnode.hasAttribute('v-model')) {
                    let attrVal = node.getAttribute('v-model');
                    this._directive[attrVal].push(new Watcher(node, this, attrVal, 'value'));
                }
            }
        }
        //订阅者,更新视图
        class Watcher {
            constructor (el, vm, exp, attr) {
                this.el = el;
                this.vm = vm;
                this.exp = exp;
                this.attr = attr;
                this.uppublished_at(); //初始化视图
            }
            uppublished_at () {
                this.el[this.attr] = this.vm.$data[this.exp];
            }
        }
    }
  1. 最后实现 V 到 M,当视图变化的时候,触发更新模型中的数据。
    //发布者
    class Vue {
        constructor (options) {
            this.options = options;
            this.$data = options.data; //获取数据
            this.$el = document.querySelector(options.el); //获取挂载对象
            this._directive = {}; //创建容器存放订阅者
            this.Observer(this.$data);
            this.Compile(this.$el);
        }
        //劫持监听所有属性
        Observer (data) {
            for (let key in data) {
                this._directive[key] = [];
            }
        }
        //解析指令
        Compile (el) {
            let Vnodes = el.children;
            for (Vnode of Vnodes) {
                //递归查找子元素
                if (Vnode.children.length) {
                    this.Compile(Vnode);
                }
                //判断是否含有对应的属性,做出相应的处理
                if (Vnode.hasAttribute('v-text')) {
                    let attrVal = node.getAttribute('v-text');
                    //向订阅者容器中添加对应的订阅者
                    this._directive[attrVal].push(new Watcher(node, this, attrVal, 'innerHTML'));
                }
                if (Vnode.hasAttribute('v-model')) {
                    let attrVal = node.getAttribute('v-model');
                    this._directive[attrVal].push(new Watcher(node, this, attrVal, 'value'));
                    node.addEventListener('input', (function () {
                        return function () {
                            this.$data[attrVal] = node.value;   //这里更新了 ~
                        }
                    })().bind(this)); //bind不会立即执行
                }
            }
        }
        //订阅者,更新视图
        class Watcher {
            constructor (el, vm, exp, attr) {
                this.el = el;
                this.vm = vm;
                this.exp = exp;
                this.attr = attr;
                this.uppublished_at(); //初始化视图
            }
            uppublished_at () {
                this.el[this.attr] = this.vm.$data[this.exp];
            }
        }
    }
  1. 同时更新相应视图。
    //发布者
    class Vue {
        constructor (options) {
            this.options = options;
            this.$data = options.data; //获取数据
            this.$el = document.querySelector(options.el); //获取挂载对象
            this._directive = {}; //创建容器存放订阅者
            this.Observer(this.$data);
            this.Compile(this.$el);
        }
        //劫持监听所有属性
        Observer (data) {
            for (let key in data) {
                this._directive[key] = [];
                let val = data[key];
                let watch = this._directive[key];
                //这里是核心 !!!
                Object.defineProperty(this.$data, key, {
                    get: function () {
                        return val;
                    },
                    set: function (newVal) {
                        if (newVal !== val) {
                            val = newVal;
                            watch.forEach(ele => {  //ele为订阅者实例对象
                                ele.uppublished_at();
                            })
                        }
                    }
                })
            }
        }
        //解析指令
        Compile (el) {
            let Vnodes = el.children;
            for (Vnode of Vnodes) {
                //递归查找子元素
                if (Vnode.children.length) {
                    this.Compile(Vnode);
                }
                //判断是否含有对应的属性,做出相应的处理
                if (Vnode.hasAttribute('v-text')) {
                    let attrVal = node.getAttribute('v-text');
                    //向订阅者容器中添加对应的订阅者
                    this._directive[attrVal].push(new Watcher(node, this, attrVal, 'innerHTML'));
                }
                if (Vnode.hasAttribute('v-model')) {
                    let attrVal = node.getAttribute('v-model');
                    this._directive[attrVal].push(new Watcher(node, this, attrVal, 'value'));
                    node.addEventListener('input', (function () {
                        return function () {
                            this.$data[attrVal] = node.value;
                        }
                    })().bind(this)); //bind不会立即执行
                }
            }
        }
        //订阅者,更新视图
        class Watcher {
            constructor (el, vm, exp, attr) {
                this.el = el;
                this.vm = vm;
                this.exp = exp;
                this.attr = attr;
                this.uppublished_at(); //初始化视图
            }
            uppublished_at () {
                this.el[this.attr] = this.vm.$data[this.exp];
            }
        }
    }