• Vue之九数据劫持实现MVVM的数据双向绑定


    vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的。

    如果不熟悉defineProperty,猛戳这里

     整理了一下,要实现mvvm的双向绑定,就必须要实现以下几点:
    1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
    2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
    3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
    4、mvvm入口函数,整合以上三者

    1、实现Observer

    我们知道可以利用Obeject.defineProperty()来监听属性变动
    那么将需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 settergetter
    这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。。相关代码可以是这样:

    var data = {name: 'kindeng'};
    observe(data);
    data.name = 'dmq'; // 监听到值变化了 kindeng --> dmq
    
    function observe(data) {
        if (!data || typeof data !== 'object') {
            return;
        }
        // 取出所有属性遍历
        Object.keys(data).forEach(function(key) {
            defineReactive(data, key, data[key]);
        });
    };
    
    function defineReactive(data, key, val) {
        observe(val); // 监听子属性
        Object.defineProperty(data, key, {
            enumerable: true, // 可枚举
            configurable: false, // 不能再define
            get: function() {
                return val;
            },
            set: function(newValue) {
                console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newValue);
                val = newValue;
            }
        });
    }
    

      上面的思路整理中我们已经明确订阅者应该是Watcher, 而且var dep = new Dep();是在 defineReactive方法内部定义的,所以想通过dep添加订阅者

    完整方法	
    function Observer(data) { this.data = data; this.walk(data); } Observer.prototype = { walk: function(data) { var me = this; Object.keys(data).forEach(function(key) { me.convert(key, data[key]); }); }, convert: function(key, val) { this.defineReactive(this.data, key, val); }, defineReactive: function(data, key, val) { var dep = new Dep(); var childObj = observe(val); Object.defineProperty(data, key, { enumerable: true, // 可枚举 configurable: false, // 不能再define get: function() { if(Dep.target) { dep.depend(); } return val; }, set: function(newVal) { if(newVal === val) { return; } val = newVal; // 新的值是object的话,进行监听 childObj = observe(newVal); // 通知订阅者 dep.notify(); } }); } }; function observe(value, vm) { if(!value || typeof value !== 'object') { return; } return new Observer(value); }; var uid = 0; function Dep() { this.id = uid++; this.subs = []; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); console.log(this.subs) }, depend: function() { Dep.target.addDep(this); }, removeSub: function(sub) { var index = this.subs.indexOf(sub); if(index != -1) { this.subs.splice(index, 1); } }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }); } }; Dep.target = null;

      这里已经实现了一个Observer了,已经具备了监听数据和数据变化通知订阅者的功能,那么接下来就是实现Compile了

    2、实现Compile

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

     因为遍历解析的过程有多次操作dom节点,为提高性能和效率,会先将跟节点el转换成文档碎片fragment进行解析编译操作,解析完成,再将fragment添加回原来的真实dom节点中

    3、实现Watcher

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

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

    4、实现MVVM

    MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

    知。

    function MVVM(options) {
        this.$options = options;
        var data = this._data = this.$options.data, me = this;
        // 属性代理,实现 vm.xxx -> vm._data.xxx
        Object.keys(data).forEach(function(key) {
            me._proxy(key);
        });
        observe(data, this);
        this.$compile = new Compile(options.el || document.body, this)
    }
    
    MVVM.prototype = {
        _proxy: function(key) {
            var me = this;
            Object.defineProperty(me, key, {
                configurable: false,
                enumerable: true,
                get: function proxyGetter() {
                    return me._data[key];
                },
                set: function proxySetter(newVal) {
                    me._data[key] = newVal;
                }
            });
        }
    };
    

      这里主要还是利用了Object.defineProperty()这个方法来劫持了vm实例对象的属性的读写权,使读写vm实例的属性转成读写了vm._data的属性值

  • 相关阅读:
    java环境变量的搭建,《ecpilse,jdk》的下载
    Android Studio的下载和环境变量的配置
    微信小程序开发者工具包(官方版)
    Activity详解生命周期(Android)
    Android环境变量配置
    Android基础测试题(四)
    Android基础测试题(二)
    手机上实现细线的效果
    前端面试题
    js判断当前浏览器是pc端还是移动端
  • 原文地址:https://www.cnblogs.com/yiyi17/p/8445259.html
Copyright © 2020-2023  润新知