多年来,JavaScript 已经成长为一种强大且适应性强的编程语言,不断发展以满足开发人员不断变化的需求。
它的一项相对较新的进步是 Proxy 对象,它使程序员能够创建强大而灵活的对象,这些对象能够拦截和修改对其他对象的关键操作。
本文深入探讨了JavaScript中 Proxy 的功能,涵盖其语法、属性、典型应用、优势、局限性、说明性实例和推荐方法。
代理是一个对象,它封装另一个对象并拦截对其的基本操作,例如访问、分配和删除属性。代理是 JavaScript 的一个重要方面,它使开发人员能够编写更通用、更健壮的代码。
本文的目标是全面理解 JavaScript 中的 Proxy,包括其语法、特征、优点、缺点、插图和推荐技术。
JavaScript 的代理是一种允许创建能够修改和自定义对其他对象执行的基本操作的对象的功能。
要建立代理对象,需要两个组件:目标对象和处理程序对象。目标对象是要拦截操作的对象,而处理程序对象负责保存用于捕获这些操作的陷阱或方法。
下面是一个演示如何创建基本 Proxy 对象的示例:
const target = { name: 'John', age: 25, }; const handler = { get: function(target, prop) { console.log(`Getting property ${prop}`); return target[prop]; }, }; const proxy = new Proxy(target, handler); console.log(proxy.name); // Getting property name // John
在此示例中,我们生成一个目标对象,该对象具有两个特征:姓名和年龄。我们还生成一个处理程序对象,该对象具有获取陷阱以捕获任何读取目标对象属性的行为。之后,我们通过向 Proxy 构造函数提供目标和处理程序对象来生成 Proxy 对象。最后,我们检索 Proxy 对象的名称属性,它调用 get 陷阱并将消息输出到控制台。
陷阱是拦截对目标对象的操作的方法。有几个陷阱可以与 Proxy 对象一起使用,包括 get、set、has、deleteProperty 等等。
以下是一些最常用陷阱的简要概述:
get :此陷阱拦截读取目标对象属性的尝试。它有两个参数:目标对象和被访问的属性。陷阱返回属性的值。
set :此陷阱捕获在目标对象上建立属性的任何努力。它需要三个参数:目标对象本身、正在建立的属性以及该属性的更新值。该机制具有改变正在建立的值的能力,或者它可以产生错误以禁止建立值。
has :此陷阱拦截检查目标对象上是否存在属性的尝试。它有两个参数:目标对象和被检查的属性。陷阱返回一个布尔值,指示该属性是否存在。
deleteProperty :此陷阱拦截从目标对象中删除属性的尝试。它有两个参数:目标对象和被删除的属性。陷阱可以删除属性或抛出错误以防止属性被删除。
代理对象拥有一个迷人的特性,允许它们失效,导致它们的陷阱不再拦截对目标对象的操作。要构造一个可以失效的 Proxy 对象,请使用Proxy.revocable()
函数。
这是一个例子:
const target = { name: 'John', age: 25, }; const handler = { get: function(target, prop) { console.log(`Getting property ${prop}`); return target[prop]; }, }; const {proxy, revoke} = Proxy.revocable(target, handler); console.log(proxy.name); // Getting property name // John revoke(); console.log(proxy.name); // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
在这个例子中,我们使用Proxy.revocable()
方法创建一个可撤销的 Proxy 对象。然后我们访问 Proxy 对象的 name 属性,这会触发 get 陷阱并将消息记录到控制台。然后,我们使用revoke()
方法撤销 Proxy 对象,这意味着任何进一步访问 Proxy 对象属性的尝试都将失败。
Proxy 对象的另一个有趣特性是它们可用于在 JavaScript 中实现继承模式。通过使用 Proxy 对象作为另一个对象的原型,您可以拦截属性查找并自定义原型链的行为。
这是一个例子:
const parent = { name: 'John', }; const handler = { get: function(target, prop) { console.log(`Getting property ${prop}`); if (!(prop in target)) { return Reflect.get(parent, prop); } return target[prop]; }, }; const child = new Proxy({}, handler); console.log(child.name); // Getting property name // John child.name = 'Bob'; console.log(child.name); // Getting property name // Bob console.log(parent.name); // John
在此示例中,父对象已定义并具有名称属性。然后,我们创建一个带有 get 陷阱的处理程序对象,以防止对子对象属性的任何读取请求。如果子对象不存在该属性,陷阱将使用 Reflect.get() 方法回退到父对象。
然后,使用 Proxy 对象作为原型,使用 handler 对象作为处理程序,我们构建了一个子对象。最后,我们可以访问并更改子对象的名称属性,这会触发获取和设置陷阱并将消息记录到控制台。
Proxy 的一个用例是缓存昂贵的函数调用。在此示例中,我们创建了一个 Proxy 对象,它根据参数缓存函数调用的结果。
function calculateCost(price, taxRate) { console.log('Calculating cost...'); return price * (1 + taxRate); } const cache = new Map(); const proxy = new Proxy(calculateCost, { apply(target, thisArg, args) { const key = args.join('-'); if (cache.has(key)) { console.log('Returning cached result...'); return cache.get(key); } else { const result = Reflect.apply(target, thisArg, args); cache.set(key, result); return result; } }, }); console.log(proxy(10, 0.2)); // Calculating cost... 12 console.log(proxy(10, 0.2)); // Returning cached result... 12 console.log(proxy(20, 0.2)); // Calculating cost... 24 console.log(proxy(20, 0.3)); // Calculating cost... 26 console.log(proxy(20, 0.3)); // Returning cached result... 26
在此示例中,我们定义了一个名为calculateCost
函数,它接受价格和税率并返回含税成本。然后我们使用Map
类创建一个缓存对象。
接下来,我们创建一个名为proxy
的 Proxy 对象,它使用apply
陷阱拦截函数调用。只要函数被调用, apply
陷阱就会被调用,它接收函数参数作为数组。我们使用参数生成缓存键并检查结果是否已在缓存中。如果是,我们返回缓存的结果。否则,我们计算结果并将其存储在缓存中。
最后,我们使用不同的参数调用proxy
函数,并观察结果存储在缓存中以供后续使用相同参数的调用使用。
Proxy 的另一个用例是验证对象属性。在此示例中,我们创建了一个 Proxy 对象来验证字符串属性的长度。
const user = { name: 'John', password: 'secret', }; const proxy = new Proxy(user, { set(target, prop, value) { if (prop === 'password' && value.length < 8) { throw new Error('Password must be at least 8 characters long'); } target[prop] = value; return true; }, }); console.log(proxy.name); // John console.log(proxy.password); // secret proxy.password = '12345678'; console.log(proxy.password); // 12345678 proxy.password = '123'; // Error
在此示例中,我们定义了一个名为user
的对象,该对象具有name
和password
属性。然后我们创建一个名为proxy
Proxy 对象,它使用set
陷阱拦截属性分配。每当分配属性时都会调用set
陷阱,它会接收属性名称、新值和目标对象。
我们使用set
陷阱来检查分配的属性是否为password
属性以及该值是否小于 8 个字符。如果是,我们抛出一个错误。否则,我们在目标对象上设置属性值。
我们利用proxy
对象为password
属性分配各种值,并注意长度低于 8 个字符的任何值都会触发错误。
Proxy 的另一个常见用例是记录对象属性访问和分配。在此示例中,我们创建了一个记录属性访问和分配的代理对象。
const user = { name: 'John', email: '[email protected]', }; const proxy = new Proxy(user, { get(target, prop) { console.log(`Getting ${prop} property`); return target[prop]; }, set(target, prop, value) { console.log(`Setting ${prop} property to ${value}`); target[prop] = value; return true; }, }); console.log(proxy.name); // Getting name property -> John proxy.email = '[email protected]'; // Setting email property to [email protected] console.log(proxy.email); // Getting email property -> [email protected]
在此示例中,我们定义了一个名为user
的对象,该对象具有name
和email
属性。然后我们创建一个名为proxy
Proxy 对象,它使用get
和set
陷阱拦截属性访问和分配。
每当访问属性时都会调用get
陷阱,它会接收属性名称和目标对象。在此示例中,我们向控制台记录一条消息,指示正在访问属性,然后我们从目标对象返回属性值。
每当分配属性时都会调用set
陷阱,它会接收属性名称、新值和目标对象。在此示例中,我们向控制台记录一条消息,指示正在分配属性,然后我们在目标对象上设置属性值。
最后,我们使用proxy
对象访问和分配各种属性,并观察消息被记录到控制台。
可自定义的行为:使用代理对象,您可以拦截和自定义对其他对象的基本操作,从而允许您创建访问控制、缓存和日志记录等高级功能。
继承:代理对象提供了在 JavaScript 中实现继承模式的能力,这可以导致更通用和可扩展的代码。
Revocable :代理对象在创建后可以被禁用或撤销,这使得它们对于限制代理对象的范围或出于安全原因很有用。
尽管 Proxy 已经伴随我们很长时间了,但并非所有版本的浏览器都可以支持此功能。
此外,使用代理会对应用程序的性能产生负面影响,特别是如果您经常使用它们。
了解使用代理的含义很重要。在应用程序关键时刻不应信任它,例如用户输入的重要验证。
注意限制:在代码中实现代理之前,请注意它施加的限制以及它们如何影响应用程序的速度和安全性。
代理对象只应在绝对必要时使用,因为它们会影响代码的性能。
仔细测试:使用代理对象时,一定要仔细测试并警惕任何潜在的意外行为。
遵守规范:为了使您的代码易于阅读和维护,请在实现 Proxy 对象时遵守公认的约定和最佳实践。
本文深入探讨了 Proxy 的高级特性,例如继承模式和创建可撤销 Proxy 对象的能力。
无论您作为开发人员的经验水平如何,理解 JavaScript 中的 Proxy 都是将您的代码提升到更高水平的基础。
由于其适应性和效力,Proxy 构成了任何渴望轻松构建复杂应用程序的 JavaScript 开发人员的重要工具。