从 0 5 开始造轮子 仿 vue 的 mvvm(二)

190 阅读2分钟

替换数据劫持对象

  上一篇实现了 mvvm 实现思路,可是不够优雅还有很多问题,我先解决这个问题数据劫持的问题。

之前的数据劫持

之前数据的劫持试是这么做的

// 重写data 的 get set  更改数据的时候,触发watch 更新视图
myVue.prototype._observer = function (obj) {
    var _this = this;
    for (key in obj){  // 遍历数据
        //订阅池
        // _this._watcherTpl.a = [];
        // _this._watcherTpl.b = [];
        _this._watcherTpl[key] = {
            _directives: []
        };
        let value = obj[key]; // 获取属`性值
        let watcherTpl = _this._watcherTpl[key]; // 数据的订阅池
        Object.defineProperty(_this._data, key, { // 数据劫持
            configurable: true,  // 可以删除
            enumerable: true, // 可以遍历
            get() {
                console.log(`${key}获取值:${value}`);
                return value; // 获取值的时候 直接返回
            },
            set(newVal) { // 改变值的时候 触发set
                console.log(`${key}更新:${newVal}`);
                if (value !== newVal) {
                    value = newVal;
                    //_this._watcherTpl.xxx.forEach(item)
                    //[{update:function(){}}]
                    watcherTpl._directives.forEach((item) => { // 遍历订阅池
                        item.update();
                        // 遍历所有订阅的地方(v-model+v-bind+{{}}) 触发this._compile()中发布的订阅Watcher 更新视图
                    });
                }
            }
        })
    };
};

这么做是可以实现可是,可以看到有这么一些缺点:

  • 对象必须是存在的。
  • 循环耗费性能。
  • 代码可读性可拓展性不是很好
  • 等等.. 那么我们能不能换一种方式去解决数据的劫持问题?

Proxy 横空出世

Proxy 是 ECMAScript 2015 的新特性,唯一的 缺点是 兼容性不是非常好。但我们要团结啊,哈哈哈。 废弃 IE。。。
下面我们将使用 Proxy 实现数据的劫持 和 代理。关于 Proxy 可以看这么两篇文章,一个是 阮一峰老师 的,不管阮一峰怎么样,当初竟然帮助过我们,我觉得就可以称之为老师 ,还有一篇 抱歉,学会 Proxy 真的可以为所欲为

// 重写data 的 get set  更改数据的时候,触发watch 更新视图
myVue.prototype._observer = function (obj) {
    const _this = this;
    this._data = new Proxy(obj, { // 数据劫持
        get(target, key, receiver) {
            return Reflect.get(target, key, receiver); // 获取值的时候 直接返回
        },
        set(target, key, newVal) { // 改变值的时候 触发set
            if (_this.value !== newVal) {
                _this.value = newVal;
                //先将数据更新完成后
                let res =  Reflect.set(target,key,newVal);
                _this._watcherTpl[key]._directives.forEach((item) => { // 遍历订阅池
                    item.update();
                });
                return res
            }
        }
    });
};

看到代码不用说了,量级的差距,简洁多了,这里直接将 VUE 的data 变成了一个 Proxy 对象。进行数据的操作。
既然这里更改了,那么我们之前的订阅池其实是废除了,因为没有循环了不存在 key:

   _this._watcherTpl[key] = {
            _directives: []
        };

所以我这里单独在_compile 处理了订阅池。

 const attrVal = node.getAttribute('v-model'); // 获取绑定的data
                _this.hasDirectives(attrVal);
//工具类判断是否有订阅池
myVue.prototype.hasDirectives = function (attr) {
    const _this = this;
    // 没有事件池 创建事件池
    if (!_this._watcherTpl[attr]) {
        _this._watcherTpl[attr] =  {};
        _this._watcherTpl[attr]._directives = [];
    } else {
        if (!_this._watcherTpl[attr]._directives) {
            _this._watcherTpl[attr]._directives = []
        }
    }
};

这样就解决了连接池的问题 ,这里的连接池使用的是数组,后面我们将会替换为map

结语

github完整实现

在线地址,需要翻墙