Vue 是一种基于 MVVM 模式的前端框架,其中的数据双向绑定
是其核心特性之一。双向绑定可以拆分成两部分:
- 视图变动反映在数据层。这部分很简单,可以通过事件监听器实现,本文不讲。
- 数据层的变化自动同步到视图层。这部分就是本文要讨论的内容,在 Vue3 是通过 Proxy 实现的。
本文通过一道面试题介绍如何通过 Proxy
监控对象属性的变化,并执行期望的动作。
一、题目要求
编写一个函数,输入是一个对象,其属性可以是基本类型,也可以是数组、嵌套对象,甚至是函数。例如:
const obj1 = {
name: '🐔',
age: 30,
like: ['唱', '跳', 'rap', '篮球'],
greet: function() { console.log('大家好!'); },
funs: {
name: '小黑子',
age: 18,
parent: {
name: 'parentName',
age: 40
}
}
};
输出一个新对象,每当该对象的任何属性发生变化时,都会在控制台打印出来。
二、实现思路
要实现这一功能,需要实现两个函数:
- 第一个是深拷贝函数,用来创建一个新对象。
- 第二个代理函数,利用 JavaScript 的
Proxy
对象拦截并自定义基本操作(例如属性查找、赋值、枚举、函数调用等),从而实现对对象的全面监控。
三、代码实现
以下是实现上述功能的完整代码(可以在控制台运行):
function createReactiveObject(obj) {
// 定义一个处理器 handler
const handler = {
get(target, property, receiver) {
const result = Reflect.get(target, property, receiver);
// 如果读取的属性值是对象,则返回该对象的代理
if (typeof result === 'object' && result !== null) {
return new Proxy(result, handler);
}
return result;
},
set(target, property, value, receiver) {
const oldValue = target[property];
const result = Reflect.set(target, property, value, receiver);
// 仅处理 push 操作作为 demo,如需兼容更多数组操作需要重新设计
if (Array.isArray(target) ) {
//push 操作会触发两次 set 操作,一次是改 length,一次是改数据。
if (property === 'length') {
console.log(`Array changed from ${JSON.stringify(target.slice(0, oldValue))} to ${JSON.stringify(target)}`);
} else if (oldValue !== value) {
//do nothing
}
} else if (oldValue !== value) {
console.log(`Property "${property}" changed from ${oldValue && oldValue.toString()} to ${value && value.toString()}`);
}
return result;
}
};
// 深度克隆对象,确保返回的新对象与原对象无直接引用关系
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Array) {
return obj.map(item => deepClone(item));
}
if (obj instanceof Function) {
return obj;
}
const newObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key]);
}
}
return newObj;
}
const clonedObj = deepClone(obj);
// 返回代理后的新对象
return new Proxy(clonedObj, handler);
}
// 测试代码
const obj1 = {
name: 'kunkun',
age: 18,
like: ['chang', 'tiao', 'rap', 'lanqiu'],
greet: function() { console.log('Hello!'); },
funs: {
name: 'funsName',
age: 10,
parent: {
name: 'parentName',
age: 40
}
}
};
const reactiveObj = createReactiveObject(obj1);
// 修改代理对象的属性,观察控制台输出变化信息
reactiveObj.name = 'kun'; // 控制台输出:Property "name" changed from "kunkun" to "kun"
reactiveObj.age = 20; // 控制台输出:Property "age" changed from 18 to 20
reactiveObj.like.push('yinyue'); // 控制台输出:Array changed from ["chang","tiao","rap","lanqiu"] to ["chang","tiao","rap","lanqiu","yinyue"]
reactiveObj.funs.name = 'newFunsName'; // 控制台输出:Property "name" changed from "funsName" to "newFunsName"
reactiveObj.funs.parent.age = 50; // 控制台输出:Property "age" changed from 40 to 50
reactiveObj.greet = function() { console.log('Hi!'); }; // 控制台输出:Property "greet" changed from function () { console.log('Hello!'); } to function () { console.log('Hi!'); }
四、结论
通过 Proxy
对象,可以方便地实现对 JavaScript 对象属性变化的监听。在 Vue3 中,我们只需要把上述代码里的 console.log
改为 Vue 的响应式更新逻辑,就可以实现数据层的变化自动同步到视图层了。