Proxy

Overview

Proxy is used to modify the default behavior of certain operations, which is equivalent to making changes at the language level, so it belongs to a kind of "meta programming" (meta programming), that is, programming a programming language.

Proxy can be understood as setting up a layer of "interception" in front of the target object. Any external access to the object must first pass through this layer. Therefore, it provides a mechanism to filter and rewrite external access. The original meaning of the word Proxy is proxy. It is used here to mean "proxy" certain operations, which can be translated as "proxy".

var obj = new Proxy(
  {},
  {
    get: function (target, propKey, receiver) {
      console.log(`getting ${propKey}!`);
      return Reflect.get(target, propKey, receiver);
    },
    set: function (target, propKey, value, receiver) {
      console.log(`setting ${propKey}!`);
      return Reflect.set(target, propKey, value, receiver);
    },
  }
);

The above code sets up a layer of interception for an empty object, and redefines the behavior of reading (get) and setting (set) of properties. The specific grammar will not be explained here for the time being, just look at the running results. For the object obj with the interception behavior set, to read and write its properties, you will get the following result.

obj.count = 1;
// setting count!
++obj.count;
// getting count!
// setting count!
// 2

The above code shows that Proxy actually overloads the dot operator, that is, it overwrites the original definition of the language with its own definition.

ES6 natively provides a Proxy constructor to generate Proxy instances.

var proxy = new Proxy(target, handler);

All uses of the Proxy object are in the above form, the only difference is the way the handler parameter is written. Among them, new Proxy() means to generate a Proxy instance, the target parameter indicates the target object to be intercepted, and the handler parameter is also an object to customize the interception behavior.

The following is another example of intercepting the behavior of reading attributes.

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

proxy.time; // 35
proxy.name; // 35
proxy.title; // 35

In the above code, as a constructor, Proxy accepts two parameters. The first parameter is the target object to be proxied (the above example is an empty object), that is, if there is no intervention of Proxy, the operation originally needs to access this object; the second parameter is a configuration object, for each The operation of the agent needs to provide a corresponding processing function, which will intercept the corresponding operation. For example, in the above code, the configuration object has a get method to intercept access requests to the properties of the target object. The two parameters of the get method are the target object and the property to be accessed. As you can see, since the interception function always returns 35, accessing any property will get 35.

Note that for Proxy to work, you must operate on the Proxy instance (the above example is the proxy object), not on the target object (the above example is an empty object).

If the handler does not set any interception, it is equivalent to directly leading to the original object.

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = "b";
target.a; // "b"

In the above code, handler is an empty object, without any interception effect, accessing proxy is equivalent to accessing target.

One trick is to set the Proxy object to the object.proxy property so that it can be called on the object object.

var object = { proxy: new Proxy(target, handler) };

Proxy instances can also be used as prototype objects for other objects.

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

let obj = Object.create(proxy);
obj.time; // 35

In the above code, the proxy object is the prototype of the obj object, and the obj object itself does not have a time attribute, so according to the prototype chain, this attribute will be read on the proxy object, resulting in interception.

The same interceptor function can be set to intercept multiple operations.

var handler = {
  get: function (target, name) {
    if (name === "prototype") {
      return Object.prototype;
    }
    return "Hello, " + name;
  },

  apply: function (target, thisBinding, args) {
    return args[0];
  },

  construct: function (target, args) {
    return { value: args[1] };
  },
};

var fproxy = new Proxy(function (x, y) {
  return x + y;
}, handler);

fproxy(1, 2); // 1
new fproxy(1, 2); // {value: 2}
fproxy.prototype === Object.prototype; // true
fproxy.foo === "Hello, foo"; // true

For operations that can be set but no interception is set, they fall directly on the target object and produce results in the original way.

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

