ES6 中的 Proxy 对象详解

ES6 中的 Proxy 对象是一种可以在对象访问中拦截、更改或扩展行为的工具。通过 Proxy 对象,我们可以更加灵活地处理对象的属性访问、方法调用、构造器调用等操作。本文将详细介绍 Proxy 对象的使用方法和特性,并给出相关示例代码。

Proxy 对象的基本用法

创建一个 Proxy 对象,需要定义一个 Target 对象和一个 Handler 对象,其中 Target 对象为被代理的对象,Handler 对象为拦截器,用于拦截、处理 Target 对象的各种操作。以下为创建 Proxy 对象的代码示例:

let target = {
    name: 'Lucy',
    age: 20
};

let handler = {
    get: (target, prop, receiver) => {
        console.log(`Getting ${prop}`);
        return target[prop];
    },

    set: (target, prop, value, receiver) => {
        console.log(`Setting ${prop} to ${value}`);
        target[prop] = value;
        return true;
    }
};

let proxy = new Proxy(target, handler);

在以上代码中,我们定义了一个 Target 对象 target,包含 nameage 两个属性。同时,我们定义了一个 Handler 对象 handler,其包含了两个拦截器:get 拦截器用于处理属性访问操作,set 拦截器用于处理属性设置操作。最后,我们使用 new Proxy() 方法创建了 Proxy 对象 proxy

通过以上代码,我们可以在 proxy 对象上进行属性的访问和设置,同时拦截器可以捕捉到相关操作并做出相应的处理。

例如,我们可以使用以下代码访问 proxy 对象中的 name 属性:

console.log(proxy.name);

执行以上代码,会先输出 "Getting name",表示访问了 name 属性,然后输出 "Lucy",表示返回了 target.name 属性的值。相应地,我们也可以使用以下代码来设置 proxy 对象的 name 属性:

proxy.name = 'Lily';

执行以上代码,会先输出 "Setting name to Lily",表示设置了 name 属性的值为 "Lily"

Proxy 对象的拦截器类型

除了 getset 拦截器,我们还可以使用 Proxy 对象提供的其他拦截器,包括:applyconstructdeletePropertyhasgetOwnPropertyDescriptordefinePropertygetPrototypeOfsetPrototypeOfisExtensiblepreventExtensionsownKeys 等。以下是拦截器的介绍和使用方法:

apply 拦截器

apply 拦截器用于拦截函数调用,比如 func(...args)func.apply(receiver, args)。其接收三个参数:

  • target:被代理的函数;
  • thisArg:函数调用时的 this 值;
  • argArray:函数调用时传入的参数数组。

以下是一个 apply 拦截器的代码示例:

let func = (...args) => {
    console.log(`Calling func with args:`, args);
    return args.reduce((sum, n) => sum + n, 0);
};

let handler = {
    apply: (target, thisArg, argArray) => {
        console.log(`Calling function with args:`, argArray);
        return target(...argArray);
    }
};

let proxy = new Proxy(func, handler);

console.log(proxy(1, 2, 3, 4, 5)); // 输出 15

在以上代码中,我们定义了一个函数 func,接收多个参数并返回它们的和。我们还定义了一个 apply 拦截器,拦截了 proxy 对象的函数调用,输出传入的参数并调用 target(...argArray) 方法。

construct 拦截器

construct 拦截器用于拦截构造函数的调用,例如 new func(...args)。其接收两个参数:

  • target:被代理的构造函数;
  • argArray:构造函数调用时传入的参数数组。

