替换数据劫持对象
上一篇实现了 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