-get(target, propKey, receiver): intercept the reading of object properties, such as proxy.foo and proxy['foo']. -set(target, propKey, value, receiver): intercept object property settings, such as proxy.foo = v or proxy['foo'] = v, return a boolean value. -has(target, propKey): Intercept the operation of propKey in proxy and return a boolean value. -deleteProperty(target, propKey): Intercept the operation of delete proxy[propKey] and return a Boolean value. -ownKeys(target): intercept Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy), for...in loop, and return an array . This method returns the property names of all its own properties of the target object, and the return result of Object.keys() only includes the traversable properties of the target object itself. -getOwnPropertyDescriptor(target, propKey): intercept Object.getOwnPropertyDescriptor(proxy, propKey) and return the description object of the property. -defineProperty(target, propKey, propDesc): intercept Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDescs), and return a boolean value. -preventExtensions(target): intercept Object.preventExtensions(proxy) and return a Boolean value. -getPrototypeOf(target): intercept Object.getPrototypeOf(proxy) and return an object. -isExtensible(target): intercept Object.isExtensible(proxy) and return a boolean value. -setPrototypeOf(target, proto): intercept Object.setPrototypeOf(proxy, proto) and return a boolean value. If the target object is a function, then there are two additional operations that can be intercepted. -apply(target, object, args): intercept Proxy instance as a function call operation, such as proxy(...args), proxy.call(object, ...args), proxy .apply(...). -construct(target, args): Intercept the operation called by the Proxy instance as a constructor, such as new proxy(...args).

Proxy instance method

The following is a detailed introduction of the above interception methods.

get()

The get method is used to intercept the read operation of an attribute. It can accept three parameters, which are the target object, the attribute name, and the proxy instance itself (strictly speaking, the object targeted by the operation). The last parameter can be selected.

There is an example of the usage of the get method above, and the following is another example of intercepting read operations.

var person = {
  name: "Zhang San",
};

var proxy = new Proxy(person, {
  get: function (target, propKey) {
    if (propKey in target) {
      return target[propKey];
    } else {
      throw new ReferenceError('Prop name "' + propKey + '" does not exist.');
    }
  },
});

proxy.name; // "Zhang San"
proxy.age; // throw an error

The above code indicates that if you access a non-existent property of the target object, an error will be thrown. Without this interception function, accessing non-existent properties will only return undefined.

The get method can be inherited.

let proto = new Proxy(
  {},
  {
    get(target, propertyKey, receiver) {
      console.log("GET " + propertyKey);
      return target[propertyKey];
    },
  }
);

let obj = Object.create(proto);
obj.foo; // "GET foo"

In the above code, the interception operation is defined on the Prototype object, so if the properties inherited by the obj object are read, the interception will take effect.

The following example uses get interception to read the index of a negative number from an array.

function createArray(...elements) {
  let handler = {
    get(target, propKey, receiver) {
      let index = Number(propKey);
      if (index < 0) {
        propKey = String(target.length + index);
      }
      return Reflect.get(target, propKey, receiver);
    },
  };

  let target = [];
  target.push(...elements);
  return new Proxy(target, handler);
}

let arr = createArray("a", "b", "c");
arr[-1]; // c

In the above code, the positional parameter of the array is -1, and the first member from the bottom of the array will be output.

Using Proxy, the operation of reading attributes (get) can be transformed into the execution of a certain function, so as to realize the chain operation of attributes.

var pipe = function (value) {
  var funcStack = [];
  var oproxy = new Proxy(
    {},
    {
      get: function (pipeObject, fnName) {
        if (fnName === "get") {
          return funcStack.reduce(function (val, fn) {
            return fn(val);
          }, value);
        }
        funcStack.push(window[fnName]);
        return oproxy;
      },
    }
  );

  return oproxy;
};

var double = (n) => n * 2;
var pow = (n) => n * n;
var reverseInt = (n) => n.toString().split("").reverse().join("") | 0;

pipe(3).double.pow.reverseInt.get; // 63

After setting the Proxy in the above code, the effect of chaining the function name is achieved.

The following example uses get interception to implement a general function dom that generates various DOM nodes.

const dom = new Proxy(
  {},
  {
    get(target, property) {
      return function (attrs = {}, ...children) {
        const el = document.createElement(property);
        for (let prop of Object.keys(attrs)) {
          el.setAttribute(prop, attrs[prop]);
        }
        for (let child of children) {
          if (typeof child === "string") {
            child = document.createTextNode(child);
          }
          el.appendChild(child);
        }
        return el;
      };
    },
  }
);

