您是否曾经遇到过希望能够控制对象或数组中的值的情况?也许您想阻止某些类型的数据,甚至在将数据存储到对象之前验证数据。假设您想以某种方式对传入数据或传出数据做出反应?例如,也许您想通过显示结果来更新 DOM,或者在数据更改时交换样式更改的类。是否曾经想过在一个需要框架(如 Vue 或 React)的一些功能的简单想法或页面部分上工作,但又不想启动一个新应用程序?
那么 JavaScript 代理 可能是您要寻找的东西!
简要介绍
我要先说:在前端技术方面,我更像是一个 UI 开发者;就像 The Great Divide 中描述的非 JavaScript 侧。我很乐意创建外观漂亮的项目,这些项目在浏览器中保持一致,以及与之相关的所有怪癖。因此,在更纯粹的 JavaScript 功能方面,我倾向于不深入研究。
然而,我仍然喜欢做研究,我一直在寻找一些东西添加到新的学习内容列表中。事实证明,JavaScript 代理是一个有趣的话题,因为仅仅了解基础知识就打开了如何利用此功能的许多可能想法。尽管如此,乍一看,代码可能会很快变得很重。当然,这完全取决于您的需要。
代理对象的概念已经存在了很长时间。我在研究中可以找到它在几年前的引用。然而,它并没有在我的清单上排名很高,因为它从未在 Internet Explorer 中得到支持。相比之下,它在所有其他浏览器中都得到了很好的支持多年。这是 Vue 3 与 Internet Explorer 11 不兼容的原因之一,因为它使用了 最新 Vue 项目中的代理。
那么,代理对象究竟是什么呢?
Proxy
对象
MDN 将 Proxy
对象 描述为
[…] 允许您为另一个对象创建一个代理,该代理可以拦截和重新定义该对象的根本操作。
一般思路是,您可以创建一个具有功能的对象,该功能允许您控制使用对象时发生的典型操作。最常见的两个是获取和设置存储在对象中的值。
const myObj = {
mykey: 'value'
}
console.log(myObj.mykey); // "gets" value of the key, outputs 'value'
myObj.mykey = 'updated'; // "sets" value of the key, makes it 'updated'
因此,在我们的代理对象中,我们将创建“陷阱”来拦截这些操作并执行我们可能希望完成的任何功能。最多有 13 个此类陷阱可用。我不一定会介绍所有这些陷阱,因为并不是所有陷阱都对我的以下简单示例都是必需的。同样,这取决于您为正在尝试创建的特定上下文的特定需求。相信我,您只需要基础知识就可以走很远。
为了扩展上面的示例以创建代理,我们将执行以下操作
const myObj = {
mykey: 'value'
}
const handler = {
get: function (target, prop) {
return target[prop];
},
set: function (target, prop, value) {
target[prop] = value;
return true;
}
}
const proxy = new Proxy(myObj, handler);
console.log(proxy.mykey); // "gets" value of the key, outputs 'value'
proxy.mykey = 'updated'; // "sets" value of the key, makes it 'updated'
首先,我们从标准对象开始。然后,我们创建一个包含处理函数(通常称为陷阱)的处理程序对象。它们代表可以在传统对象上执行的操作,在这种情况下,是get
和set
,它们只是传递值,没有任何更改。之后,我们使用目标对象和处理程序对象使用构造函数创建我们的代理。此时,我们可以引用代理对象来获取和设置值,这些值将是原始目标对象myObj
的代理。
请注意设置陷阱末尾的return true
。目的是通知代理,设置值应被认为是成功的。在某些情况下,如果您希望阻止设置值(想想验证错误),则将返回false
。这也会导致控制台错误,输出TypeError
。
现在需要注意的一点是,原始目标对象仍然可用。这意味着您可以绕过代理并更改对象的 value,而不会影响代理。在我关于使用Proxy
对象的阅读中,我发现了一些可以帮助解决此问题的有用模式。
let myObj = {
mykey: 'value'
}
const handler = {
get: function (target, prop) {
return target[prop];
},
set: function (target, prop, value) {
target[prop] = value;
return true;
}
}
myObj = new Proxy(myObj, handler);
console.log(myObj.mykey); // "gets" value of the key, outputs 'value'
myObj.mykey = 'updated'; // "sets" value of the key, makes it 'updated'
在此模式中,我们将目标对象用作代理对象,同时在代理构造函数中引用目标对象。是的,发生了这种情况。这有效,但我发现很容易混淆到底发生了什么。因此,让我们改为在代理构造函数中创建目标对象
const handler = {
get: function (target, prop) {
return target[prop];
},
set: function (target, prop, value) {
target[prop] = value;
return true;
}
}
const proxy = new Proxy({
mykey: 'value'
}, handler);
console.log(proxy.mykey); // "gets" value of the key, outputs 'value'
proxy.mykey = 'updated'; // "sets" value of the key, makes it 'updated'
为此,如果我们愿意,我们可以在构造函数中创建目标对象和处理程序对象
const proxy = new Proxy({
mykey: 'value'
}, {
get: function (target, prop) {
return target[prop];
},
set: function (target, prop, value) {
target[prop] = value;
return true;
}
});
console.log(proxy.mykey); // "gets" value of the key, outputs 'value'
proxy.mykey = 'updated'; // "sets" value of the key, makes it 'updated'
事实上,这是我在下面的示例中使用的最常见模式。值得庆幸的是,创建代理对象的方法很灵活。只需使用适合您的模式即可。
以下是有关 JavaScript 代理用法的示例,从基本数据验证到使用 fetch 更新表单数据。请记住,这些示例确实涵盖了 JavaScript 代理的基础知识;如果您愿意,它可以很快深入研究。在某些情况下,它们只是关于在代理对象中创建执行常规 JavaScript 操作的常规 JavaScript 代码。将它们视为通过更多地控制数据来扩展一些常见 JavaScript 任务的方法。
一个简单问题的简单示例
我的第一个示例涵盖了我一直认为是一个相当简单而奇怪的编码面试问题:反转字符串。我一直不喜欢它,在面试时也不会问它。作为在这方面喜欢逆流而上的人,我玩了一些非传统的解决方案。你知道的,只是为了有时玩玩,这些解决方案中有一个非常有趣。它也为展示代理的用途提供了一个简单的示例。
如果您在输入中输入内容,您将看到输入的内容在下面打印出来,但已反转。显然,这里可以使用许多反转字符串的方法中的任何一种。但是,让我们看一下我奇怪的反转方法。
const reverse = new Proxy(
{
value: ''
},
{
set: function (target, prop, value) {
target[prop] = value;
document.querySelectorAll('[data-reverse]').forEach(item => {
let el = document.createElement('div');
el.innerHTML = '\u{202E}' + value;
item.innerText = el.innerHTML;
});
return true;
}
}
)
document.querySelector('input').addEventListener('input', e => {
reverse.value = e.target.value;
});
首先,我们创建新的代理,目标对象是一个名为value
的单个键,它保存输入中输入的任何内容。get
陷阱不存在,因为我们只需要简单的传递,因为我们没有与之相关的任何实际功能。在这种情况下,无需执行任何操作。我们稍后会介绍。
对于set
陷阱,我们确实有一些要执行的小功能。仍然有一个简单的传递,其中将 value 设置为目标对象中的value
键,就像平常一样。然后,有一个querySelectorAll
用于查找页面上所有具有data-reverse
数据属性的元素。这使我们能够定位页面上的多个元素并一次性更新它们。这给了我们每个人都喜欢的框架式绑定操作。这也可以更新为定位输入,以允许适当的双向绑定类型的情况。
这就是我的小乐趣奇葩的反转字符串方法发挥作用的地方。一个 div 在内存中创建,然后元素的innerHTML
使用字符串更新。字符串的第一部分使用一个特殊的 Unicode 十进制代码,它实际上反转了后面的所有内容,使其成为从右到左。然后,页面上实际元素的innerText
将被赋予内存中 div 的innerHTML
。这每次在输入中输入内容时都会运行;因此,所有具有data-reverse
属性的元素都会更新。
最后,我们在输入上设置一个事件监听器,该监听器通过输入的值(事件的目标)来设置目标对象中的value
键。
最后,这是一个非常简单的示例,说明了通过将值设置到对象来对页面 DOM 执行副作用。
实时格式化输入值
一个常见的 UI 模式是将输入的值格式化为比仅仅字母和数字的字符串更精确的序列。一个例子是电话输入。有时,如果输入的电话号码看起来像一个电话号码,它看起来和感觉更好。不过,诀窍是,当我们格式化输入的值时,我们可能仍然希望有一个未格式化的数据版本。
对于 JavaScript 代理来说,这是一项简单的任务。
当您在输入框中输入数字时,它们会格式化为标准的美国电话号码(例如 `(123) 456-7890`)。请注意,电话号码以纯文本形式显示在输入框下方,就像上面的反转字符串示例一样。按钮将格式化和未格式化的数据版本都输出到控制台。
以下是代理的代码
const phone = new Proxy(
{
_clean: '',
number: '',
get clean() {
return this._clean;
}
},
{
get: function (target, prop) {
if (!prop.startsWith('_')) {
return target[prop];
} else {
return 'entry not found!'
}
},
set: function (target, prop, value) {
if (!prop.startsWith('_')) {
target._clean = value.replace(/\D/g, '').substring(0, 10);
const sections = {
area: target._clean.substring(0, 3),
prefix: target._clean.substring(3, 6),
line: target._clean.substring(6, 10)
}
target.number =
target._clean.length > 6 ? `(${sections.area}) ${sections.prefix}-${sections.line}` :
target._clean.length > 3 ? `(${sections.area}) ${sections.prefix}` :
target._clean.length > 0 ? `(${sections.area}` : '';
document.querySelectorAll('[data-phone_number]').forEach(item => {
if (item.tagName === 'INPUT') {
item.value = target.number;
} else {
item.innerText = target.number;
}
});
return true;
} else {
return false;
}
}
}
);
此示例中包含更多代码,让我们将其分解。第一部分是我们在代理本身内部初始化的目标对象。它有三个方面。
{
_clean: '',
number: '',
get clean() {
return this._clean;
}
},
第一个键 `_clean` 是我们用于保存未格式化数据版本的变量。它以下划线开头,遵循传统的变量命名模式,将其视为“私有”。我们希望在正常情况下使其不可用。随着我们深入了解,它将有更多内容。
第二个键 `number` 只是保存格式化的电话号码值。
第三个 `“key”` 是使用名称 `clean` 的 `get` 函数。它返回我们私有 `_clean` 变量的值。在本例中,我们只是返回该值,但如果需要,这提供了对它执行其他操作的机会。这就像代理的 `get` 函数的代理 getter 一样。看起来很奇怪,但它提供了一种简单的方式来控制我们的数据。根据您的特定需求,这可能是一种相当简单的处理这种情况的方式。它适用于我们这里简单的示例,但可能需要采取其他步骤。
现在是代理的 `get` 陷阱。
get: function (target, prop) {
if (!prop.startsWith('_')) {
return target[prop];
} else {
return 'entry not found!'
}
},
首先,我们检查传入的 prop 或对象键,以确定它是否 *不* 以下划线开头。如果它没有以下划线开头,我们只需将其返回。如果它有,则返回一个字符串,表明未找到该条目。这种类型的负返回可以根据需要以不同的方式处理。返回一个字符串、返回一个错误或运行具有不同副作用的代码。这一切都取决于情况。
在我的示例中需要注意的一点是,我没有处理其他可能会与代理中被认为是私有变量的内容一起使用的代理陷阱。为了更全面地保护这些数据,您必须考虑其他陷阱,例如 `[defineProperty] `(https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty),`deleteProperty` 或 `ownKeys` - 通常是关于操作或引用对象键的任何内容。您是否要进行这些操作将取决于谁将使用该代理。如果是为了您自己,那么您就知道如何使用代理。但如果是其他人,您可能需要考虑尽可能地锁定东西。
现在是这个示例中大部分神奇之处 - `set` 陷阱
set: function (target, prop, value) {
if (!prop.startsWith('_')) {
target._clean = value.replace(/\D/g, '').substring(0, 10);
const sections = {
area: target._clean.substring(0, 3),
prefix: target._clean.substring(3, 6),
line: target._clean.substring(6, 10)
}
target.number =
target._clean.length > 6 ? `(${sections.area}) ${sections.prefix}-${sections.line}` :
target._clean.length > 3 ? `(${sections.area}) ${sections.prefix}` :
target._clean.length > 0 ? `(${sections.area}` : '';
document.querySelectorAll('[data-phone_number]').forEach(item => {
if (item.tagName === 'INPUT') {
item.value = target.number;
} else {
item.innerText = target.number;
}
});
return true;
} else {
return false;
}
}
首先,对我们代理中存在的私有变量进行相同的检查。我没有真正测试其他类型的 prop,但您可能需要在这里考虑这样做。我假设只有代理目标对象中的数字键将被调整。
传入的值(输入的值)将删除除数字字符以外的所有内容,并保存到 `_clean` 键中。然后,该值将被用于整个重建过程,以重建为我们希望看到的格式化值。基本上,每次您键入时,整个字符串都会实时重建为预期的格式。substring 方法将数字锁定在十位数字内。
然后创建一个 `sections` 对象,用于保存我们电话号码的不同部分,这些部分基于美国电话号码的细分。当 `_clean` 变量长度增加时,我们更新 `number` 到我们希望在那个时间点看到的格式模式。
一个 `querySelectorAll` 正在寻找任何具有 `data-phone_number` 数据属性的元素,并将其通过 `forEach` 循环运行。如果该元素是输入(其值已更新),则任何其他元素的 `innerText` 将被更新。这就是文本在输入框下方显示的方式。如果我们在该数据属性下放置另一个输入元素,我们将看到它的值实时更新。这是一种创建单向或双向绑定方法,具体取决于需求。
最后,返回 `true` 以让代理知道一切顺利。如果传入的 prop 或键以下划线开头,则改为返回 `false`。
最后,使这一切生效的事件监听器
document.querySelectorAll('input[data-phone_number]').forEach(item => {
item.addEventListener('input', (e) => {
phone.number = e.target.value;
});
});
document.querySelector('#get_data').addEventListener('click', (e) => {
console.log(phone.number); // (123) 456-7890
console.log(phone.clean); // 1234567890
});
第一组找到所有具有我们特定数据属性的输入,并为它们添加一个事件监听器。对于每个输入事件,代理的数字键值都将使用当前输入的值更新。由于我们正在格式化每次发送的输入的值,因此我们删除了所有非数字字符。
第二组找到输出两组数据的按钮(按要求)到控制台。这展示了我们可以编写请求任何时间需要的数据的代码的方式。希望很清楚 `phone.clean` 是指我们目标对象中的 `get` 代理函数,它返回对象中的 `_clean` 变量。请注意,它没有像 `phone.clean()` 一样被调用为函数,因为它在我们代理中充当 `get` 代理。
将数字存储在数组中
您可以使用数组作为代理中的目标“对象”,而不是对象。由于它将是一个数组,因此需要考虑一些事项。数组的特性(如 `push()`)将在代理的 setter 陷阱中以特定方式处理。此外,在这种情况下,在目标对象概念中创建自定义函数实际上并不可行。但是,使用数组作为目标可以完成一些有用的事情。
当然,将数字存储在数组中并不是什么新鲜事。很明显。但是,我将为这个数字存储数组附加一些规则,例如不允许重复值,只允许数字。我还将提供一些输出选项,例如排序、求和、求平均值和清除值。然后更新一个小的用户界面来控制所有这些。
以下是代理对象
const numbers = new Proxy([],
{
get: function (target, prop) {
message.classList.remove('error');
if (prop === 'sort') return [...target].sort((a, b) => a - b);
if (prop === 'sum') return [...target].reduce((a, b) => a + b);
if (prop === 'average') return [...target].reduce((a, b) => a + b) / target.length;
if (prop === 'clear') {
message.innerText = `${target.length} number${target.length === 1 ? '' : 's'} cleared!`;
target.splice(0, target.length);
collection.innerText = target;
}
return target[prop];
},
set: function (target, prop, value) {
if (prop === 'length') return true;
dataInput.value = '';
message.classList.remove('error');
if (!Number.isInteger(value)) {
console.error('Data provided is not a number!');
message.innerText = 'Data provided is not a number!';
message.classList.add('error');
return false;
}
if (target.includes(value)) {
console.error(`Number ${value} has already been submitted!`);
message.innerText = `Number ${value} has already been submitted!`;
message.classList.add('error');
return false;
}
target[prop] = value;
collection.innerText = target;
message.innerText = `Number ${value} added!`;
return true;
}
});
在这个示例中,我将从 setter 陷阱开始。
首先要做的是检查是否将 `length` 属性设置为数组。它只是返回 `true`,以便以正常方式发生。如果需要,它可以始终在代码中放置一些代码以在设置长度时做出反应。
接下来的两行代码引用页面上的两个 HTML 元素,这些元素使用 `querySelector` 存储。`dataInput` 是输入元素,我们希望在每次输入时清除它。`message` 是用于保存对数组更改响应的元素。由于它具有错误状态的概念,因此我们确保它在每次输入时都不处于错误状态。
第一个 `if` 检查该条目是否实际上是一个数字。如果不是,它将执行几项操作。它发出一个控制台错误,说明问题所在。消息元素获取相同的语句。然后,该消息通过 CSS 类置于错误状态。最后,它返回 `false`,这也会导致代理将自己的错误发出到控制台。
第二个 `if` 检查该条目是否已存在于数组中;请记住,我们不希望出现重复。如果存在重复,则会发生与第一个 `if` 相同的消息。消息略有不同,因为它是一个模板文字,因此我们可以看到重复的值。
最后一段假设一切都已顺利完成,可以继续。该值按惯例设置,然后我们更新 `collection` 列表。`collection` 指的是页面上的另一个元素,它向我们展示数组中当前的数字集合。同样,该消息将使用添加的条目更新。最后,我们返回 `true` 以让代理知道一切都很好。
现在,`get` 陷阱与之前的示例略有不同。
get: function (target, prop) {
message.classList.remove('error');
if (prop === 'sort') return [...target].sort((a, b) => a - b);
if (prop === 'sum') return [...target].reduce((a, b) => a + b);
if (prop === 'average') return [...target].reduce((a, b) => a + b) / target.length;
if (prop === 'clear') {
message.innerText = `${target.length} number${target.length === 1 ? '' : 's'} cleared!`;
target.splice(0, target.length);
collection.innerText = target;
}
return target[prop];
},
这里发生的事情是利用了不是正常数组方法的“prop”;它作为 prop 传递给 `get` 陷阱。例如,第一个“prop”由以下事件监听器触发
dataSort.addEventListener('click', () => {
message.innerText = numbers.sort;
});
因此,当单击排序按钮时,消息元素的 `innerText` 将使用 `numbers.sort` 返回的任何内容更新。它充当一个 getter,代理会拦截它并返回一些非典型的与数组相关的结果。
在删除消息元素的潜在错误状态后,我们接下来弄清楚是否需要发生除标准数组获取操作以外的事情。每个操作都会返回对原始数组数据的操作,而不会改变原始数组。这是通过对目标使用扩展运算符来创建一个新数组,然后使用标准的数组方法来完成的。每个名称都应该暗示它所做的事情:排序、求和、求平均值和清除。嗯,好吧,清除并不是完全标准的数组方法,但这听起来不错。由于条目可以以任何顺序排列,因此我们可以让它提供排序列表或对条目执行数学运算。清除只是按照您的预期擦除数组。
以下是用于按钮的其他事件监听器
dataForm.addEventListener('submit', (e) => {
e.preventDefault();
numbers.push(Number.parseInt(dataInput.value));
});
dataSubmit.addEventListener('click', () => {
numbers.push(Number.parseInt(dataInput.value));
});
dataSort.addEventListener('click', () => {
message.innerText = numbers.sort;
});
dataSum.addEventListener('click', () => {
message.innerText = numbers.sum;
});
dataAverage.addEventListener('click', () => {
message.innerText = numbers.average;
});
dataClear.addEventListener('click', () => {
numbers.clear;
});
有很多方法可以扩展数组并添加功能。我已经看到一些数组示例,这些数组允许使用负索引选择条目,该索引从末尾开始计数。根据对象中的属性值在对象数组中查找条目。尝试获取数组中不存在的值时返回消息,而不是 `undefined`。有很多想法可以利用和探索使用数组代理。
交互式地址表单
地址表单是网页上相当标准的东西。让我们为它添加一些交互性以获得乐趣(和非标准)确认。它还可以充当表单值在单个对象中的数据收集,可以按需请求该对象。
以下是代理对象
const model = new Proxy(
{
name: '',
address1: '',
address2: '',
city: '',
state: '',
zip: '',
getData() {
return {
name: this.name || 'no entry!',
address1: this.address1 || 'no entry!',
address2: this.address2 || 'no entry!',
city: this.city || 'no entry!',
state: this.state || 'no entry!',
zip: this.zip || 'no entry!'
};
}
},
{
get: function (target, prop) {
return target[prop];
},
set: function (target, prop, value) {
target[prop] = value;
if (prop === 'zip' && value.length === 5) {
fetch(`https://api.zippopotam.us/us/${value}`)
.then(response => response.json())
.then(data => {
model.city = data.places[0]['place name'];
document.querySelector('[data-model="city"]').value = target.city;
model.state = data.places[0]['state abbreviation'];
document.querySelector('[data-model="state"]').value = target.state;
});
}
document.querySelectorAll(`[data-model="${prop}"]`).forEach(item => {
if (item.tagName === 'INPUT' || item.tagName === 'SELECT') {
item.value = value;
} else {
item.innerText = value;
}
})
return true;
}
}
);
目标对象非常简单;表单中每个输入的条目。getData
函数将返回对象,但如果属性的值为空字符串,它将更改为“无条目!”。这是可选的,但该函数提供了比我们通过获取代理对象的 state 获得的更简洁的对象。
getter 函数像往常一样简单地传递内容。你可能可以不用它,但我喜欢为了完整性而包含它。
setter 函数将值设置为 prop。但是,if
语句检查要设置的 prop 是否恰好是邮政编码。如果是,则我们检查值的长度是否为五。当评估结果为 true
时,我们将执行一个 fetch,使用邮政编码调用地址查找 API。返回的任何值都将插入到对象属性、城市输入中,并在选择元素中选择状态。这是一个方便的快捷方式示例,可以让用户跳过键入这些值。如有需要,这些值可以手动更改。
对于下一部分,让我们看一个输入元素的示例
<input class="in__input" id="name" data-model="name" placeholder="name" />
代理有一个 querySelectorAll
,它查找具有匹配数据属性的任何元素。这与我们之前看到的反转字符串示例相同。如果找到匹配项,它将更新输入的值或元素的 innerText
。这就是实时更新旋转卡以显示完成的地址将是什么样子的方式。
需要注意的一点是输入上的 data-model
属性。该数据属性的值实际上告诉代理在操作期间要锁定哪个键。代理根据该键找到涉及的元素。事件监听器通过让代理知道哪个键正在使用来执行几乎相同的操作。下面是它的样子
document.querySelector('main').addEventListener('input', (e) => {
model[e.target.dataset.model] = e.target.value;
});
因此,主元素内的所有输入都被选中,并且当触发输入事件时,代理将更新。data-model
属性的值用于确定在代理中要定位哪个键。实际上,我们正在使用一个类似于模型的系统。想想这样的系统还能如何利用。
至于“获取数据”按钮?它只是对 getData
函数进行简单的控制台记录……
getDataBtn.addEventListener('click', () => {
console.log(model.getData());
});
这是一个有趣的示例,可以用来构建和探索这个概念。这种示例让我思考我可以使用 JavaScript Proxy 创建什么。有时,你只需要一个小的 widget,它有一些数据收集/保护功能,并且能够通过与数据交互来操作 DOM。没错,你可以使用 Vue 或 React,但有时它们对于如此简单的事情来说过于庞大。
目前就这些了
“目前”意味着这取决于你们每个人,以及你们是否会更深入地研究 JavaScript Proxy。就像我在本文开头所说,我只涵盖了此功能的基础知识。它可以提供更多功能,并且可以比我提供的示例更强大。在某些情况下,它可以为 针对利基解决方案的小型助手 提供基础。很明显,这些示例可以轻松地使用基本函数来完成几乎相同的功能。甚至我大多数示例代码都是常规 JavaScript 与代理对象混合在一起。
不过,重点是提供使用代理的示例,以显示如何对数据交互做出反应——甚至控制如何对这些交互做出反应以保护数据、验证数据、操作 DOM 以及获取新数据——所有这些都基于某人尝试保存或获取数据。从长远来看,这非常强大,并允许创建可能不值得使用更大库或框架的简单应用程序。
因此,如果你是一位像我一样更专注于 UI 方面的前端开发人员,你可以探索一些基础知识,看看是否有更小的项目可以从 JavaScript Proxy 中获益。如果你是一位更专业的 JavaScript 开发人员,那么你可以开始更深入地研究代理以用于更大的项目。也许是一个新的框架或库?
只是一个想法……
我使用 Proxy 写了一个小型库来为 JS 对象添加一些隐藏的元数据,如果你有兴趣:https://npmjs.net.cn/package/proxy-extend
感谢你撰写这篇文章。我看到了“简单问题的简单示例”部分,在那里,它看起来像是从右到左的 HTML 实体正在对代码本身做完全相同的事情。即,将其反转。这让我困惑了一会儿!
el.innerHTML = ‘;eulav + ‘
是的,看起来 HTML 实体在代码示例中发挥了作用。它现在应该被转义,或者你可以查看 CodePen 看看它是什么样子的。
我最近开始使用代理进行状态驱动的 DOM 操作。主要用于那些不完全需要整个框架但仍然可以从类似状态的工作流程中受益的项目,以避免 jQuery 式的意大利面条代码。(现在我想想,这甚至可以与 jQuery 一起使用来解决人们在规模上遇到的许多问题,我认为。)
我写了一个 助手类(不是完全一个库),它还包括状态批处理和 DOM 操作去抖。你根据
constructor
方法中的任何事件更新你的状态,然后在sync
方法中执行所有 DOM 操作。可能不适合所有人,但我真的很喜欢这种分离,因为它帮助我以更具声明性的方式思考(即使它在技术上仍然是“命令式编程”)。如果有人发现它有趣
Vue 使用 Proxy 来实现其响应式系统。
我不明白为什么需要使用 Proxy,因为你可以简单地捕获值并运行一个普通函数来执行这些操作(并恢复浏览器兼容性并降低复杂性)。
这绝对是正确的,对于其中一些基本示例,你可以轻松地使用传统函数来完成。一个区别是使用 Proxy 允许你控制目标对象中的数据,以防止任何你可能不希望发生的副作用。如果对象以传统方式可用,那么该数据将不受你的控制。另一个区别可能是将所有功能都包含在同一个 Proxy 对象中,这在某些情况下可能很有用。
这实际上取决于你在遇到的情况中的需求。