Detailed explanation of Proxy in JavaScript

Posted by rcatal02 on Sat, 25 Dec 2021 01:54:00 +0100

Zero. Introduction

reference

Corrected some errors and reorganized the editing and typesetting for personal learning only.

The origin of the problem

vue3.0 starts Proxy instead of object Defineproperty, causing some confusion.

  • What is Proxy?
  • What can Proxy do?
  • Vue uses object What did defineproperty do?
  • Why use Proxy instead of object defineProperty?

1, What is Proxy?

1.1 understanding Proxy

MDN definition: Proxy objects are used to define custom behaviors of basic operations (such as attribute lookup, assignment, enumeration, function call, etc.).

Generally speaking, Proxy is an object operation interceptor, which intercepts the operation of the target object and performs some custom behaviors. A layered idea is a bit similar to spring's AOP.

1.2 how to use proxy

let p = new Proxy(target, handler);

The syntax is very simple, with only two parameters, which is easy to understand

target

A target object wrapped in a Proxy (can be any type of object, including a native array, a function, or even another Proxy).

handler

An object whose properties are functions that define the behavior of an agent when an operation is performed.

var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35; 
  }
});

let obj = Object.create(proxy);
obj.time // 35
No action forwarding agent

The proxy object p will forward all operations applied to it to the target object target. You can directly operate on the proxy object, and the operations will be forwarded to the target object.

let target = {};
let p = new Proxy(target, {});

p.a = 37;   // Forward operation to destination

console.log(target.a);    // 37. The operation has been forwarded correctly

1.3 operations that can be intercepted

There are 13 kinds of surrogate operations. The code (property name / method name) of each operation and the way to trigger this operation are listed below. Note that if an operation is not defined, it will be forwarded to the target object.

  • handler.getPrototypeOf()

This operation is triggered when reading the prototype of the proxy object, such as when executing object Getprototypeof (proxy).

  • handler.setPrototypeOf()

This operation is triggered when setting the prototype of the proxy object, such as when executing object Setprototypeof (proxy, null).

  • handler.isExtensible()

This operation is triggered when determining whether a proxy object is extensible, such as executing object Isextensible (proxy).

  • handler.preventExtensions()

This operation is triggered when a proxy object is made non extensible, such as object When using preventextensions (proxy).

  • handler.getOwnPropertyDescriptor()

This operation is triggered when the property description of a property of the proxy object is obtained, such as object Getownpropertydescriptor (proxy, "foo").

  • handler.defineProperty()

This operation is triggered when defining an attribute description of a proxy object, for example, when executing object When defining property (proxy, "foo", {}).

  • handler.has()

This operation is triggered when determining whether the proxy object has a property, such as when executing "foo" in proxy.

  • handler.get()

This operation is triggered when reading a property of the proxy object, such as when executing proxy Foo.

  • handler.set()

This operation is triggered when assigning a value to a property of the proxy object, such as when executing proxy Foo = 1.

  • handler.deleteProperty()

This operation is triggered when a property of the proxy object is deleted, for example, when the delete proxy Foo.

  • handler.ownKeys()

This operation is triggered when all the attribute keys of the proxy object are obtained, such as object Getownpropertynames (proxy).

  • handler.apply()

Triggered when the target object is a function and is called.

  • handler.construct()

This operation is triggered when constructing an instance of a proxy object whose target object is a constructor, such as when executing new proxy().

1.4 direction of this

Special attention should be paid to this pointing in proxy objects and interception operations

Once the object is proxied, his this points to the proxy object

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};
 
const proxy = new Proxy(target, handler);
 
target.m() // false
proxy.m()  // true.  this of the objective function points to proxy

In the interception operation defined by the handler, this points to the handler and the receiver points to the proxy

const target = {
    m: 100
  };
  const handler = {
    get(target, property, receiver) {
      console.log(this === handler) // true.   this in the interception operation points to the handler
      console.log(receiver === proxy) // true.  The receiver points to the proxy object proxy
      return target[property]
    }
  };
 
  const proxy = new Proxy(target, handler);
  console.log(proxy.m)

2, What can Proxy do?

What can Proxy do with various commonly used interceptions? There are four commonly used operations

2.1 get()

The get method is used to intercept the read operation of a property. It can accept three parameters, namely, the target object, the property name and the proxy instance itself. All property calls will enter the same get, even if there are no properties.

  • You can create properties that you don't have
  • Data can be verified and converted when fetching data
  • You can customize some syntax operations
  • If get returns a function, it can convert a property into a method