const el = dom.div(
  {},
  "Hello, my name is",
  dom.a({ href: "//example.com" }, "Mark"),
  ". I like:",
  dom.ul(
    {},
    dom.li({}, "The web"),
    dom.li({}, "Food"),
    dom.li({}, "…actually that's it")
  )
);

document.body.appendChild(el);

The following is an example of the third parameter of the get method. It always points to the object where the original read operation is located. In general, it is the Proxy instance.

const proxy = new Proxy(
  {},
  {
    get: function (target, key, receiver) {
      return receiver;
    },
  }
);
proxy.getReceiver === proxy; // true

In the above code, the getReceiver property of the proxy object is provided by the proxy object, so the receiver points to the proxy object.

const proxy = new Proxy(
  {},
  {
    get: function (target, key, receiver) {
      return receiver;
    },
  }
);

const d = Object.create(proxy);
da === d; // true

In the above code, the d object itself does not have the a attribute, so when reading da, it will go to the prototype proxy object of d to find it. At this time, receiver points to d, which represents the object where the original read operation is located.

If a property is not configurable and writable, the Proxy cannot modify the property, otherwise an error will be reported when the property is accessed through the Proxy object.

const target = Object.defineProperties(
  {},
  {
    foo: {
      value: 123,
      writable: false,
      configurable: false,
    },
  }
);

const handler = {
  get(target, propKey) {
    return "abc";
  },
};

const proxy = new Proxy(target, handler);

proxy.foo;
// TypeError: Invariant check failed

set()

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

Assuming that the Person object has an age property, which should be an integer no greater than 200, then you can use Proxy to ensure that the property value of age meets the requirements.

let validator = {
  set: function (obj, prop, value) {
    if (prop === "age") {
      if (!Number.isInteger(value)) {
        throw new TypeError("The age is not an integer");
      }
      if (value > 200) {
        throw new RangeError("The age seems invalid");
      }
    }

    // For age attributes and other attributes that meet the conditions, save directly
    obj[prop] = value;
    return true;
  },
};

let person = new Proxy({}, validator);

person.age = 100;

person.age; // 100
person.age = "young"; // report an error
person.age = 300; // error

In the above code, due to the setting of the stored value function set, any assignment of the age property that does not meet the requirements will throw an error. This is an implementation method of data verification. Using the set method, data binding is also possible, that is, whenever the object changes, the DOM will be automatically updated.

Sometimes, we will set internal attributes on the object. The first character of the attribute name starts with an underscore, indicating that these attributes should not be used externally. Combining the get and set methods can prevent these internal properties from being read and written externally.

