JavaScript 之数据劫持
每当收到候选人简历中有 Vue 的免不了提问 如何实现数据的双向绑定,往往很多人都是浅尝辄止,简单来说,就是通过以下步骤实现双向绑定:
- 监听器 Observer,用来劫持所有属性变动,如果有则通知订阅者
- 订阅者 Watcher,收到属性的变化通知并执行相应的函数,从而更新视图
所以 Vue 双向绑定关键的一环就是数据劫持(Vue 2.x 使用的是 Object.defineProperty(),而 Vue 在 3.x 版本之后改用 Proxy 进行实现),数据劫持即在访问或修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作如修改返回结果,以下专门梳理一下 JavaScript 的数据劫持。
Object.defineProperty
通过 Object.defineProperty() 来劫持对象属性的 setter 和 getter 操作,在数据变动时做你想要做的事情,示例代码如下:
Object.defineProperty(obj, key, { get() { // 相关操作,最终 return }, set(newVal) { // 一些 handle 操作 } })
Object.defineProperty 有它存在的问题,比如不能监听数组的变化,对 object 劫持必须遍历每个属性,可能存在深层次的嵌套遍历。那还有没有比 Object.defineProperty 更好的实现方式呢?
Proxy
在上一篇文章中已经有提及到 Proxy。
Proxy 的构造函数能够使用代理模式,即 let proxy = new Proxy(target, handler);
,Proxy 构造函数中的两个参数具体是:
- target 是用 Proxy 包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
- handler 是一个对象,其声明了代理 target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数
Proxy 其内部功能十分强大的,有13种数据劫持的操作,如get、set、has、ownKey(获取目标对象的所有 key)、deleteProperty等,下面主要梳理 set、get。
get
get 即在获取到某个对象属性值的时候做预处理的方法,其有两个参数:target、key,示例代码如下:
let Obj = {}; let proxyObj = new Proxy(Obj, { get: function(target, key) { // 如通过 target 判断某个属性是否符合预期 if (target[‘xx’] > 80) {return “该考生优秀!”} else if (!/^[0-9]+$/.test(key)) {return “学号格式不对!”} return Reflect.get(target, name, receiver); // Reflect 见文章最后的总结 } });
set
set 即用来拦截某个属性赋值操作的方法,可以接受四个参数:
- target: 目标值
- key:目标的 key
- value:当前需要做改变的值
- receiver:改变前的原始值
还是沿用上面的例子,比如一个考试成绩录入的校验,代码如下:
let validator = { set: function(target, key, value) { if (key === 'score') { if (!/^[0-9]+$/.test(value)) { throw new TypeError(‘分数必须为整数'); } if (value > 100) { throw new TypeError(‘成绩满分为 100'); } } // 对于满足条件的属性直接写入 target[key] = value; } }; let proxy = new Proxy(obj, handler); // ...
Proxy 相对 Object.defineProperty,它支持对数组的数据对象的劫持,不用像 VUE 那样要对数据劫持的话,需要进行重载 hack。对于以上提到的嵌套问题,Proxy 可以在 get 里面递归调用 Proxy 即返回下一个”代理“来完成递归嵌套,可以说 ES6 的 Proxy 就是对 Object.defineProperty 的改进和升级。关于嵌套举例如下:
let obj = { 0801080132: { name: ‘Jason', score: 99 }, // … }; let handler = { get (target, key, receiver) { // 如果属性值不为空或者是一个对象,则继续递归 if (typeof target[key] === 'object' && target[key] !== null) { return new Proxy(target[key], handler) } return xxx // 返回业务数据 } } let proxy = new Proxy(obj, handler)
Proxy 本质上就是在数据层外加上代理,通过这个代理实例访问里边的数据,这就是 Proxy 实现数据劫持的方式。总结一下:
- 在 ES6 支持,即是 Javascript 标准层面的方式
- 强大的功能支持,能够更方便支持业务定制,完全可以取代 Object.defineProperty
- ES6 Reflect 为了操作对象而提供的新 API,直接静态拥有 Proxy 的 13 种方法。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论