在 ES11 中更好地使用 JavaScript 的代理

代理(Proxy)是 JavaScript 提供的一种元编程的能力,它可以拦截对象的一些基本操作,比如读写属性、调用方法等。在 ES11 中,代理的功能得到了进一步增强,本文将详细介绍如何更好地使用 JavaScript 的代理。

基本用法

在 ES6 中,我们已经可以使用代理来拦截对象的基本操作了,例如:

const obj = {
  name: 'Alice',
  age: 18
};

const proxy = new Proxy(obj, {
  get(target, prop) {
    console.log(`Getting ${prop}`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`Setting ${prop} to ${value}`);
    target[prop] = value;
  }
});

proxy.name; // => Getting name
           // => 'Alice'

proxy.age = 20; // => Setting age to 20
console.log(proxy.age); // => Getting age
                       // => 20

上面的代码中,我们使用 new Proxy() 创建了一个代理对象 proxy,它可以拦截对象 obj 的读写操作。其中,拦截读操作的方法是 get(),拦截写操作的方法是 set()。当我们使用 proxy.name 读取属性值时,会触发 get() 方法,并输出 Getting name;当我们使用 proxy.age = 20 修改属性值时,会触发 set() 方法,并输出 Setting age to 20

新增的拦截方法

在 ES11 中,代理新增了一些拦截方法,让我们可以更加精细地控制对象的行为。

has()

has() 方法用于拦截 in 操作符,即判断对象是否包含某个属性。例如:

const obj = {
  name: 'Alice',
  age: 18
};

const proxy = new Proxy(obj, {
  has(target, prop) {
    console.log(`Checking ${prop} existence`);
    return prop in target;
  }
});

'age' in proxy; // => Checking age existence
                // => true

'gender' in proxy; // => Checking gender existence
                   // => false

上面的代码中,我们使用 has() 方法拦截了 in 操作符,当我们使用 'age' in proxy 判断属性存在时,会触发 has() 方法,并输出 Checking age existence

apply()

apply() 方法用于拦截函数的调用。例如:

function add(a, b) {
  return a + b;
}

const proxy = new Proxy(add, {
  apply(target, thisArg, args) {
    console.log(`Calling ${target.name} with ${args}`);
    return target.apply(thisArg, args);
  }
});

proxy(1, 2); // => Calling add with 1,2
             // => 3

上面的代码中,我们使用 apply() 方法拦截了函数的调用,当我们使用 proxy(1, 2) 调用函数时,会触发 apply() 方法,并输出 Calling add with 1,2

construct()

construct() 方法用于拦截 new 操作符,即创建对象实例时的行为。例如:

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

const proxy = new Proxy(Person, {
  construct(target, args) {
    console.log(`Creating ${target.name} instance with ${args}`);
    return new target(...args);
  }
});

const p = new proxy('Alice', 18); // => Creating Person instance with Alice,18
console.log(p); // => Person { name: 'Alice', age: 18 }

上面的代码中,我们使用 construct() 方法拦截了 new 操作符,当我们使用 new proxy('Alice', 18) 创建对象实例时,会触发 construct() 方法,并输出 Creating Person instance with Alice,18

deleteProperty()

deleteProperty() 方法用于拦截 delete 操作符,即删除对象属性的行为。例如:

const obj = {
  name: 'Alice',
  age: 18
};

const proxy = new Proxy(obj, {
  deleteProperty(target, prop) {
    console.log(`Deleting ${prop}`);
    return delete target[prop];
  }
});

delete proxy.age; // => Deleting age
console.log(proxy); // => { name: 'Alice' }

上面的代码中,我们使用 deleteProperty() 方法拦截了 delete 操作符,当我们使用 delete proxy.age 删除属性时,会触发 deleteProperty() 方法,并输出 Deleting age

getOwnPropertyDescriptor()

getOwnPropertyDescriptor() 方法用于拦截 Object.getOwnPropertyDescriptor() 方法,即获取对象属性描述符的行为。例如:

const obj = {
  name: 'Alice',
  age: 18
};

const proxy = new Proxy(obj, {
  getOwnPropertyDescriptor(target, prop) {
    console.log(`Getting descriptor of ${prop}`);
    return Object.getOwnPropertyDescriptor(target, prop);
  }
});

Object.getOwnPropertyDescriptor(proxy, 'age');
// => Getting descriptor of age
// => { value: 18, writable: true, enumerable: true, configurable: true }

上面的代码中,我们使用 getOwnPropertyDescriptor() 方法拦截了 Object.getOwnPropertyDescriptor() 方法,当我们使用 Object.getOwnPropertyDescriptor(proxy, 'age') 获取属性描述符时,会触发 getOwnPropertyDescriptor() 方法,并输出 Getting descriptor of age

getPrototypeOf()

getPrototypeOf() 方法用于拦截 Object.getPrototypeOf() 方法,即获取对象原型的行为。例如:

const obj = {
  name: 'Alice',
  age: 18
};

const proto = {
  gender: 'female'
};

const proxy = new Proxy(obj, {
  getPrototypeOf(target) {
    console.log(`Getting prototype of ${target}`);
    return proto;
  }
});

Object.getPrototypeOf(proxy); // => Getting prototype of [object Object]
                              // => { gender: 'female' }

上面的代码中,我们使用 getPrototypeOf() 方法拦截了 Object.getPrototypeOf() 方法,当我们使用 Object.getPrototypeOf(proxy) 获取对象原型时,会触发 getPrototypeOf() 方法,并输出 Getting prototype of [object Object]

isExtensible()

isExtensible() 方法用于拦截 Object.isExtensible() 方法,即判断对象是否可扩展的行为。例如:

const obj = {
  name: 'Alice',
  age: 18
};

const proxy = new Proxy(obj, {
  isExtensible(target) {
    console.log(`Checking ${target} extensibility`);
    return Object.isExtensible(target);
  }
});

Object.isExtensible(proxy); // => Checking [object Object] extensibility
                            // => true

上面的代码中,我们使用 isExtensible() 方法拦截了 Object.isExtensible() 方法,当我们使用 Object.isExtensible(proxy) 判断对象是否可扩展时,会触发 isExtensible() 方法,并输出 Checking [object Object] extensibility

ownKeys()

ownKeys() 方法用于拦截 Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys() 方法,即获取对象自身属性名的行为。例如:

const obj = {
  name: 'Alice',
  age: 18
};

const proxy = new Proxy(obj, {
  ownKeys(target) {
    console.log(`Getting own keys of ${target}`);
    return Reflect.ownKeys(target);
  }
});

Object.getOwnPropertyNames(proxy); // => Getting own keys of [object Object]
                                   // => [ 'name', 'age' ]

Object.getOwnPropertySymbols(proxy); // => Getting own keys of [object Object]
                                      // => []

Object.keys(proxy); // => Getting own keys of [object Object]
                    // => [ 'name', 'age' ]

上面的代码中,我们使用 ownKeys() 方法拦截了 Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys() 方法,当我们使用这些方法获取对象自身属性名时,会触发 ownKeys() 方法,并输出 Getting own keys of [object Object]

总结

在 ES11 中,代理的功能得到了进一步增强,我们可以使用新增的拦截方法更加精细地控制对象的行为。本文介绍了这些拦截方法的用法,并给出了示例代码。希望本文对你学习和使用 JavaScript 的代理有帮助。

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