const handler = {
  get(target, key) {
    invariant(key, "get");
    return target[key];
  },
  set(target, key, value) {
    invariant(key, "set");
    target[key] = value;
    return true;
  },
};
function invariant(key, action) {
  if (key[0] === "_") {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`);
  }
}
const target = {};
const proxy = new Proxy(target, handler);
proxy._prop;
// Error: Invalid attempt to get private "_prop" property
proxy._prop = "c";
// Error: Invalid attempt to set private "_prop" property

In the above code, as long as the first character of the attribute name to be read and written is an underscore, an error will be thrown, so as to achieve the purpose of prohibiting reading and writing of internal attributes.

The following is an example of the fourth parameter of the set method.

const handler = {
  set: function (obj, prop, value, receiver) {
    obj[prop] = receiver;
    return true;
  },
};
const proxy = new Proxy({}, handler);
proxy.foo = "bar";
proxy.foo === proxy; // true

In the above code, the fourth parameter receiver of the set method refers to the object where the original operation behavior is located. Generally, it is the proxy instance itself. Please see the following example.

const handler = {
  set: function (obj, prop, value, receiver) {
    obj[prop] = receiver;
    return true;
  },
};
const proxy = new Proxy({}, handler);
const myObj = {};
Object.setPrototypeOf(myObj, proxy);

myObj.foo = "bar";
myObj.foo === myObj; // true

In the above code, when setting the value of the myObj.foo property, myObj does not have the foo property, so the engine will go to the prototype chain of myObj to find the foo property. The prototype object proxy of myObj is a Proxy instance, and setting its foo property will trigger the set method. At this time, the fourth parameter receiver points to the object myObj where the original assignment behavior is located.

Note that if an attribute of the target object itself is not writable, the set method will not work.

const obj = {};
Object.defineProperty(obj, "foo", {
  value: "bar",
  writable: false,
});

const handler = {
  set: function (obj, prop, value, receiver) {
    obj[prop] = "baz";
    return true;
  },
};

const proxy = new Proxy(obj, handler);
proxy.foo = "baz";
proxy.foo; // "bar"

In the above code, the obj.foo property is not writable, and the set proxy of this property by Proxy will not take effect.

Note that the set proxy should return a boolean value. In strict mode, if the set proxy does not return true, an error will be reported.

"use strict";
const handler = {
  set: function (obj, prop, value, receiver) {
    obj[prop] = receiver;
    // Whether there is the following line or not, an error will be reported
    return false;
  },
};
const proxy = new Proxy({}, handler);
proxy.foo = "bar";
// TypeError:'set' on proxy: trap returned falsish for property'foo'

In the above code, in strict mode, if the set proxy returns false or undefined, an error will be reported.

apply()

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

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

var handler = {
  apply(target, ctx, args) {
    return Reflect.apply(...arguments);
  },
};

Below is an 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"

In the above code, the variable p is an instance of Proxy. When it is called as a function (p()), it will be intercepted by the apply method and return a string.

Here is another example.

var twice = {
  apply(target, ctx, args) {
    return Reflect.apply(...arguments) * 2;
  },
};
function sum(left, right) {
  return left + right;
}
var proxy = new Proxy(sum, twice);
proxy(1, 2); // 6
proxy.call(null, 5, 6); // 22
proxy.apply(null, [7, 8]); // 30

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

In addition, directly calling the Reflect.apply method will also be intercepted.

Reflect.apply(proxy, null, [9, 10]); // 38

has()

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

The has() method can accept two parameters, which are the target object and the property name to be queried.

The following example uses the has() method to hide certain attributes from being discovered by the in operator.

var handler = {
  has(target, key) {
    if (key[0] === "_") {
      return false;
    }
    return key in target;
  },
};
var target = { _prop: "foo", prop: "foo" };
var proxy = new Proxy(target, handler);
"_prop" in proxy; // false

In the above code, if the first character of the property name of the original object is an underscore, proxy.has() will return false, so it will not be found by the in operator.

If the original object is not configurable or expansion is forbidden, then the has() interception will report an error.

var obj = { a: 10 };
Object.preventExtensions(obj);

var p = new Proxy(obj, {
  has: function (target, prop) {
    return false;
  },
});

"a" in p; // TypeError is thrown

In the above code, the obj object is forbidden to expand. As a result, using has interception will report an error. In other words, if a property is not configurable (or the target object is not extensible), the has() method must not "hide" (ie return false) the property of the target object.

It is worth noting that the has() method intercepts the HasProperty operation, not the HasOwnProperty operation, that is, the has() method does not judge whether a property is an object's own property or an inherited property.

In addition, although the in operator is also used in the for...in loop, the has() interception does not take effect on the for...in loop.

let stu1 = { name: "Zhang San", score: 59 };
let stu2 = { name: "Li Si", score: 99 };

let handler = {
  has(target, prop) {
    if (prop === "score" && target[prop] < 60) {
      console.log(`${target.name} failed`);
      return false;
    }
    return prop in target;
  },
};

let oproxy1 = new Proxy(stu1, handler);
let oproxy2 = new Proxy(stu2, handler);

"score" in oproxy1;
// Zhang San failed
// false

"score" in oproxy2;
// true

for (let a in oproxy1) {
  console.log(oproxy1[a]);
}
// Zhang San
// 59

for (let b in oproxy2) {
  console.log(oproxy2[b]);
}
// Li Si
// 99

In the above code, the has() interception only takes effect for the in operator, and does not take effect for the for...in loop. As a result, the attributes that do not meet the requirements are not excluded by the for...in loop.

construct()

The construct() method is used to intercept the new command, the following is the way to intercept the object.

const handler = {
  construct(target, args, newTarget) {
    return new target(...args);
  },
};

The construct() method can accept three parameters.

-target: The target object. -args: The parameter array of the constructor. -newTarget: The constructor used by the new command when creating an instance object (p in the following example).

const p = new Proxy(function () {}, {
  construct: function (target, args) {
    console.log("called: " + args.join(","));
    return { value: args[0] * 10 };
  },
});

new p(1).value;
// "called: 1"
// 10

The construct() method must return an object, otherwise an error will be reported.

const p = new Proxy(function () {}, {
  construct: function (target, argumentsList) {
    return 1;
  },
});

new p(); // error
// Uncaught TypeError:'construct' on proxy: trap returned non-object ('1')

In addition, because construct() intercepts a constructor, its target object must be a function, otherwise an error will be reported.

const p = new Proxy(
  {},
  {
    construct: function (target, argumentsList) {
      return {};
    },
  }
);

new p(); // error
// Uncaught TypeError: p is not a constructor

In the above example, the intercepted target object is not a function, but an object (the first parameter of new Proxy()), resulting in an error.

Note that the this in the construct() method refers to the handler, not the instance object.

const handler = {
  construct: function (target, args) {
    console.log(this === handler);
    return new target(...args);
  },
};

let p = new Proxy(function () {}, handler);
new p(); // true

deleteProperty()

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.

var handler = {
  deleteProperty(target, key) {
    invariant(key, "delete");
    delete target[key];
    return true;
  },
};
function invariant(key, action) {
  if (key[0] === "_") {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`);
  }
}

