🖥? NodeJS专栏:Node.js从入门到精通 🖥? 蓝桥杯真题解析:蓝桥杯Web国赛真题解析 🧧 加入社区领红包:海底烧烤店ai(从前端到全栈) 🧑?💼个人简介:即将大三的学生,一个不甘平庸的平凡人🍬 👉 你的一键三连是我更新的最大动力??!
前言
在ES6 之前,我们常使用Object.defineProperty() 方法来进行数据代理从而实现数据的劫持(如:Vue2的响应式原理)
而在ES6 之后诞生了一个全新的对象(构造器):Proxy ,作为数据代理而言,它比Object.defineProperty() 要强大许多,这也是为什么Vue3 的响应式要使用Proxy 来做的原因
这篇文章将深入去研究Proxy 代理,让我们开始吧!
一、为什么要使用代理?
之所以使用代理,就是不希望用户能够直接访问某个对象,直接操作对象的某个成员(因为这样是不可控的,我们不知道用户在访问操作哪一个对象)
通过代理,我们可以拦截用户的访问(称为数据劫持),拦截住后我们就可以对数据进行一些处理,比如做一些数据的验证或者像Vue一样做一些视图更新的额外操作,之后再允许用户的访问操作(因为我们拦截了用户的每一次访问,这样用户操作对象就完全是在我们可控的范围内)
简单来说,就是我们希望用户在访问对象时我们能够清除的知道用户在访问什么并且能够在中间做一些我们自己的操作
二、Proxy是什么?
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
Proxy 是 ES6 中新增的一个构造函数,也可以叫类,通过new 操作符调用使用。 但在JavaScript 中函数和类本质上也是对象,所以我们也能将Proxy 直接作为对象访问它的属性进行操作,如Proxy.revocable()
但需要注意的是,Proxy 并没有prototype 原型对象:
console.log(Proxy.prototype);
ECMA 的官方说明: 因为 Proxy 构造出来的实例对象仅仅是对目标对象的一个代理,所以 Proxy 在构造过程中是不需要 prototype 进行初始化的
其他构造函数之所以需要 prototype ,是因为构造出来的对象需要一些初始化的成员,所以将这些成员定义到了 protoype 上
三、基础语法
const proxyTarget = new Proxy(target, handler)
👉 参数:
target :Proxy 会对 target 对象进行包装。它可以是任何类型的对象,包括内置的数组,函数甚至是另一个代理对象。handler :它是一个对象,它的属性提供了某些操作发生时所对应的处理函数。
一个空的 handler 参数将会创建一个与被代理对象行为几乎完全相同的代理对象。通过在 handler 对象上定义一组处理函数,你可以自定义被代理对象的一些特定行为。例如, 通过定义 get() 你就可以自定义被代理对象的 属性访问器。
👉 返回值:
proxyTarget :经过Proxy包装后的target对象
👉 基础使用:
const obj = {
name: 'Ailjx'
}
const proxyTarget = new Proxy(obj, {})
console.log(proxyTarget);
需要注意的是,返回值proxyTarget 并不是target 的深拷贝,而只是浅引用:
const obj = {
name: 'Ailjx'
}
const newObj = new Proxy(obj, {})
obj.name = 9
console.log(newObj.name);
const obj = {
name: 'Ailjx'
}
const newObj = new Proxy(obj, {})
newObj.name = 9
console.log(obj.name);
四、handler处理函数
Proxy 代理的灵魂就在于它的第二个参数:handler 对象,在这个对象内我们可以定义一些处理函数来进行数据劫持,从而实现一些额外的操作
🎉 apply() 拦截函数的调用
handler.apply() 方法用于拦截函数的调用
👉 语法:
var ProxyTarget = new Proxy(target, {
apply: function(target, thisArg, argumentsList) {}
});
👉 参数:
下面的参数将会传递给 apply() 方法,this 绑定在 handler 上
target :目标对象(函数)thisArg :被调用时的上下文对象argumentsList :被调用时的参数数组
👉 返回值:
apply 方法可以返回任何值
👉 使用:
function sum(a, b) {
return a + b;
}
const handler = {
apply: function (target, thisArg, argumentsList) {
console.log(`你调用了函数!`);
return target(argumentsList[0], argumentsList[1]) * 10;
},
};
const newSum = new Proxy(sum, handler);
console.log(sum(1, 2));
console.log(newSum(1, 2));
🎉 construct() 拦截 new 操作符
handler.construct() 方法用于拦截 new 操作符。为了使 new 操作符在生成的 Proxy 对象上生效,用于初始化代理的目标对象自身必须具有 [[Construct]] 内部方法(即 new target 必须是有效的)
👉 语法:
var ProxyTarget = new Proxy(target, {
construct: function(target, argumentsList, newTarget) {}
});
👉 参数:
下面的参数将会传递给 construct 方法,this 绑定在 handler 上
👉 返回值:
construct 方法必须返回一个对象
👉 使用:
function MyName(name) {
this.name = name;
}
const handler = {
construct(target, args, newTarget) {
console.log(`你使用了new操作符!`, newTarget);
return new target(...args);
},
};
const ProxyMyName = new Proxy(MyName, handler);
console.log(new ProxyMyName("Ailjx").name);
🎉 get() 拦截对象属性的读取操作
handler.get() 方法用于拦截对象的读取属性操作
👉 语法:
var proxyTarget = new Proxy(target, {
get: function(target, property, receiver) {}
});
👉 参数:
以下是传递给 get 方法的参数,this 上下文绑定在handler 对象上
👉 返回值:
get 方法可以返回任何值,这些返回值就是用户真正获取到的属性值
👉 使用:
const obj = {
name: "Ailjx",
};
var p = new Proxy(obj, {
get: function (target, property, receiver) {
console.log("你访问的属性为:", property);
console.log(receiver);
return "My name is " + target.name;
},
});
console.log(p.name);
🎉 set() 拦截对象属性的修改/设置操作
handler.set() 方法是设置属性值操作的捕获器
👉 语法:
const p = new Proxy(target, {
set: function(target, property, value, receiver) {}
});
👉 参数:
下面的参数将会传递给 set() 方法,this 绑定在 handler 上
👉 返回值:
set() 方法应当返回一个布尔值
- 返回
true 代表属性设置成功 - 在严格模式下,如果
set() 方法返回 false ,那么会抛出一个 TypeError 异常
👉 使用:
const obj = {
name: "Ailjx",
};
const handler = {
set(target, property, value) {
if (property === "name" && typeof value !== "string") {
console.log("姓名必须是字符串!");
} else {
target.name = value;
return true;
}
},
};
const proxy1 = new Proxy(obj, handler);
proxy1.name = 1;
console.log(proxy1.name);
proxy1.name = "Chen";
console.log(proxy1.name);
🎉 deleteProperty()拦截对象属性的删除操作
handler.deleteProperty() 方法用于拦截对对象属性的删除操作
👉 语法:
var p = new Proxy(target, {
deleteProperty: function(target, property) {}
});
👉 参数:
deleteProperty 方法将会接受以下参数。this 被绑定在 handler 上
-
target :目标对象 -
property :待删除的属性名。
👉 返回值:
deleteProperty 必须返回一个 Boolean 值,表示了该属性是否被成功删除
👉 使用:
const obj = {
name: "Ailjx",
};
var p = new Proxy(obj, {
deleteProperty: function (target, prop) {
delete target[prop];
console.log("你删除了" + prop + "属性");
return true;
},
});
console.log(p.name);
delete p.name;
console.log(p.name);
🎉 has() 拦截 in 操作符
handler.has() 方法是针对 in 操作符的代理方法
👉 语法:
var p = new Proxy(target, {
has: function(target, prop) {}
});
👉 参数:
下面的参数将会传递给 has() 方法,this 绑定在 handler 上
-
target :目标对象 -
prop :需要检查是否存在的属性的名称
👉 返回值:
has 方法返回一个 boolean 值
👉 使用:
const handler = {
has(target, prop) {
if (prop[0] === "_") {
console.log("不允许判断_开头的属性");
return false;
}
if (prop in target) {
console.log("?你判断的属性存在!");
return true;
} else {
console.log("?你判断的属性不存在!");
return false;
}
},
};
const obj = {
_name: "艾莉加薪",
name: "Ailjx",
age: 18,
};
const proxyObj = new Proxy(obj, handler);
console.log("aaa" in proxyObj);
console.log("----------------------");
console.log("_name" in proxyObj);
console.log("_name" in obj);
console.log("----------------------");
console.log("name" in proxyObj);
🎉 更多处理函数
handler 对象内还有以下处理函数:
五、可撤销代理
在前面说过:能将Proxy 直接作为对象访问它的属性进行操作,如Proxy.revocable()
这个Proxy.revocable() 方法可以用来创建一个可撤销的代理对象,先看下面这个例子:
const revocable = Proxy.revocable({},{
get(target, name) {
return "[[" + name + "]]";
},
}
);
console.log(revocable);
Proxy.revocable() 方法具有和Proxy 一样的两个参数:target 目标对象和handler 对象
但它的返回值有点特殊,它返回一个包含了代理对象本身和它的撤销方法的可撤销 Proxy 对象,其结构为:
{"proxy": proxy, "revoke": revoke}
-
proxy :表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉 -
revoke :撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象
一旦某个代理对象被撤销,它将变得几乎完全不可调用,在它身上执行任何的可代理操作都会抛出 TypeError 异常(可代理操作指的就是我们在handler 对象函数属性上能拦截到的操作,一共有14种,执行这14种以外的情况不会报错)
一旦被撤销,这个代理对象便不可能被直接恢复到原来的状态,同时和它关联的目标对象以及处理器对象都有可能被垃圾回收掉。再次调用撤销方法 revoke() 则不会有任何效果,但也不会报错
👉 示例:
const revocable = Proxy.revocable(
{},
{
get(target, name) {
return "[[" + name + "]]";
},
}
);
const proxy = revocable.proxy;
proxy.foo;
revocable.revoke();
console.log(proxy.foo);
proxy.foo = 1;
delete proxy.foo;
typeof proxy;
结语
深入了解了Proxy 之后,真的会被它强大的代理拦截功能所折服,在它的基础上我们可以创建几乎任何我们想要的响应式系统,它像是一个硕大的地基,至于地基之上需要建筑什么,全由我们自己掌握!
看完本篇文章相信你已经对Proxy 有了深入的理解,学习Proxy 是我们学习像Vue3 这种响应式原理的第一步,大家加油!
如果本篇文章对你有所帮助,还请客官一件四连!??
|