Proxy

In ES6, a new Proxy object is added. It is used to change the default access behavior of objects. In fact, a layer of interception is added between objects. In interception, we can add custom behavior.

Basic syntax for Proxy:

let proxy = new Proxy(target, handler)
Copy the code

Target — is the object to wrap, which can be anything, including a function.

Handler — Agent configuration: Objects with “traps” (methods for intercepting operations). Get catchers are used to read properties of target, set catchers are used to write properties of target, and so on.

Operate on the proxy, and if a corresponding catcher exists in handler, it will run and the Proxy will have a chance to process it, otherwise target will be processed directly.

The Proxy instance

We use get to implement the default value of an object.
// We will create an array that returns 0 for nonexistent items.
// Usually, when people try to get an array item that doesn't exist, they get undefined,
// But here we wrap a regular array in a proxy to capture the read operation and return 0 if there are no properties to read

// Define the target array
let numbers = [0.1.2];

// Define the configuration object
let handler = {
  get(target, prop) {
    return prop in target ? target[prop] : 0}}// Generate a Proxy instance
let numbersProxy = new Proxy(numbers, handler)

// Execution result
console.log(numbersProxy[1]); / / 1
console.log(numbersProxy[123]); // 0 (no such array entry)
Copy the code

In the example above, we define a configuration object that contains a get() function, which is triggered when properties of the proxy object are read. Therefore, the execution of numbersProxy[1], which accesses the value of the Proxy instance array item of 1, triggers the get() function, which outputs custom results on the console.

Precautions when using Proxy

It must be accessed through a proxy instance

If you need to configure the interception behavior of an object to work, it must be accessed on the properties of the proxy instance, not directly on the target object.

Proxy instance functions and their basic use

In the example above, we trigger the get() function of the custom configuration object by accessing the properties of the proxy object. The get() function is just one of a total of 13 functions supported by Proxy instances, summarized below.

get(target, property, receiver)

Used to intercept read property operations on objects. For example, call proxy.name or proxy[name], where target represents the target object, property represents the read property value, and Receiver represents the configuration object (the object that is initially invoked). Usually the proxy itself).

set(target, property, value, receiver)

Intercepting the write operation of object attributes, that is, setting the attribute value, such as proxy.name = ‘xx’ or proxy[name] = ‘xx’, where target represents the target object, property represents the property to be set, value represents the value of the property to be set, Receiver is a configuration object.

has(target, prop)

Intercepts the hasProperty operation and returns a Boolean value, typically in the form of prop in Target, where target represents the target object and prop represents the judged property value.

deleteProperty(target, property)

Intercepts the delete proxy[property] operation and returns a Boolean value indicating whether the operation was successfully executed, where target indicates the target object and property indicates the property to be deleted.

ownkeys(target)

Interception Object. GetOwnPropertyNames (target), Object. GetOwnPropertySymbols (target), the Object. The keys (target), for… In loop, where target stands for all of the property names of the object itself.

expand

Object. GetOwnPropertyNames (obj) returns a key Symbol.

Return to Symbol key Object. GetOwnPropertySymbols (obj).

Keys /values() returns a non-symbol key /value with the Enumerable flag.

for.. The IN loop iterates through all non-symbol keys with the Enumerable flag, as well as the keys of the prototype object.

getOwnPropertyDecriptor(target, prop)

Interception Object. GetOwnPropertyDecriptor (proxy, prop) operation, return to the attribute of descriptor Object, including target, the target Object, prop said need access descriptor set attribute.

defineProperty(target, property, descriptor)

Object. DefineProperty (proxy, Property, decriptor), Object. DefineProperties (proxy, decriptors), return a Boolean where target represents the target Object, Property is the new property, descriptor is the property descriptor object.

preventExtensions(target)

Object preventExtensions(proxy) intercepts object.preventExtensions (proxy) and returns a Boolean to make the Object unextensible and not add new properties, where target indicates the target Object.

isExtensible(target)

Intercepting object.isextensible (proxy), which returns a Boolean value indicating whether the Object isExtensible. Target indicates the target object.

getPrototypeOf(target)

GetPrototypeOf (proxy) intercepts the Object.getProtoTypeof (proxy) operation, which returns an Object representing the intercepting and retrieving Object prototype properties, where target represents the target Object.

setPropertyOf(target, prototype)

Object. SetPrototype (proxy, prototype) intercepts the object.setPrototype (proxy, prototype) operation, which returns a Boolean that represents the action of intercepting and setting the Object’s prototype properties, where target represents the target Object and prototype represents the new prototype Object.

apply(target, object, args)

Intercepting operations called by Proxy instances as functions, such as Proxy (… The args), proxy. Call (object,… The args), proxy. Apply (…). , where target represents the target object, object represents the caller of the function, and args represents the parameters passed by the function call.

constructor(target, args)

Intercepts operations called by Proxy instances as constructors, such as new Proxy(… Args), where target represents the target object and args represents the argument passed by the function call.

One common feature of these functions is that if the this keyword is used in target and processed by Proxy, the this keyword refers to the Proxy instance, not the target object.