var target = { _prop: "foo" };
var proxy = new Proxy(target, handler);
delete proxy._prop;
// Error: Invalid attempt to delete private "_prop" property

In the above code, the deleteProperty method intercepts the delete operator, and an error will be reported if the first character is an underscore property.

Note that the configurable properties of the target object itself cannot be deleted by the deleteProperty method, otherwise an error will be reported.

defineProperty()

The defineProperty() method intercepts the Object.defineProperty() operation.

var handler = {
  defineProperty(target, key, descriptor) {
    return false;
  },
};
var target = {};
var proxy = new Proxy(target, handler);
proxy.foo = "bar"; // will not take effect

In the above code, there is no operation inside the defineProperty() method, only false is returned, which makes adding new properties always invalid. Note that the false here is only used to indicate that the operation failed, and does not prevent the addition of new attributes by itself.

Note that 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.

getOwnPropertyDescriptor()

The getOwnPropertyDescriptor() method intercepts Object.getOwnPropertyDescriptor() and returns a property description object or undefined.

var handler = {
  getOwnPropertyDescriptor(target, key) {
    if (key[0] === "_") {
      return;
    }
    return Object.getOwnPropertyDescriptor(target, key);
  },
};
var target = { _foo: "bar", baz: "tar" };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, "wat");
// undefined
Object.getOwnPropertyDescriptor(proxy, "_foo");
// undefined
Object.getOwnPropertyDescriptor(proxy, "baz");
// {value:'tar', writable: true, enumerable: true, configurable: true}

In the above code, the handler.getOwnPropertyDescriptor() method will return undefined for the property name whose first character is underscore.

getPrototypeOf()

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

-Object.prototype.__proto__ -Object.prototype.isPrototypeOf() -Object.getPrototypeOf() -Reflect.getPrototypeOf() -instanceof

Below is an example.

var proto = {};
var p = new Proxy(
  {},
  {
    getPrototypeOf(target) {
      return proto;
    },
  }
);
Object.getPrototypeOf(p) === proto; // true

In the above code, the getPrototypeOf() method intercepts the Object.getPrototypeOf() and returns the proto object.

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

isExtensible()

The isExtensible() method intercepts the Object.isExtensible() operation.

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

Object.isExtensible(p);
// "called"
// true

The above code sets up the isExtensible() method, which will output called when calling Object.isExtensible.

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

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

Object.isExtensible(proxy) === Object.isExtensible(target);

Below is an example.

var p = new Proxy(
  {},
  {
    isExtensible: function (target) {
      return false;
    },
  }
);

Object.isExtensible(p);
// Uncaught TypeError:'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is'true')

ownKeys()

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

-Object.getOwnPropertyNames() -Object.getOwnPropertySymbols() -Object.keys() -for...in loop