var base = {
  a: 100,
  small: "hello world!!"
};

var proxy = new Proxy(base, {
  get(target, property, receiver) {
    //Property to function
    if ("fn" === property) {
      return function(value) {
        console.log(value);
      };
    }

    if ("ghost" === property) {
      return "ghost";
    }

    if ("fn" === property) {
      return function(value) {
        console.log(value);
      };
    }
    //Custom syntax sugar
    if (property.includes("_")) {
      const direct = property.split("_")[1];
      const propertyBase = property.split("_")[0];
      switch (direct) {
        case "Big":
          return receiver[propertyBase].toLocaleUpperCase();
        default:
          break;
      }
    }

    if (!(property in target)) {
      throw new ReferenceError('Property "' + property + '" does not exist.'); //Validate attribute values
    }

    return target[property];
  }
});

console.log(proxy.a)//Output 100 normal access
proxy.fn("fn")//Convert output fn attribute to method
console.log(proxy.small_Big)//Output HELLO WORLD!! Custom syntax sugar
console.log(proxy.ghost)//Output ghost create attribute
console.log(proxy.ghost_Big)//Use of output ghost receiver
console.log(proxy.b)//Error thrown during data validation

2.2 set()

The set method is used to intercept the assignment operation of an attribute. It can accept four parameters, namely, the target object, attribute name, attribute value and Proxy instance itself. The last parameter is optional.

  • It can be used to verify whether the attribute meets the requirements
  • Can be used to change the data format
  • Can be used to listen for data change events
  • Mask some assignment operations, such as "" Private variable at the beginning
var base = {
  a: 100,
  small: "hello world!!"
};

const A_Change_Event = "aChangeEvent";
window.addEventListener(A_Change_Event, e => {
  console.log(e.data.value);
});

var proxy = new Proxy(base, {
  set(obj, property, value, receiver) {
    if ("a" === property) {
      if (!Number.isInteger(value)) {
        throw new TypeError("The" + property + "is not an integer");
      }
      if (value > 100) {
        throw new RangeError("The " + property + " seems invalid");
      }

      obj[property] = value;

      //event
      const dataChangeEvent = new Event(A_Change_Event); //Create an event
      dataChangeEvent.data = { value };
      window.dispatchEvent(dataChangeEvent);
    }
  }
});
proxy.a = 80;
console.log(proxy.a);
proxy.a = 120;

2.3 apply()

The function object can also be the target object of the proxy

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.

The simplest example

var target = function () { return 'I am the target'; };
var handler = {
  apply: function () {
    return 'I am the proxy';
  }
};

var p = new Proxy(target, handler);

p()
// "I am the proxy"

2.4 construct ()

The new operation is intercepted by construct
Accept three parameters

  • Target: target object
  • args: parameter object of constructor
  • newTarget: the constructor used by the new command when creating an instance object

3, Proxy and object in Vue defineProperty

3.1 Object.defineProperty implements observe

Recursively traverse all attributes, using object Defineproperty defines listening one by one

var data = {name: 'kindeng'};
observe(data);
data.name = 'dmq'; // Hahaha, the listening value has changed, King -- > DMQ

function observe(data) {
    if (!data || typeof data !== 'object') {
        return;
    }
    // Take out all attribute traversal
    Object.keys(data).forEach(function(key) {
        defineReactive(data, key, data[key]);
    });
};

function defineReactive(data, key, val) {
    observe(val); // Listening sub attribute
    Object.defineProperty(data, key, {
        enumerable: true, // enumerable 
        configurable: false, // Can no longer define
        get: function() {
            return val;
        },
        set: function(newVal) {
            console.log('Hahaha, the monitoring value has changed ', val, ' --> ', newVal);
            val = newVal;
        }
    });
}

3.2 Proxy implementation observe

There is no need to traverse the binding one by one in advance. Judge by key every time

observe(data) {
    const that = this;
    let handler = {
    get(target, property) {
        return target[property];
    },
    set(target, key, value) {
        let res = Reflect.set(target, key, value);
        that.subscribe[key].map(item => {
          item.update();
        });
        return res;
      }
    }
    this.$data = new Proxy(data, handler);
  }

4, Why use Proxy instead of object defineProperty?

​ vue3.0 uses Proxy instead of traversing object The defineproperty method adds a set to a property, which is a clumsy way to get accessors. In other words, you should not traverse, but directly monitor the data object.

Topics: Javascript Front-end React TypeScript Vue.js