Proxy 与 Reflect 的常见使用场景
首先对代理与反射还不熟悉的朋友可以看看我的上一篇博客详解 JS 中的 Proxy(代理)和 Reflect(反射)。
这篇博客只讲 Proxy 的应用场景,分别有以下三种:
- 数据绑定与观察者模式
- 函数参数的验证
- 简便的构造函数
1.数据绑定与观察者模式
实现数据绑定与观察者模式,我们也可以使用 ES5 新增的Object.defineProperty()方法
,Vue2 就是使用该技术实现的,而 Vue3 为什么换用 Pro 需要来实现呢?
所以现在我们先来对比一下这两种实现方法。
1.1 Object.defineProperty()方法实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script>
//创建一个观察者 target为被观察的目标
function observer(target) {
const div = document.getElementById("container");
const ob = {
}; //创建一个观察对象
const props = Object.keys(target);
for (const prop of props) {
//给ob新增属性
Object.defineProperty(ob, prop, {
get() {
return target[prop];
},
set(val) {
target[prop] = val;
render();
},
enumerable: true,
});
}
render();
//渲染函数,将ob的属性与属性值渲染到视图上
function render() {
let html = "";
for (const prop of Object.keys(ob)) {
html += `<p><span>${
prop}:</span><span>${
ob[prop]}</span></p>`;
}
div.innerHTML = html;
}
return ob;
}
const target = {
a: 1,
b: 2,
};
const obj = observer(target);
</script>
</body>
</html>
1.2 Proxy 与 Reflect 方法实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script>
//创建一个观察者
function observer(target) {
const div = document.getElementById("container");
//为被观察的目标 创建一个代理对象
const proxy = new Proxy(target, {
set(target, prop, value) {
Reflect.set(target, prop, value);
render();
},
get(target, prop) {
return Reflect.get(target, prop);
},
});
render();
//渲染函数
function render() {
let html = "";
for (const prop of Object.keys(target)) {
html += `<p><span>${
prop}:</span><span>${
target[prop]}</span></p>`;
}
div.innerHTML = html;
}
return proxy;
}
const target = {
a: 1,
b: 2,
};
const obj = observer(target);
</script>
</body>
</html>
1.3 两种实现方法的对比
我认为 Proxy 与 Reflect 主要有以下优点:
- 直观角度上来看,Proxy 与 Reflect 方法的代码量更少,而且直接调用了对应的 API,可阅读性也更强,更容易看懂代码。
- Proxy 与 Reflect 方法不用去新增一个 ob 对象,而是只需要创建一个代理对象,这样就不会占用内存,会减少内存消耗。
- 最后也是最重要的一点,Proxy 与 Reflect 方法可监听目标新增的属性以及目标删除的属性,而 Object.defineProperty()方法无法实现动态监听,所以在 Vue2 中我们需要使用 s e t 以及 set以及 set以及delete 方法来进行响应式数据的增加与删除。
2.函数参数的验证
由于 Javascript 是一种动态类型语言,不能像 C 语言与 Java 一样限定数据的类型,平时我们在进行函数传参是偶尔会遇到一定的麻烦,所以我们可使用 Proxy 对象的 apply 捕获器实现函数参数的验证,对参数进行审查,不符合条件的参数,给予对应的提示。
如当我们需要给两数相加 sum()函数限定两个参数 a,b 都为 Number 类型。
//封装的 函数参数验证 方法
function validatorFunction(func, ...types) {
const proxy = new Proxy(func, {
//调用目标函数时触发该捕获器
apply(target, thisArgument, argumentsList) {
//thisArg:调用函数时的 this 参数。
//argumentsList:调用函数时的参数列表
//types为传入的参数数据类型数组 当前为["number", "number"]
types.forEach((t, i) => {
if (typeof argumentsList[i] !== t) {
throw new TypeError(
`第${
i + 1}个参数${
argumentsList[i]}不满足类型${
t}`
);
}
});
return Reflect.apply(target, thisArgument, argumentsList);
},
});
return proxy;
}
//简单的两数相加函数
function sum(a, b) {
return a + b;
}
const sumProxy = validatorFunction(sum, "number", "number");
console.log(sumProxy(1, 2));
3.简便的构造函数
因为我们平时使用 Class 运算符构建一个类时,需要写大量的重复代码,使用我们可以使用 Proxy 对象的 construct 捕获器实现代码简化,并且还可以对属性传值进行一定的限制。
如当我们创建一个简单的 User 类,共有 name、sex、age 这三个属性。
//封装的 简便构造函数 方法
function ConstructorProxy(Class, ...propNames) {
return new Proxy(Class, {
//在 new 操作符中调用该捕获器
construct(target, argumentsList, newTarget) {
//target:目标构造函数
//argumentsList:传给目标构造函数的参数列表。
//newTarget:最初被调用的构造函数。
const obj = Reflect.construct(target, argumentsList);
//propNames 为创建代理对象时,传入的构造函数的属性列表 目前为["name", "sex", "age"]
propNames.forEach((name, i) => {
obj[name] = argumentsList[i];
});
return obj;
},
});
}
//创建User类
class User {
}
const UserProxy = ConstructorProxy(User, "name", "sex", "age"); //传入属性名
const obj = new UserProxy("xx", "男", 18); //创建实例,并传入属性值
console.log(obj);
//创建Phone类
class Phone {
} //User {name: 'xx', sex: '男', age: 18}
const ProductProxy = ConstructorProxy(Phone, "brand", "price", "inventory");
const phone1 = new ProductProxy("华为", 4000, 100);
console.log(phone1); //Phone {brand: '华为', price: 4000, inventory: 100}
结语
这是我目前所了解的知识面中最好的解答,当然也有可能存在一定的误区。
所以如果对本文存在疑惑,可以在评论区留言,我会及时回复的,欢迎大家指出文中的错误观点。
最后码字不易,觉得有帮助的朋友点赞、收藏、关注走一波。