The following is an example of intercepting Object.keys().

let target = {
  a: 1,
  b: 2,
  c: 3,
};

let handler = {
  ownKeys(target) {
    return ["a"];
  },
};

let proxy = new Proxy(target, handler);

Object.keys(proxy);
// ['a']

The above code intercepts the Object.keys() operation on the target object and only returns the a attribute among the three attributes a, b, and c.

The following example is to intercept the attribute name whose first character is underscore.

let target = {
  _bar: "foo",
  _prop: "bar",
  prop: "baz",
};

let handler = {
  ownKeys(target) {
    return Reflect.ownKeys(target).filter((key) => key[0] !== "_");
  },
};

let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {
  console.log(target[key]);
}
// "baz"

Note that when using the Object.keys() method, there are three types of attributes that will be automatically filtered by the ownKeys() method and will not be returned.

-Properties that do not exist on the target object -The attribute name is Symbol value -Non-traversable (enumerable) attributes

let target = {
  a: 1,
  b: 2,
  c: 3,
  [Symbol.for("secret")]: "4",
};

Object.defineProperty(target, "key", {
  enumerable: false,
  configurable: true,
  writable: true,
  value: "static",
});

let handler = {
  ownKeys(target) {
    return ["a", "d", Symbol.for("secret"), "key"];
  },
};

let proxy = new Proxy(target, handler);

Object.keys(proxy);
// ['a']

In the above code, the ownKeys() method explicitly returns non-existent attributes (d), Symbol values ​​(Symbol.for('secret')), and non-traversable attributes (key) , The results are automatically filtered out.

The ownKeys() method can also intercept Object.getOwnPropertyNames().

var p = new Proxy(
  {},
  {
    ownKeys: function (target) {
      return ["a", "b", "c"];
    },
  }
);

Object.getOwnPropertyNames(p);
// ['a','b','c']

The for...in loop is also intercepted by the ownKeys() method.

const obj = { hello: "world" };
const proxy = new Proxy(obj, {
  ownKeys: function () {
    return ["a", "b"];
  },
});

for (let key in proxy) {
  console.log(key); // No output
}

In the above code, ownkeys() specifies that only a and b attributes are returned. Since obj does not have these two attributes, there will be no output from the for...in loop.

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

var obj = {};

var p = new Proxy(obj, {
  ownKeys: function (target) {
    return [123, true, undefined, null, {}, []];
  },
});

Object.getOwnPropertyNames(p);
// Uncaught TypeError: 123 is not a valid property name

In the above code, although the ownKeys() method returns an array, each array member is not a string or Symbol value, so an error is reported.

If the target object itself contains a non-configurable property, the property must be returned by the ownKeys() method, otherwise an error will be reported.

var obj = {};
Object.defineProperty(obj, "a", {
  configurable: false,
  enumerable: true,
  value: 10,
});

var p = new Proxy(obj, {
  ownKeys: function (target) {
    return ["b"];
  },
});

Object.getOwnPropertyNames(p);
// Uncaught TypeError:'ownKeys' on proxy: trap result did not include'a'

In the above code, the a property of the obj object is not configurable. At this time, the array returned by the ownKeys() method must contain a, otherwise an error will be reported.

In addition, if the target object is non-extensible, the array returned by the ownKeys() method must contain all the attributes of the original object and cannot contain redundant attributes, otherwise an error will be reported.

var obj = {
  a: 1,
};

Object.preventExtensions(obj);

var p = new Proxy(obj, {
  ownKeys: function (target) {
    return ["a", "b"];
  },
});

Object.getOwnPropertyNames(p);
// Uncaught TypeError:'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible

In the above code, the obj object is not extensible. At this time, the array returned by the ownKeys() method contains the extra attribute b of the obj object, which causes an error.

preventExtensions()

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 (ie Object.isExtensible(proxy) is false), proxy.preventExtensions can return true, otherwise an error will be reported.

var proxy = new Proxy(
  {},
  {
    preventExtensions: function (target) {
      return true;
    },
  }
);

Object.preventExtensions(proxy);
// Uncaught TypeError:'preventExtensions' on proxy: trap returned truish but the proxy target is extensible

