JavaScript 代理:像专业人士一样使用 JavaScript 代理

2025-06-10

JavaScript 代理:像专业人士一样使用 JavaScript 代理

代理是 JavaScript 中的对象,它允许你创建对象的代理,同时还可以为诸如getset和 之类的标准对象操作定义自定义行为has。这意味着,例如,当有人尝试从对象中获取属性值时,你可以定义一组自定义行为。这使得代理成为一个非常强大的工具,让我们来看看它是如何工作的。

JavaScript 代理的基础知识

上面的内容听起来很复杂,所以让我们先看一个没有任何方法的简单示例。可以使用构造函数创建代理new Proxy(),该构造函数接受两个参数:

  • target即原始对象。
  • ,这handler是我们将添加到对象顶部的一组方法或属性。

可以handler包含一系列预定义方法。get例如,如果我们为 定义一个方法,它将自定义当我们尝试get从对象中获取某个项时发生的情况。

let target = {
    firstName: "John",
    lastName: "Doe",
    age: 152
}

let handler = {
    get: (object, prop) => {
        console.log(`Hi ${object.firstName} ${object.lastName}`)
    }
}

let proxyExample = new Proxy(target, handler);

proxyExample.age; // console logs "Hi John Doe"
Enter fullscreen mode Exit fullscreen mode

由于我们尝试在代理上get获取 的值,自定义处理程序被触发 - 因此我们在控制台中输出了。如你所见,这可以成为一个非常强大的工具,因为当调用对象的标准操作时,你可以做各种各样的事情。proxyExample.agegetHi ${object.firstName} ${object.lastName}

注意,当我们在上面添加get内容时handler,我们有一些自定义参数。每个可以添加到代理的处理程序都带有一组自定义参数。

get使用的函数是get(object, prop, receiver)

  • object- 原始对象。在上面的例子中,这是包含firstNamelastName和 的对象age
  • prop- 某人试图 的属性get。在上面的例子中,age
  • reciever- 代理本身。

处理程序方法被称为“陷阱”

更新代理值

代理仍然引用原始对象,因此对象值和代理值的引用是相同的。因此,如果您尝试更新代理的值,它也会更新原始对象的值。例如,下面我尝试更新代理,如您所见,原始对象和代理对象都被更新了:

let target = {
    name: "John",
    age: 152
}

let handler = {
}

let proxyExample = new Proxy(target, handler);
proxyExample.name = "Dave";

console.log(proxyExample.name); // Console logs Dave
console.log(target.name); // Console logs Dave
Enter fullscreen mode Exit fullscreen mode

了解这一点很有用 - 不要指望代理会完全创建一个单独的对象 - 它不是复制对象的方法。

JavaScript 代理中的自定义处理程序

代理有许多自定义处理程序,允许我们“捕获”任何对象操作并对其进行一些有趣的操作。最常用的方法如下:

  • proxy.apply(objects, thisObject, argList)- 一种捕获函数调用的方法。
  • proxy.construct(object, argList, newTarget)- 使用构造函数关键字调用函数时捕获的方法new
  • proxy.defineProperty(object, prop, descriptor)- 当使用 向对象添加新属性时,用于捕获的方法Object.defineProperty
  • proxy.deleteProperty(object, prop)- 当从对象中删除属性时捕获的方法。
  • proxy.get(object, prop, receiver)- 如前所述,当有人试图get从对象中获取属性时,采用这种方法进行捕获。
  • proxy.set(object, prop, value, receiver)- 当属性被赋予值时捕获的方法。
  • proxy.has(object, prop)- 一种诱捕in操作员的方法。

上述方法足以完成你想用代理做的所有事情。它们很好地覆盖了所有主要的对象操作,你可以根据需要进行修改和自定义。

不过,还有一些其他操作 - 除了这些非常基本的对象操作之外,我们还可以访问:

  • proxy.getPrototypeOf(object)- 一种捕获Object.getPrototypeOf方法的方法。
  • proxy.getOwnPropertyDescriptor(object, prop)- 一种捕获的方法getOwnPropertyDescriptor,它返回特定属性的描述符 - 例如,它是否可枚举等。
  • proxy.isExtensible(object)Object.isExtensible()- 一种在触发时进行捕获的方法。
  • proxy.preventExtensions(object)Object.preventExtensions()- 一种在触发时进行捕获的方法。
  • proxy.setPrototypeOf(object, prototype)Object.setPrototypeOf()- 一种在触发时进行捕获的方法。
  • proxy.ownKeys(object)- 当类似方法被触发时捕获的方法Object.getOwnPropertyNames()

让我们更详细地了解其中的一些内容,以了解代理的工作原理。

 将 in 运算符与代理一起使用

我们已经讲过了proxy.get(),现在让我们来看看has()。这主要在使用in运算符时触发。例如,如果我们想在in使用 时在控制台记录某个属性不存在的事实,我们可以这样做:

let target = {
    firstName: "John",
    lastName: "Doe",
    age: 152
}

let handler = {
    has: (object, prop) => {
        if(object[prop] === undefined) {
            console.log('Property not found');
        }
        return object[prop]
    }
}

let proxyExample = new Proxy(target, handler);

console.log('address' in proxyExample); 
// console logs 
// 'Property not found' 
// false
Enter fullscreen mode Exit fullscreen mode

由于address未在中定义target(因此在中proxyExample),尝试控制台记录'address' in proxyExample将返回 false - 但它也会控制台记录'Property not found',因为我们在代理中定义了它。

