• 模拟源码深入理解Vue数据驱动原理(1)


      Vue有一核心就是数据驱动(Data Driven),允许我们采用简洁的模板语法来声明式的将数据渲染进DOM,且数据与DOM是绑定在一起的,这样当我们改变Vue实例的数据时,对应的DOM元素也就会改变了。

      通过粗浅地走读Vue的源码,发现达到这一效果的核心思路其实就是利用ES5的defineProperty方法,监听data数据,如果数据改变,那么就对页面做相关操作。

      有了大体思路,那么我们就开始一步一步实现一个简易版的Vue数据驱动吧,简称SimpleVue。

      Vue实例的创建过程,如下:

    var vm = new Vue({
        el: '#test',
        data: {
            name: 'Monkey'
        }
    });

      因此,我们也依瓢画葫芦,构建SimpleVue构造函数如下

    function SimpleVue(obj){
        this.$el = document.querySelector(obj.el);
        this.$options = obj;
        this._data = Object.create(null);
        //入口
        this.init();
        obj = null;
    };
    SimpleVue.prototype = {
        constructor: SimpleVue,
        init: function(){
            //TODO    
        }
    };

      接下来,我们在SimpleVue原型上编写一个watchData方法,通过利用ES5原生的defineProperty方法,监听data中的属性,如果属性值改变,那么我们就进行相关的页面处理。

    SimpleVue.prototype = {
        //监听data属性
        watchData: function(){
            var data = this.$options.data,//得到data对象
                keys = Object.keys(data),//data对象上全部的自身属性,返回数组
                that = this;
            keys.forEach(function(elem){//监听每个属性
                Object.defineProperty(that, elem, {
                    enumerable: true,
                    configurable: true,
                    get: function(){
                        return that._data[elem];
                    },
                    set: function(newVal){
                        that._data[elem] = newVal;
                        that.update();//数据变化,更新页面
                    }
                });
                that[elem] = data[elem];//初次进入改变that[elem],从而触发update方法
            });
        }
    };

      好了,如果我们检测到数据变化了呢?那么,我们就更新视图嘛。但是,怎么更新呢?简单的实现方式就是,在初次构建SimpleVue实例时,就将页面中的模板保存下来,每次实例数据一改变,就通过正则替换掉原始的模板,即双括号中的变量,如下:

    SimpleVue.prototype = {
        //初始化SimpleVue实例时,就将原始模板保留
        getTemplate: function(){
            this.template = this.$el.innerHTML;    
        },
        //数据改变更新视图
        update: function(){
            var that = this,
                template = that.template,
                reg = /(.*?){{(w*)}}/g,
                result = '';
            result = template.replace(reg, function(rs, $1, $2){
                var val = that[$2] || '';
                return $1 + val;
            });
            this.$el.innerHTML = result;
            console.log('updated');
        }
    };

      上述实现效果,还不错哦。但是,我们走读下上述代码,感觉还可以优化下:

      (1)在watchData方法中监听每个data属性时,如果我们设置相同值,页面也会更新的,因为set是监听赋值的,它又不知道是不是同一个值,因此,优化如下:

    keys.forEach(function(elem){//监听每个属性
        Object.defineProperty(that, elem, {
            enumerable: true,
            configurable: true,
            get: function(){
                return that._data[elem];
            },
            set: function(newVal){
                var oldVal = that[elem];
                if(oldVal == newVal){
                    return;
                }
                that._data[elem] = newVal;
                that.update();//数据变化,更新页面
            }
        });
        that[elem] = data[elem];
    });

      (2)在上述基础,我们加入了新旧值判断,但是如果我们频繁更新data属性呢?那么也就会频繁调用update方法。例如,当我们给vm.name同时赋值两个值时,页面就会更新两次。

      怎么解决呢?利用节流,即可:

    SimpleVue.throttle = function(method, context, delay){
        clearTimeout(method.tId);
        method.tId = setTimeout(function(){
            method.call(context);
        }, delay);
    };

      完整代码:

    function SimpleVue(obj){
        this.$el = document.querySelector(obj.el);
        this.$options = obj;
        this._data = Object.create(null);
        this.init();
        obj = null;
    };
    SimpleVue.throttle = function(method, context, delay){
        clearTimeout(method.tId);
        method.tId = setTimeout(function(){
            method.call(context);
        }, delay);
    };
    SimpleVue.prototype = {
        constructor: SimpleVue,
        init: function(){
            this.getTemplate();
            this.watchData();
        },
        getTemplate: function(){
            this.template = this.$el.innerHTML;    
        },
        watchData: function(){
            var data = this.$options.data,
                keys = Object.keys(data),
                that = this;
            keys.forEach(function(elem){
                Object.defineProperty(that, elem, {
                    enumerable: true,
                    configurable: true,
                    get: function(){
                        return that._data[elem];
                    },
                    set: function(newVal){
                        var oldVal = that[elem];
                        if(oldVal === newVal){
                            return;
                        }
                        that._data[elem] = newVal;
                        SimpleVue.throttle(that.update, that, 50);
                    }
                });
                that[elem] = data[elem];
            });
        },
        update: function(){
            var that = this,
                template = that.template,
                reg = /(.*?){{(w*)}}/g,
                result = '';
            result = template.replace(reg, function(rs, $1, $2){
                var val = that[$2] || '';
                return $1 + val;
            });
            this.$el.innerHTML = result;
            console.log('updated');
        }
    };

      好了,简单的数据驱动,我们算 实现了,也优化了,但,其实上述简易版Vue有很多问题,例如:

      1)、监听的属性是个对象呢?且对象里又有其他属性,不就监听不成功了么?

      2)通过上述1)介绍,如果监听的属性是个对象,那么又该如何渲染DOM呢?

      3)渲染DOM我们采用的是innerHTML,那么随着DOM的扩大,性能显而易见,又该如何解决?

  • 相关阅读:
    无规矩不成方圆,聊一聊 Spring Boot 中 RESTful 接口设计规范
    一次SQL查询优化原理分析(900W+数据,从17s到300ms)
    重磅!GitHub官方开源新命令行工具
    JVM调优的反思与总结
    SpringMVC 进阶版
    《四大点,搞懂Redis到底快在哪里?》
    《Docker基础与实战,看这一篇就够了》
    带你从头到尾捋一遍MySQL索引结构
    MySQL信息提示不是英文问题
    完美解决windows+ngnix+phpcgi自动退出的问题
  • 原文地址:https://www.cnblogs.com/goloving/p/8672216.html
Copyright © 2020-2023  润新知