In the above code, the proxy.preventExtensions() method returns true, but at this time, Object.isExtensible(proxy) will return true, so an error is reported.

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

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

Object.preventExtensions(proxy);
// "called"
// Proxy {}

setPrototypeOf()

The setPrototypeOf() method is mainly used to intercept the Object.setPrototypeOf() method.

Below is an example.

var handler = {
  setPrototypeOf(target, proto) {
    throw new Error("Changing the prototype is forbidden");
  },
};
var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// 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 a Boolean value, otherwise it will be automatically converted to a Boolean value. In addition, if the target object is non-extensible, the setPrototypeOf() method must not change the prototype of the target object.

Proxy.revocable()

The Proxy.revocable() method returns a revocable Proxy instance.

let target = {};
let handler = {};

let { proxy, revoke } = Proxy.revocable(target, handler);

proxy.foo = 123;
proxy.foo; // 123

revoke();
proxy.foo; // TypeError: Revoked

The Proxy.revocable() method returns an object whose proxy property is a Proxy instance, and the revoke property is a function that can cancel a Proxy instance. In the above code, when the revoke function is executed and then the Proxy instance is accessed, an error will be thrown.

One use scenario of Proxy.revocable() is that the target object is not allowed to be accessed directly, and must be accessed through a proxy. Once the access is over, the proxy right is withdrawn and no further access is allowed.

this question

Although Proxy can proxy the access to the target object, it is not a transparent proxy for the target object, that is, without any interception, it cannot be guaranteed to be consistent with the target object's behavior. The main reason is that in the case of Proxy proxy, the keyword this inside the target object will point to the Proxy proxy.

const target = {
  m: function () {
    console.log(this === proxy);
  },
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m(); // false
proxy.m(); // true

In the above code, once proxy proxy target, this inside target.m() points to proxy instead of target.

The following is an example. Proxy cannot proxy the target object due to the change of this pointing.

const _name = new WeakMap();

class Person {
  constructor(name) {
    _name.set(this, name);
  }
  get name() {
    return _name.get(this);
  }
}

const jane = new Person("Jane");
jane.name; //'Jane'

const proxy = new Proxy(jane, {});
proxy.name; // undefined

In the above code, the name property of the target object jane is actually stored on the external WeakMap object _name, distinguished by the this key. When accessing through proxy.name, this points to proxy, so the value cannot be obtained, so undefined is returned.

In addition, the internal properties of some native objects can only be obtained through the correct this, so Proxy cannot proxy the properties of these native objects.

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate();
// TypeError: this is not a Date object.

In the above code, the getDate() method can only be obtained on the Date object instance. If this is not a Date object instance, an error will be reported. At this time, this problem can be solved by binding this to the original object.

const target = new Date("2015-01-01");
const handler = {
  get(target, prop) {
    if (prop === "getDate") {
      return target.getDate.bind(target);
    }
    return Reflect.get(target, prop);
  },
};
const proxy = new Proxy(target, handler);

proxy.getDate(); // 1

In addition, the this inside the Proxy interception function points to the handler object.

const handler = {
  get: function (target, key, receiver) {
    console.log(this === handler);
    return "Hello, " + key;
  },
  set: function (target, key, value) {
    console.log(this === handler);
    target[key] = value;
    return true;
  },
};

const proxy = new Proxy({}, handler);

proxy.foo;
// true
// Hello, foo

proxy.foo = 1;
// true

In the above example, the this inside the interception function of get() and set() all point to the handler object.

Example: Web service client

The Proxy object can intercept any attributes of the target object, which makes it very suitable for writing Web service clients.

const service = createWebService("http://example.com/data");

service.employees().then((json) => {
  const employees = JSON.parse(json);
  // ···
});

The above code creates a new web service interface, which returns various data. Proxy can intercept any attribute of this object, so there is no need to write an adaptation method for each type of data, just write a Proxy to intercept it.

function createWebService(baseUrl) {
  return new Proxy(
    {},
    {
      get(target, propKey, receiver) {
        return () => httpGet(baseUrl + "/" + propKey);
      },
    }
  );
}

In the same way, Proxy can also be used to implement the ORM layer of the database.