以下是一个 construct 拦截器的代码示例:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    sayHello() {
        console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old.`);
    }
}

let handler = {
    construct: (target, argArray) => {
        let [name, age] = argArray;
        console.log(`Creating new Person with name ${name} and age ${age}.`);
        return new target(name, age);
    }
};

let proxy = new Proxy(Person, handler);

let person = new proxy('Lucy', 20);
person.sayHello(); // 输出 "Hello, my name is Lucy, I'm 20 years old."

在以上代码中,我们定义了一个 Person 类,包含 nameage 两个属性,和 sayHello() 方法。我们还定义了一个 construct 拦截器,拦截了 proxy 对象的构造调用,输出传入的参数并调用 target(...argArray) 方法。

deleteProperty 拦截器

deleteProperty 拦截器用于拦截删除一个属性的操作,例如 delete obj.prop。其接收两个参数:

  • target:被代理的对象;
  • prop:要删除的属性名。

以下是一个 deleteProperty 拦截器的代码示例:

let obj = {
    name: 'Lucy',
    age: 20
};

let handler = {
    deleteProperty: (target, prop) => {
        console.log(`Deleting property ${prop}`);
        delete target[prop];
        return true;
    }
};

let proxy = new Proxy(obj, handler);

delete proxy.age; // 输出 "Deleting property age"
console.log(proxy); // 输出 {name: 'Lucy'}

在以上代码中,我们定义了一个对象 obj,包含 nameage 两个属性。我们还定义了一个 deleteProperty 拦截器,拦截了 proxy 对象的删除属性操作,输出被删除的属性名并调用 delete target[prop] 方法。

has 拦截器

has 拦截器用于拦截属性是否存在的操作,例如 'prop' in obj。其接收两个参数:

  • target:被代理的对象;
  • prop:属性名。

以下是一个 has 拦截器的代码示例:

let obj = {
    name: 'Lucy',
    age: 20
};

let handler = {
    has: (target, prop) => {
        console.log(`Checking if property ${prop} exists`);
        return prop in target;
    }
};

let proxy = new Proxy(obj, handler);

console.log('name' in proxy); // 输出 "Checking if property name exists",并返回 true
console.log('gender' in proxy); // 输出 "Checking if property gender exists",并返回 false

在以上代码中,我们定义了一个对象 obj,包含 nameage 两个属性。我们还定义了一个 has 拦截器,拦截了 proxy 对象的检查属性是否存在的操作,输出被检查的属性名并调用 prop in target 方法。

getOwnPropertyDescriptor 拦截器

getOwnPropertyDescriptor 拦截器用于拦截获取属性描述符的操作,例如 Object.getOwnPropertyDescriptor(obj, 'prop')。其接收两个参数:

  • target:被代理的对象;
  • prop:属性名。

以下是一个 getOwnPropertyDescriptor 拦截器的代码示例:

let obj = {
    name: 'Lucy',
    age: 20
};

let handler = {
    getOwnPropertyDescriptor: (target, prop) => {
        console.log(`Getting property descriptor of ${prop}`);
        return Object.getOwnPropertyDescriptor(target, prop);
    }
};

let proxy = new Proxy(obj, handler);

console.log(Object.getOwnPropertyDescriptor(proxy, 'name')); // 输出 "Getting property descriptor of name",并返回 {value: "Lucy", writable: true, enumerable: true, configurable: true}

在以上代码中,我们定义了一个对象 obj,包含 nameage 两个属性。我们还定义了一个 getOwnPropertyDescriptor 拦截器,拦截了 proxy 对象的获取属性描述符的操作,输出被获取的属性名并调用 Object.getOwnPropertyDescriptor(target, prop) 方法。

defineProperty 拦截器

defineProperty 拦截器用于拦截定义一个属性的操作,例如 Object.defineProperty(obj, 'prop', {value: 123})。其接收三个参数:

  • target:被代理的对象;
  • prop:要定义的属性名;
  • desc:要定义的属性描述符对象。

以下是一个 defineProperty 拦截器的代码示例:

let obj = {
    name: 'Lucy',
    age: 20
};

let handler = {
    defineProperty: (target, prop, desc) => {
        console.log(`Defining property ${prop} with descriptor:`, desc);
        return Object.defineProperty(target, prop, desc);
    }
};

let proxy = new Proxy(obj, handler);

Object.defineProperty(proxy, 'gender', {value: 'female'}); // 输出 "Defining property gender with descriptor: {value: 'female', writable: false, enumerable: false, configurable: false}"
console.log(proxy); // 输出 {name: 'Lucy', age: 20, gender: 'female'}

在以上代码中,我们定义了一个对象 obj,包含 nameage 两个属性。我们还定义了一个 defineProperty 拦截器,拦截了 proxy 对象的定义属性的操作,输出被定义的属性名和属性描述符并调用 Object.defineProperty(target, prop, desc) 方法。

getPrototypeOf 拦截器

getPrototypeOf 拦截器用于拦截获取一个对象的原型的操作,例如 Object.getPrototypeOf(obj)。其接收一个参数:

  • target:被代理的对象。

以下是一个 getPrototypeOf 拦截器的代码示例:

let obj = {
    name: 'Lucy',
    age: 20
};

let handler = {
    getPrototypeOf: (target) => {
        console.log(`Getting prototype of object`);
        return Object.getPrototypeOf(target);
    }
};

let proxy = new Proxy(obj, handler);

console.log(Object.getPrototypeOf(proxy)); // 输出 "Getting prototype of object",并返回 Object.prototype

在以上代码中,我们定义了一个对象 obj,包含 nameage 两个属性。我们还定义了一个 getPrototypeOf 拦截器,拦截了 proxy 对象的获取原型的操作,输出被代理的对象并调用 Object.getPrototypeOf(target) 方法。

setPrototypeOf 拦截器

setPrototypeOf 拦截器用于拦截设置一个对象的原型的操作,例如 Object.setPrototypeOf(obj, proto)。其接收两个参数:

  • target:被代理的对象;
  • proto:要设置的新原型对象。

以下是一个 setPrototypeOf 拦截器的代码示例:

let obj = {
    name: 'Lucy',
    age: 20
};

let handler = {
    setPrototypeOf: (target, proto) => {
        console.log(`Setting prototype of object to ${proto}`);
        return Object.setPrototypeOf(target, proto);
    }
};

let proxy = new Proxy(obj, handler);

Object.setPrototypeOf(proxy, {});
console.log(Object.getPrototypeOf(proxy)); // 输出 "[object Object]"

在以上代码中,我们定义了一个对象 obj,包含 nameage 两个属性。我们还定义了一个 setPrototypeOf 拦截器,拦截了 proxy 对象的设置原型的操作,输出被代理的对象和新的原型对象并调用 Object.setPrototypeOf(target, proto) 方法。

isExtensible 拦截器

isExtensible 拦截器用于拦截检测一个对象是否可扩展的操作,例如 Object.isExtensible(obj)。其接收一个参数:

  • target:被代理的对象。

以下是一个 isExtensible 拦截器的代码示例:

let obj = {
    name: 'Lucy',
    age: 20
};

let handler = {
    isExtensible: (target) => {
        console.log(`Checking if object is extensible`);
        return Object.isExtensible(target);
    }
};

let proxy = new Proxy(obj, handler);

console.log(Object.isExtensible(proxy)); // 输出 "Checking if object is extensible",并返回 true

在以上代码中,我们定义了一个对象 obj,包含 nameage 两个属性。我们还定义了一个 isExtensible 拦截器,拦截了 proxy 对象的检测是否可扩展的操作,输出被代理的对象并调用 Object.isExtensible(target) 方法。

preventExtensions 拦截器

preventExtensions 拦截器用于拦截使一个对象不可扩展的操作,例如 Object.preventExtensions(obj)。其接收一个参数:

  • target:被代理的对象。

以下是一个 preventExtensions 拦截器的代码示例:

let obj = {
    name: 'Lucy',
    age: 20
};

let handler = {
    preventExtensions: (target) => {
        console.log(`Preventing object from being extended`);
        return Object.preventExtensions(target);
    }
};

let proxy = new Proxy(obj, handler);

Object.preventExtensions(proxy);
console.log(Object.isExtensible(proxy)); // 输出 "Preventing object from being extended",并返回 false

在以上代码中,我们定义了一个对象 obj,包含 nameage 两个属性。我们还定义了一个 preventExtensions 拦截器,拦截了 proxy 对象的使其不可扩展的操作,输出被代理的对象并调用 Object.preventExtensions(target) 方法。

ownKeys 拦截器

ownKeys 拦截器用于拦截获取一个对象自身属性键名的操作,例如 Object.getOwnPropertyNames(obj)Object.getOwnPropertySymbols(obj)Reflect.ownKeys(obj)。其接收一个参数:

  • target:被代理的对象。

以下是一个 ownKeys 拦截器的代码示例:

let obj = {
    name: 'Lucy',
    age: 20
};

let handler = {
    ownKeys: (target) => {
        console.log(`Getting own property keys of object`);
        return Object.getOwnPropertyNames(target);
    }
};

let proxy = new Proxy(obj, handler);

console.log(Object.getOwnPropertyNames(proxy)); // 输出 "Getting own property keys of object",并返回 ["name", "age"]

在以上代码中,我们定义了一个对象 obj,包含 nameage 两个属性。我们还定义了一个 ownKeys 拦截器,拦截了 proxy 对象的获取其自身属性键名的操作,输出被代理的对象并调用 Object.getOwnPropertyNames(target) 方法。

Proxy 对象的符号键

除了上述拦截器类型,Proxy 对象还支持一些特殊的符号键,用于进行属性访问拦截、代理的相关操作。以下是常用的几种符号键:

  • get(target, prop, receiver):拦截对象的属性读取操作;
  • set(target, prop, value, receiver):拦截对象的属性设置操作;
  • has(target, prop):拦截 in 操作符;
  • deleteProperty(target, prop):拦截 delete 操作符;
  • apply(target, thisArg, args):拦截函数调用;
  • construct(target, args):拦截构造函数调用。

其它符号键的使用方法和用途,请参考 MDN 文档。

总结

通过本文介绍,我们了解了 ES6 中的 Proxy 对象的基本用法和特性。Proxy 对象的拦截器类型和符号键,可以让我们更加灵活地处理对象的属性访问、方法调用、构造器调用等操作,为我们的编码提供了很大的方便和开发效率。经过深入的学习和练习,我们可以更好地掌握 Proxy 对象的使用方法,并在实际的项目开发中灵活运用。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65a99fa4add4f0e0ff2fd314


纠错反馈