上一篇的末尾,笔者简单介绍了Object.defineProperty
在数组监控方面的不足以及其替代品Proxy
。但是对于前者总感觉还少点什么,emmmmm...好像是demo,于是笔者精心准备了一下。所以本篇会主要会分成两大块:一是讲述如何弥补Object.defineProperty
先天不足的情况下实现对数组的精准监控,二是着重用Proxy
实现双向数据绑定。
Object.defineProperty
首先呢,对于Object.defineProperty
在数组监控方面的不足,我们不仅要知其然,更要知其所以然。因此先用栗子来证实下这个不足
。上代码:
let data = {
list: []
}
Object.keys(data).forEach(function (key) {
let value = data[key];
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
return value;
},
set(newValue) {
console.log(`Setting`);
value = newValue;
return true;
}
})
})
data.list.push(1); //---> A
// data.list = [1, 2, 3]; //---> B
console.log(data.list);
事先我们需要明确下:所以对数组的监控,就是能监测到数组元素的 增加或者删除。因此按照‘理论’,上述代码如果向list
中增加元素的时候,理应会有打印Setting
(笔者已经标记了最后的A
行 和B
行)
在只注释 B
行情况下看运行结果:
/usr/local/bin/node --inspect-brk=17809 demo.js
Debugger listening on ws://127.0.0.1:17809/48911764-8533-4c95-a501-20384c924f6a
Debugger attached.
Array(1) [1]
可以看出,虽然我们成功得向数组里增加了一个元素,但是并没有打印出 Setting
,因此就是说并没有监测到数组的变化.
在只注释 A
行情况下看运行结果:
/usr/local/bin/node --inspect-brk=27303 demo.js
Debugger listening on ws://127.0.0.1:27303/2fac4f06-e775-4485-b70b-b2660a98c2b8
Debugger attached.
Setting
Array(3) [1, 2, 3]
我们成功得向数组里增加了一个元素也没有打印出 Setting
,说明数组的变化被监测到了.源码
所以我们可以大胆得猜测:当监控 数组 数据对象的时候,实质上就是监控数组的 地址,地址不变也就不会被监测到,所以我们向list
里 push 元素的时候并没有触发打印;当我们直接替换list
对象的时候就触发了打印。所以这就是Object.defineProperty
在数组监控方面的不足。
下面就用例子介绍下如何弥补这方面的不足(当然,也是Vue的处理方式)
其核心思想就是
覆写
数组对象中的方法,在调用数组方法的同时能触发回调。下面是核心代码:
let arrayMethod = Object.create(Array.prototype);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) {
Object.defineProperty(arrayMethod, method, {
enumerable: true,
configurable: true,
value: function () {
let args = [...arguments]
Array.prototype[method].apply(this, args);
console.log(`operation: ${method}`)
dep.notify();
}
})
});
[Array Object].__proto__ = arrayMethod;
同时这样做的好处是,仅仅是数据中的数组对象的原型被修改掉了,并不会影响到全局的数组对象。
然后笔者也做了一个例子,如下:
通过向 list
中添加随机数或者删除随机数,能同步渲染到页面上,源码在这
Proxy
Proxy
意为代理
,通俗来说就是在 目标数据对象 外围设置一层 拦截。
举个形象的栗子:有一个仓库
,一开始大家可以任意得存放货物,后来老板请了一个仓库管家
,所有人想要存放货物的必须要经过管家的手。而且老板还给管家制定了一套管理标准
,管家对仓库的管理必须严格按照这套标准。
根据这个栗子,罗列了三个关键词:仓库
,仓库管家
,管理标准
,下面用一段简单的代码来表述下, 如果想全面得学习Proxy, 请参考阮一峰老师的博客:
let dataProxy = new Proxy(data, {
set() {
},
get() {
}
})
那么代码与栗子的对于关系就出来了:
仓库 --- data
仓库管家 --- Proxy / dataProxy
管理标准 --- {set(),get()}
而且可以注意到 Proxy 其实是个构造函数,所有我们对原数据对象的操作都得通过构造函数new
出来的对象 dataProxy
。就好像所有货物的存取都得通过管家一样
然后接下来笔者用 Proxy 写了一个栗子,用来 重现 上面对数组的监控,核心代码如下:
//数据源
let vm = {
list: [1, 2, 3, 4]
}
let vmProxy = new Proxy(vm.list, {
set(target, prop, value) {
console.log(`Setting: ${value}`);
Reflect.set(target, prop, value);
dep.notify();
return true;
}
})
我们可以注意到我们并没有直接将 vm
用Proxy进行“包装”,而是将vm.list
进行“包装”。因为笔者在学习的时候发现如果直接用 vm
的话,当我们向 list
中添加元素的时候并不会被监测到,笔者猜测的原因和 Object.defineProperty
一样,栗子呈上。如果有知道原因的小伙伴,请多多指教。
篇幅有限,这个例子的代码直接上链接了,有兴趣的小伙伴可以直接down下来看。
数组说完了,剩下就普通类型的数据就是按照正常套路走就对了。另外,笔者这次吸取上篇博客的教训,将代码写得更详尽,实现方式更贴近框架级别。用Proxy实现数据的双向绑定,栗子的功能和上篇博客是一样的,只是内容更加饱满。该栗子中笔者简单实现了一个简单版的类Vue的框架 View
,包含Html文档的解析、Watcher的构造、数据的绑定等,代码简单不复杂,所以这里就不赘述了,我觉得千言万语不敌一段代码,那么相关的代码就此呈上。
感谢小伙伴的捧场!我会坚持写博客,分享自己的见闻和工作中遇到的坑,共同学习,共同进步