阅读 833

百度前端学院任务动态数据绑定(五)

觉得这个系列任务还是很有趣的,这是一种性能很差的实现,也许之后会尝试使用虚拟dom、改善其中的遍历。

任务信息


首先分析一下要干嘛:可以看出Vue是个构造函数;因为传入的对象可能有很多层对象,所以需要一个遍历传入对象的方法;双向绑定打算通过访问器属性实现、需要刷新dom,所以要有能刷新dom的方法、以及把传入Vue构造函数的对象中的值转换成Vue实例中的带有刷新dom回调的访问器的方法;最后还要一个初始化的方法,来保证没触发set时的初次访问的渲染;

//    Vue构造函数将会把入口(entry)和data实例化
    let Vue = function (obj) {
        this.entry = document.querySelector(obj.el);
        this.data = {};
        this.init(obj);
    };

//    初始化,遍历传入的对象转换成实例上的访问器,渲染一下页面
    Vue.prototype.init=function (obj) {
        this.walk(this.data, obj.data);
        this.render(this.data, this.entry);
    };复制代码

因为渲染时需要入口,所以把相应的入口实例化;把用来双向绑定的数据放到data里,这样语义化一点(之前任务要求);调用一下Vue原型对象上的init方法。通用的方法没必要实例化,放在原型对象上就好。

//    用来遍历输入的对象
    Vue.prototype.walk = function (output, input) {
        for (let key in input) {
            if (input.hasOwnProperty(key)) {
                if (typeof input[key] !== 'object' || input[key] === null) {
                    this.convert(output, key, input[key]);
                } else {
                    this.walk(output[key] = {}, input[key]);
                }
            }
        }
    };复制代码

遍历输入上的每一个属性,使用hasOwnProperty判断如果是它自己的属性进入下一步判断,用来过滤掉继承的属性。如果传入的属性不是对象,则直接把该属性转换成输出上的访问器属性,如果是对象的话,递归调用walk去深层遍历。

//    将输入转换成Vue实例上的访问器属性
    Vue.prototype.convert =
        function (ins, key, value) {
            let _value = value;
            let that = this;
            Object.defineProperty(ins, key, {
                configurable: true,
                enumerable: true,
                get: function () {
                    return _value;
                },
                set: function (newVal) {
                    if (newVal === null || typeof newVal !== 'object') {
                        _value = newVal;
                        that.render(that.data, that.entry);
                    } else {
                        delete ins[key];
                        that.walk(ins[key], newVal);
                        that.render(that.data, that.entry);
                    }
                }
            })
        };复制代码

转换方法convert就是把对应的键和值通过Object.defineProperty转换成实例对象上访问器属性,使用一个变量_value来保存get返回的值。其中配置属性(configurable)要设置为true(默认为false),这样需要需要重写同名属性的时候可以把它的值给删除。set里如果设置的值不是对象则直接改变闭包的_value,使用render方法来刷新页面。如果设置的值是对象的话要把现在的访问器给删除。使用walk来遍历调用convert来设置新设置的值,然后刷新页面。

//    用来渲染页面
    Vue.prototype.render = (function () {
        let domCache;
        return function (data, entry) {
            console.log('render...');
            domCache = domCache || entry.innerHTML;
            let domInnerHtml = domCache;
            let reg = /{{.*}}/g;
            let templateArr = [];
            let matchCache;
            let keyCache;
            let value;
            while (matchCache = reg.exec(domCache)) {
                templateArr.push(matchCache[0]);
            }
            templateArr.forEach(item => {
                keyCache = item.slice(2, -2).split('.');
                value = this.find(keyCache, data);
                if (value !== undefined && (typeof value !== 'object' || value === null)) {
                    reg = new RegExp('{{' + keyCache.join('.') + '}}', 'g');
                    domInnerHtml = domInnerHtml.replace(reg, value);
                }
            });
            entry.innerHTML = domInnerHtml;
        }
    }());复制代码
//    查询某个属性在data中的值
    Vue.prototype.find = function (key, data) {
        for (let i = 0, len = key.length; i < len; i++) {
            if (data.hasOwnProperty(key[i])) {
                data = data[key[i]];
            } else {
                return undefined;
            }
        }
        return data;
    };复制代码

使用一个闭包保存一个单例的domCache来保存dom缓存,防止{{}}被替换后,找不到双向绑定的位置。不过也有个问题就是不能动态地改变双向绑定的dom,不过我发现Vue也不了就释然了……创建一个dom缓存的副本,避免直接修改缓存,使用正则找到双花括号的位置,用正则的exec方法产生的数组的第一个元素就是匹配到的元素,所有的匹配保存到一个数组里。对匹配到的元素数组遍历。遍历中用slice返回一个切掉{{和}}的副本再以.分割,得到每一层属性名组成的数组,通过find方法用这个数组对data迭代判断、操作得到最终的值,创建正则,把dom缓存副本中属性替换成对应的值。遍历完后把修改过的dom缓存副本赋值给真实的dom,完成渲染。


预览
源码
求star、赞、关注😊

关注下面的标签,发现更多相似文章
评论