const person = {
  getName() {
    console.log(this === proxy)
  }
}

const proxy = new Proxy(person, {})
person.getName() // fasle
proxy.getName() // true
Copy the code

Proxy Application Scenario

Read nonexistent properties

Normally, trying to read a nonexistent property returns undefined.

Create an agent that throws an error when trying to read a property that does not exist.

This can help catch programming errors early.

let user = {
  name: "xx"
};

function wrap(target) {
  return new Proxy(target, {
    get(target, prop) {
      if (prop in target) {
        return target[prop]
      } else {
        throw new ReferenceError(`Property doesn't exist: "${prop}"`)}}}); } user = wrap(user);console.log(user.name); // xx
console.log(user.age); // ReferenceError: Property doesn't exist: "age"
Copy the code

Reads the value of the negative index

The index value of the array increases from 0. Normally we cannot read the value of the negative index, but Proxy get() can do this.

Negative indexing is essentially finding the position of the element starting at the end of the array and working backwards.

In other words, array[-n] is the same as array[array.length-n].

let arr = [1.2.3.4.5]
arr = new Proxy(arr, {
  get(target, index) {
    if (index > 0) {
      return target[index]
    } else {
      return target[+index + target.length]
    }
  }
})
console.log(arr[1]) / / 2
console.log(arr[2]) / / 3
console.log(arr[-1]) / / 5
console.log(arr[-2]) / / 4
Copy the code

Disallow access to private properties

In some convention conventions, private properties begin with an underscore (_). In fact, we don’t want users to have access to private properties.

We will need the following catchers:

Get throws an error when reading such a property

Set threw an error while writing attributes

DeleteProperty Throws an error when deleting an attribute

Has excludes attributes beginning with _ when using the in method

OwnKeys is using for.. In and methods like object. keys exclude attributes that begin with _

let user = {
  name: "xx"._age: "18"
};

user = new Proxy(user, {
  get(target, prop) { // Intercepts property reading
    if (prop.startsWith('_')) {
      throw new Error("Access denied")}else {
      return target[prop]
    }
  },
  set(target, prop, val) { // Intercepts attribute writing
    if (prop.startsWith('_')) {
      throw new Error("Access denied")}else {
      target[prop] = val
      return true}},deleteProperty(target, prop) { // Intercepting attribute deletion
    if (prop.startsWith('_')) {
      throw new Error("Access denied")}else {
      delete target[prop]
      return true}},has(target, prop) {
    if (prop.startsWith('_')) { // Intercept the in operation
      throw new Error("Access denied");
    } else {
      return prop in target
    }
  },
  ownKeys(target) { // Intercepts reading property lists
    return Object.keys(target).filter(key= >! key.startsWith('_'))}});// "get" does not allow reading _age
try {
  user._age // Error: Access denied
} catch(e) { console.log(e.message) }

// "set" does not allow writing to _age
try {
  user._age = "20" // Error: Access denied
} catch(e) { console.log(e.message) }

// "deleteProperty" does not allow _age to be deleted
try {
  delete user._age // Error: Access denied
} catch(e) { console.log(e.message) }

// "has" filters out _age
  try {
    '_age' in user
  } catch (e) { console.log( e.message ) }
  
// "ownKeys" filters out _age
for(let key in user) console.log(key) // name
Copy the code

Restrictions on Proxy access to attributes

When we want to use Proxy to Proxy an object property and modify the return value of the property, we need the property not to be both unconfigurable and unwritable. If the property is both unconfigurable and unwritable, an exception is thrown when the property is read through the proxy.

let target = Object.defineProperties({}, {
  // Configurable name
  name: {
    value: 'xx'.configurable: true.writable: false
  },

  // Age cannot be configured
  age: {
    value: 18.configurable: false.writable: false
  }
})

target = new Proxy(target, {
  get(target, prop) {
    return 'abc'}})console.log(target.name) // abc
console.log(target.age) // Uncaught TypeError: 'get' on proxy: property 'age' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '18' but got 'abc')
Copy the code

Intercepting property assignment operations

Define a Person object with an age property between 0 and 100 that throws an exception whenever the value is not set within the range.

let user = {
  name: 'xx'.age: 18
}

user = new Proxy(user, {
  set(target, prop, number) {
    if (prop === 'age' && number > 100 || number < 0) {
      throw new Error("The age is invalid")
    }
    target[prop] = number
  }
})

user.name = 'yy'
console.log(user.name) // yy
user.age = 20
console.log(user.age) / / 20
user.age = 200
console.log(user.age) // Uncaught Error: The age is invalid
Copy the code

Function interception

Proxy provides the apply() function to intercept the operation of function invocation, including direct invocation, call() function invocation, and apply() function invocation.

By intercepting function calls, you can add custom operations to get new function processing results.

function sum (num1, num2) {
  return num1 + num2
}

sum = new Proxy(sum, {
  apply(target, obj, args) {
    return target.apply(obj, args) * 2}})console.log( sum(1.2))/ / 6
console.log( sum.call(null.1.2))/ / 6
console.log( sum.apply(null[1.2]))/ / 6

Copy the code