使用代理设置值

您可能想要修改的一个同样有用的方法是set()。下面,我使用自定义set处理程序来修改尝试更改用户年龄时发生的情况。对于每个集合操作,如果属性是数字,那么我们将在数字更新时在控制台中记录差异。

let target = {
    firstName: "John",
    lastName: "Doe",
    age: 152
}

let handler = {
    set: (object, prop, value) => {
        if(typeof object[prop] === "number" && typeof value === "number") {
            console.log(`Change in number was ${value - object[prop]}`);
        }
        return object[prop]
    }
}

let proxyExample = new Proxy(target, handler);

proxyExample['age'] = 204;
// Console logs 
// Change in number was 52
Enter fullscreen mode Exit fullscreen mode

由于proxyExample.age更新后的值204都是数字,我们不仅将值更新为204,而且还会收到一个有用的控制台日志,告诉我们这两个数字之间的差值。很酷吧?

虽然set会对任何集合操作触发,包括向对象添加新项,但你也可以使用 实现类似的行为defineProperty。例如,下面这样也可以:

let target = {
    firstName: "John",
    lastName: "Doe",
    age: 152
}

let handler = {
    defineProperty: (object, prop, descriptor) => {
        console.log(`A property was set - ${prop}`);
    },
}

let proxyExample = new Proxy(target, handler);

proxyExample['age'] = "123 Fake Street";
// Console logs
// A property was set - address
Enter fullscreen mode Exit fullscreen mode

但是请注意,如果您添加setdefineProperty都作为处理程序,则在我们使用方括号符号设置属性的情况下set将会覆盖。但是,如果您明确使用,仍然会触发,如下所示:defineProperty[].definePropertyObject.defineProperty

let target = {
    firstName: "John",
    lastName: "Doe",
    age: 152
}

let handler = {
    defineProperty: (object, prop, descriptor) => {
        console.log(`A property was set with defineProperty - ${prop}`);
        return true;
    },
    set: (object, prop, descriptor) => {
        console.log(`A property was set - ${prop}`);
        return true;
    },
}

let proxyExample = new Proxy(target, handler);

Object.defineProperty(proxyExample, 'socialMedia', {
    value: 'twitter',
    writable: false
});
proxyExample['age'] = "123 Fake Street";
// Console logs
// A property was set with defineProperty - socialMedia
// A property was set - address
Enter fullscreen mode Exit fullscreen mode

使用代理删除值

除了这些实用的方法之外,我们还可以用它deleteProperty来处理用户使用delete关键字删除某些内容时发生的情况。例如,我们可以在控制台中记录日志,告知用户属性正在被删除:

let target = {
    firstName: "John",
    lastName: "Doe",
    age: 152
}

let handler = {
    deleteProperty: (object, prop) => {
        console.log(`Poof! The ${prop} property was deleted`);
    },
}

let proxyExample = new Proxy(target, handler);

delete proxyExample['age'];
// Console logs
// Poof! The age property was deleted
Enter fullscreen mode Exit fullscreen mode

使用代理自定义函数调用

代理还允许我们在调用函数时运行自定义代码。这是因为 JavaScript 的特性:函数本身就是对象。有两种方法可以做到这一点:

  • 使用apply()处理程序来捕获标准函数调用。
  • 使用construct()处理程序来捕获new构造函数调用。

这是一个简单的例子,我们捕获一个函数调用,并通过在其输出末尾附加一些内容来修改它。

let target = (firstName, lastName) => {
    return `Hello ${firstName} ${lastName}`
}

let handler = {
    apply: (object, thisObject, argsList) => {
        let functionCall = object(...argsList);
        return `${functionCall}. I hope you are having a nice day!`
    },
}

let proxyExample = new Proxy(target, handler);

proxyExample("John", "Doe");
// Returns
// Hello John Doe. I hope you are having a nice day!
Enter fullscreen mode Exit fullscreen mode

apply接受三个参数:

  • object- 原始对象。
  • thisObject-this函数/对象的值。
  • argsList- 传递给函数的参数。

object上面,我们使用包含原始函数的参数调用了我们的函数target。然后我们在参数末尾添加了一些文本来改变函数的输出。是不是很酷?

我们也可以使用 做同样的事情construct,它也有三个参数:

  • object- 原始对象。
  • argsList- 函数/对象的参数。
  • newTarget- 最初调用的构造函数 - 即代理。

construct下面是一个函数返回对象的示例,我们使用代理上的方法向其添加一些属性:

function target(a, b, c) {
    return { 
        a: a,
        b: b,
        c: c
    }
}

let handler = {
    construct: (object, argsList, newTarget) => {
        let functionCall = object(...argsList);
        return { ...functionCall, d: 105, e: 45 }
    },
}

let proxyExample = new Proxy(target, handler);

new proxyExample(15, 24, 45);
// Returns
// {a: 15, b: 24, c: 45, d: 105, e: 45}
Enter fullscreen mode Exit fullscreen mode

结论

代理是 JavaScript 工具库中一个非常棒的工具,它允许你修改对象的基本操作。代理提供了大量的方法供你使用,如果使用得当,它们可以大大简化你的代码。希望你喜欢这篇文章——你可以在这里阅读更多我的 JavaScript 内容

鏂囩珷鏉ユ簮锛�https://dev.to/smpnjn/using-javascript-proxies-like-a-pro-590
PREV
Filament V3 和 Laravel 10 入门
NEXT
如何修复“zsh:未找到命令:python”