[JS informal learning] - ES6 proxy

Posted by rsmith on Wed, 15 Dec 2021 13:35:02 +0100

Proxy

Proxy is used to create a proxy for an object to modify the default behavior of some operations. It can be understood as setting up a layer of "interception" in front of the target object. External access to the object must first pass through this layer of interception. Therefore, it provides a mechanism for us to filter and rewrite external access. These filters can be defined by ourselves:

grammar

  cosnt p = new Proxy(target, handler);

parameter

target
The target object to be wrapped with a Proxy (can be any type of object, including a native array, a function, or even another Proxy)

handler
Is an object with a function as its attribute. Its attribute is the function of the agent when performing various operations (it can be understood as the catcher of some operations of the object).

// Define a common object like Zhang San
let userInfo = {
  name: "kobe"
};
// We use proxy as a simple proxy
userInfo = new Proxy(userInfo, {
  // Set up a catcher for reading and operating
  get(target, prop) {
    console.log('getter: Got object properties' + prop);
    return target[prop]
  },
  set(target, prop, value) {
    console.log(`setter: Operand properties ${prop},Value is ${value}`);
    // You can also write some attribute setting conditions' l
    if(prop === 'age' && value >= 200) {
      throw new RangeError("I'm afraid it's not an old monster");
    }
    // Save qualified attributes
    target[prop] = value;
    return true;
  }
});

// Brothers, let's operate it
// Read the properties of userInfo to see what happens
console.log(userInfo.name); // getter: got the object attribute name Kobe
// Add a new attribute to userInfo
userInfo.age = 41; // setter: operation object attribute age, value 41
// Add another attribute
userInfo.gender = 'male'; // setter: operand attribute gender, with value of 'male'
++userInfo.age;
// getter: got the object property
// setter: operation object attribute age, value 42

From the above example, we first define a user information object userInfo containing name, and then we wrap it through Proxy and return it to userInfo. At this time, userInfo becomes a Proxy instance, and all our operations on it will be intercepted by Proxy. For operations that can be set but do not set interception, they will directly fall on the target object and produce results in the original way.

Method of handler object

The following is a list of 13 interception operations supported by Proxy.

var userInfo = {
  name: "Zhang San"
}

const user = new Proxy(userInfo, {
  get(target, prop) {
    if (prop in target) {
      return target[prop];
    } else {
      throw new ReferenceError("prop name \"" + prop + "\" does not exist.");
    }
  }
})
user.name // "Zhang San"
user.age // Uncaught ReferenceError: prop name "age" does not exist.

let handler = {
  set(target, prop, value) {
    if(prop === "age") {
      if(!Number.isInteger(value))
      if(!Number.isInteger(value))
        throw new TypeError('The age is not an integer');
      if(value > 200)
         throw new RangeError('I'm afraid it's not an old monster!') 
    }
    target[prop] = value;
    return true;
  }
}

let user = new Proxy({}, handler);
user.name = "Zhang San"; // 'Zhang San'
user.age = 100; // 100
user.age = 1.1; // Uncaught TypeError: The age is not an integer
user.age = "old"; // Uncaught TypeError: The age is not an integer
user.age = 250; // Uncaught RangeError: I'm afraid it's not an old monster!

The has() method is used to intercept the HasProperty operation, that is, this method will take effect when determining whether an object has a property. A typical operation is the in operator.

The has() method can accept two parameters: the target object and the attribute name to be queried.

The following example uses the has() method to hide some properties from the in operator.

  let handler = {
    has(target, prop) {
      if(prop.indexOf('_') > -1) {
        return false;
      }
      return true;
    }
  }
  let person = {
    _name: "zhang",
    name: "Zhang San",
    old_name: "Zhang goudan"
  }
  let user = new Proxy(person, handler);

  "name" in user; // true
  "_name" in user; // false
  "old_name" in user; // false

The deleteProperty method is used to intercept the delete operation. If this method throws an error or returns false, the current property cannot be deleted by the delete command.

// Define dog egg information object
let person = {
  name: "son of a bitch",
  ID_number: "88888888",
  age: 25,
  gender: 'male'
}
// Create proxy object
let user = new Proxy(person, {
  deleteProperty(target, prop) {
    // Output information
    console.log(`I want to delete your ${prop}`);
    if(prop === 'name' || prop === 'ID_number') {
      throw new Error(`Second, Dad's ${prop}Can you delete it!!!`)
    }
    return true;
  }
})

delete user.age //  true
delete user.name // Uncaught Error: Er Huo, can you delete dad's name!!!

The ownKeys() method is used to intercept the reading operation of the object's own properties. Specifically, intercept the following operations.

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for...in cycle
// 1. Intercept object Examples of keys()
let person = {
  name: "son of a bitch",
  ID_number: "88888888",
  age: 25,
  gender: 'male'
} 

let user = new Proxy(person, {
  ownKeys(target) {
    // Returns a property that does not contain an underscore
    return Object.keys(target).filter(key => key.indexOf('_') < 0)
  }
})

Object.keys(user); //  ['name', 'age', 'gender']

// 2. Intercept object getOwnPropertyNames()
Object.getOwnPropertyNames(user); // ['name', 'age', 'gender']

// 3,for...in cycle
for (let key in user) {
  console.log(key); // name age gender
}
// Returns a property that you do not have
let user = new Proxy(person, {
  ownKeys(target) {
    return ['name', 'age', 'a', 'b']
  }
})
for (let key in user) {
  console.log(key); // name age 
}

The array members returned by the ownKeys() method can only be string or Symbol values. If there are other types of values, or the returned value is not an array at all, an error will be reported

The getownpropertydescriptor () method intercepts object getOwnPropertyDescriptor(), return a property description object or undefined.

const user = new Proxy(person, {
  getOwnPropertyDescriptor(target, prop) {
    if(prop === "ID_number") return;
    return Object.getOwnPropertyDescriptor(target, prop);
  }
})

Object.getOwnPropertyDescriptor(user, 'name');
// {configurable: true, enumerable: true, value: "dog egg", writable: true}
Object.getOwnPropertyDescriptor(user, 'ID_number');
// undefined

The defineProperty() method intercepted object defineProperty() operation.

  const user = new Proxy(person, {
    defineProperty (target, prop, desc) {
      return false;
    }
  })

  user.foo = "bar"; // Will not take effect

There is no operation inside the defineProperty() method, so adding a new property is always invalid.

Note: if the target object is non extensible, defineProperty() cannot add properties that do not exist on the target object, otherwise an error will be reported. In addition, if a property of the target object is not writable or configurable, the defineProperty() method must not change these two settings.

The preventextensions() method intercepts object preventExtensions(). The method must return a Boolean value, otherwise it will be automatically converted to a Boolean value.

This method has a limitation. Only when the target object is not extensible (that is, Object.isExtensible(proxy) is false), proxy Only preventextensions can return true, otherwise an error will be reported.

var user = new Proxy(person, {
  preventExtensions: function(target) {
    return true;
  }
});
Object.preventExtensions(user);
// Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible

In the above code, proxy The preventextensions () method returns true, but object Isextensible (proxy) will return true, so an error is reported.

In order to prevent this problem, it is usually in proxy In the preventextensions () method, call object once preventExtensions().

var user = new Proxy(person, {
  preventExtensions: function(target) {
    Object.preventExtensions(target);
    return true;
  }
});
Object.preventExtensions(user); // Proxy {...}

The getPrototypeOf() method is mainly used to intercept and obtain object prototypes. Specifically, intercept the following operations.

  • -Object.prototype.__proto__
  • -Object.prototype.isPrototypeOf()
  • -Object.getPrototypeOf()
  • -Reflect.getPrototypeOf()
  • -instanceof
// Define an object as common as you
var obj = {};
var user = new Proxy({}, {
  getPrototypeOf(target) {
    return obj;
  }
})
Object.getPrototypeOf(user) === obj // true

The getPrototypeOf() method intercepts object getPrototypeOf(), return obj object.

Note that the return value of getPrototypeOf() method must be object or null, otherwise an error will be reported. In addition, if the target object is not extensible, the getPrototypeOf() method must return the prototype object of the target object.

The setprototypeof () method is mainly used to intercept object setPrototypeOf() method.

var obj = {};
var target = function () {};
var handler = {
  setPrototypeOf (target, proto) {
    throw new Error('Changing the prototype is forbidden');
  }
};

var user = new Proxy(target, handler);
Object.setPrototypeOf(user, obj); // Error: Changing the prototype is forbidden

In the above code, as long as the prototype object of target is modified, an error will be reported.

Note that this method can only return Boolean values, otherwise it will be automatically converted to Boolean values. In addition, if the target object is not extensible, the setPrototypeOf() method must not change the prototype of the target object.

The isExtensible() method intercepts object isExtensible() operation. Object. The isextensible () method is used to determine whether an object is extensible.

var user = new Proxy({}, {
  isExtensible: function(target) {
    console.log("called");
    return true;
  }
});

Object.isExtensible(user); // called true

Note that this method can only return Boolean values, otherwise the returned values will be automatically converted to Boolean values.

This method has a strong limitation. Its return value must be consistent with the isExtensible property of the target object, or an error will be thrown.

The apply method intercepts function calls, calls, and apply operations.

The apply method can accept three parameters: the target object, the context object (this) of the target object, and the parameter array of the target object.

var person = function() {
  return "I am a more ordinary person than Zhang San";
}

var handler = {
  apply() {
    return "I was intercepted!"
  }
}

var user = new Proxy(person, handler);
user(); // 'I was intercepted!'
user.call(); // 'I was intercepted!'
user.apply(); // 'I was intercepted!'
Reflect.apply(user, null, []); // 'I was intercepted!'

In the above code, whenever the proxy function is executed (called directly or by call and apply), it will be intercepted by the apply method.

In addition, directly call reflect The apply method will also be intercepted.

The construct() method is used to intercept the new command. The following is the writing method of the intercepting object.

var user = new Proxy(function () {}, {
  construct: function(target, args) {
    console.log('called: ' + args.join(', '));
    return { name: args[0]};
  }
});
new user('Zhang San'); 
// called: Zhang San
// {name: 'Zhang San'}

Since the constructor is intercepted by construct(), its target object must be a function, or an error will be reported.
Note that this in the construct() method points to the handler, not the instance object.

example

The Proxy object can intercept any property of the target object, which makes it suitable for clients writing Web services.

const service = createWebService('http://exam.com/data');
service.employees().then(json => {
  // ....
})

Create a new web service interface. Proxy can intercept any attribute of this object, so you don't need to write adaptation methods for each kind of data, just use proxy to intercept.

function createWebService(baseUrl) {
  return new Proxy({}, {
    get(target, prop) {
      return Http.get(`${baseUrl}/{prop}`)
    },
    // ...
  });
}

Topics: Javascript ECMAScript