VDone Demo VDone Demo
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)

Nikolay Tuzov

Backend Developer
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)
  • Introduction to ECMAScript 6
  • let and const Commands
  • Destructuring Assignment of Variables
  • String Extensions
  • New String Methods
  • Regular Expression Extensions
  • Number Extensions
  • Function Extensions
  • Array Extensions
  • Object Extensions
  • 对象的新增方法
  • Symbol
  • Set 和 Map 数据结构
  • Proxy
    • Overview
    • Proxy Instance Methods
      • get()
      • set()
      • apply()
      • has()
      • construct()
      • deleteProperty()
      • defineProperty()
      • getOwnPropertyDescriptor()
      • getPrototypeOf()
      • isExtensible()
      • ownKeys()
      • preventExtensions()
      • setPrototypeOf()
    • Proxy.revocable()
    • The this Issue
    • Example: Web Service Client
  • Reflect
  • Promise 对象
  • Iterator 和 for-of 循环
  • Generator Function Syntax
  • Asynchronous Applications of Generator Functions
  • async Functions
  • Class 的基本语法
  • Class 的继承
  • Module 的语法
  • Module 的加载实现
  • 编程风格
  • 读懂 ECMAScript 规格
  • Async Iterator
  • ArrayBuffer
  • 最新提案
  • 装饰器
  • 函数式编程
  • Mixin
  • SIMD
  • 参考链接
  • 《ES6 教程》笔记
阮一峰
2020-02-09
Contents

Proxy

# Proxy

# Overview

Proxy is used to modify the default behavior of certain operations. It is equivalent to making changes at the language level, making it a form of "metaprogramming" -- programming the programming language itself.

Proxy can be understood as a layer of "interception" set up in front of the target object. All external access to that object must first pass through this interception layer, providing a mechanism to filter and rewrite external access. The word "Proxy" means an agent, and here it "proxies" certain operations.

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);
  }
});
1
2
3
4
5
6
7
8
9
10

The code above sets up an interception layer on an empty object, redefining the read (get) and set (set) behavior of its properties. Without explaining the specific syntax for now, let's look at the results. Reading or writing properties on the intercepted object obj produces the following results.

obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  2
1
2
3
4
5
6

The code above shows that Proxy actually overloads the dot operator, replacing the language's original definitions with custom ones.

ES6 natively provides the Proxy constructor for generating Proxy instances.

var proxy = new Proxy(target, handler);
1

All uses of Proxy objects follow this form; only the handler parameter varies. Here, new Proxy() generates a Proxy instance. The target parameter is the target object to be intercepted, and the handler parameter is also an object that defines the interception behavior.

Here is another example of intercepting property read operations.

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

proxy.time // 35
proxy.name // 35
proxy.title // 35
1
2
3
4
5
6
7
8
9

In the code above, Proxy accepts two arguments as a constructor. The first is the target object to be proxied (an empty object in the example above) -- the object that would normally be accessed without Proxy's involvement. The second is a configuration object that provides a handler function for each proxied operation. For example, the code above has a get method that intercepts requests to access properties on the target object. The get method takes two parameters: the target object and the property being accessed. As you can see, since the interceptor always returns 35, accessing any property returns 35.

Note that for Proxy to work, operations must be performed on the Proxy instance (the proxy object in the example above), not on the target object (the empty object in the example above).

If the handler has no interceptors set, operations pass through directly to the original object.

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
1
2
3
4
5

In the code above, handler is an empty object with no interception effects. Accessing proxy is equivalent to accessing target.

A useful trick is to set the Proxy object on an object.proxy property, allowing it to be called on the object object.

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

Proxy instances can also serve as prototype objects for other objects.

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

let obj = Object.create(proxy);
obj.time // 35
1
2
3
4
5
6
7
8

In the code above, proxy is the prototype of obj. Since obj itself does not have a time property, it looks up the property on the proxy object via the prototype chain, which triggers the interception.

The same interceptor function can be set up 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

For operations that can be intercepted but have no interceptors set, they fall through to the target object and produce results in the original way.

Below is a complete list of interception operations supported by Proxy -- 13 in total.

  • get(target, propKey, receiver): Intercepts reading of object properties, e.g., proxy.foo and proxy['foo'].
  • set(target, propKey, value, receiver): Intercepts setting of object properties, e.g., proxy.foo = v or proxy['foo'] = v, returns a boolean.
  • has(target, propKey): Intercepts propKey in proxy operations, returns a boolean.
  • deleteProperty(target, propKey): Intercepts delete proxy[propKey] operations, returns a boolean.
  • ownKeys(target): Intercepts Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy), and for...in loops, returns an array. This method returns all own property names of the target object, while Object.keys() returns only the target object's own enumerable properties.
  • getOwnPropertyDescriptor(target, propKey): Intercepts Object.getOwnPropertyDescriptor(proxy, propKey), returns the property's descriptor object.
  • defineProperty(target, propKey, propDesc): Intercepts Object.defineProperty(proxy, propKey, propDesc) and Object.defineProperties(proxy, propDescs), returns a boolean.
  • preventExtensions(target): Intercepts Object.preventExtensions(proxy), returns a boolean.
  • getPrototypeOf(target): Intercepts Object.getPrototypeOf(proxy), returns an object.
  • isExtensible(target): Intercepts Object.isExtensible(proxy), returns a boolean.
  • setPrototypeOf(target, proto): Intercepts Object.setPrototypeOf(proxy, proto), returns a boolean. If the target object is a function, two additional operations can be intercepted.
  • apply(target, object, args): Intercepts Proxy instance invocations as a function, e.g., proxy(...args), proxy.call(object, ...args), proxy.apply(...).
  • construct(target, args): Intercepts Proxy instance invocations as a constructor, e.g., new proxy(...args).

# Proxy Instance Methods

Below are detailed descriptions of the interception methods listed above.

# get()

The get method intercepts read operations on a property. It accepts three parameters: the target object, the property name, and the proxy instance itself (strictly speaking, the object at which the operation is targeted), with the last parameter being optional.

The usage of get was shown in an earlier example. Below is another example of intercepting a read operation.

var person = {
  name: "张三"
};

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 // "张三"
proxy.age // Throws an error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

The code above shows that if you access a non-existent property on the target object, an error is thrown. Without this interceptor, accessing a non-existent property would simply 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"
1
2
3
4
5
6
7
8
9

In the code above, the interceptor is defined on the Prototype object, so it takes effect when reading inherited properties of obj.

The following example uses get interception to enable reading negative indices 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

In the code above, an index of -1 outputs the last element of the array.

Using Proxy, you can transform property read operations (get) into function executions, enabling chained property operations.

var pipe = (function () {
  return 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

The code above achieves the effect of chaining function names after setting up Proxy.

The following example uses get interception to implement a generic 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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Here is an example of the get method's third parameter, which always points to the object where the original read operation is located -- generally the Proxy instance.

const proxy = new Proxy({}, {
  get: function(target, key, receiver) {
    return receiver;
  }
});
proxy.getReceiver === proxy // true
1
2
3
4
5
6

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

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

const d = Object.create(proxy);
d.a === d // true
1
2
3
4
5
6
7
8

In the code above, d does not have an a property, so reading d.a will look for it on d's prototype proxy. At this point, receiver points to d, representing the object where the original read operation is located.

If a property is non-configurable and non-writable, Proxy cannot modify that property; otherwise, accessing it through the Proxy object will throw an error.

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# set()

The set method intercepts assignment operations on a property. It accepts four parameters: the target object, the property name, the property value, and the Proxy instance itself, with the last parameter being optional.

Suppose a Person object has an age property that should be an integer no greater than 200. You can use Proxy to ensure the age property value 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 properties that meet the conditions and other properties, save directly
    obj[prop] = value;
  }
};

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

person.age = 100;

person.age // 100
person.age = 'young' // Error
person.age = 300 // Error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

In the code above, since the set function is configured, any age property assignment that doesn't meet the requirements will throw an error. This is one way to implement data validation. The set method can also be used for data binding, automatically updating the DOM whenever the object changes.

Sometimes we set internal properties on an object, with property names starting with an underscore, indicating that these properties should not be used externally. Combining get and set methods allows you to prevent these internal properties from being read or 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

In the code above, any read or write of a property whose name starts with an underscore will throw an error, effectively preventing read/write access to internal properties.

Here is an example of the set method's fourth parameter.

const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = receiver;
  }
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
proxy.foo === proxy // true
1
2
3
4
5
6
7
8

In the code above, the fourth parameter receiver of the set method refers to the object where the original operation is located -- generally the proxy instance itself. See the following example.

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

myObj.foo = 'bar';
myObj.foo === myObj // true
1
2
3
4
5
6
7
8
9
10
11

In the code above, when setting the foo property on myObj, since myObj doesn't have a foo property, the engine looks up the prototype chain for foo. myObj's prototype proxy is a Proxy instance, and setting its foo property triggers the set method. At this point, the fourth parameter receiver points to the object where the original assignment operation takes place, myObj.

Note that if the target object's own property is non-writable and non-configurable, the set method will not take effect.

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

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

const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
proxy.foo // "bar"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

In the code above, obj.foo is non-writable, so the Proxy's set proxy for this property will not take effect.

Note that in strict mode, if the set proxy does not return true, it will throw an error.

'use strict';
const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = receiver;
    // Whether or not the line below is present, an error will be thrown
    return false;
  }
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
// TypeError: 'set' on proxy: trap returned falsish for property 'foo'
1
2
3
4
5
6
7
8
9
10
11

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

# apply()

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

The apply method accepts three parameters: the target object, the context object (this) of the target object, and the target object's argument array.

var handler = {
  apply (target, ctx, args) {
    return Reflect.apply(...arguments);
  }
};
1
2
3
4
5

Here 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"
1
2
3
4
5
6
7
8
9
10
11

In the code above, when the variable p (a Proxy instance) is called as a function (p()), it is intercepted by the apply method, which returns 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
1
2
3
4
5
6
7
8
9
10
11
12

In the code above, every time the proxy function is executed (direct call, call, or apply), it is intercepted by the apply method.

Additionally, calling Reflect.apply directly is also intercepted.

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

# has()

The has method intercepts HasProperty operations -- that is, it takes effect when checking whether an object has a certain property. A typical operation is the in operator.

The has method accepts two parameters: the target object and the property name to query.

The following example uses the has method to hide certain properties from 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
1
2
3
4
5
6
7
8
9
10
11

In the code above, if the property name starts with an underscore, proxy.has returns false, so it won't be found by the in operator.

If the original object is non-configurable or non-extensible, the has interception will throw 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
1
2
3
4
5
6
7
8
9
10

In the code above, obj is non-extensible, so using has interception throws an error. In other words, if a property is non-configurable (or the target object is non-extensible), the has method must not "hide" (return false for) that property of the target object.

It's worth noting that the has method intercepts HasProperty operations, not HasOwnProperty operations -- meaning has does not distinguish between a property that is the object's own or inherited.

Also, although for...in loops also use the in operator, has interception does not affect for...in loops.

let stu1 = {name: '张三', score: 59};
let stu2 = {name: '李四', 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
// 张三 failed
// false

'score' in oproxy2
// true

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

for (let b in oproxy2) {
  console.log(oproxy2[b]);
}
// 李四
// 99
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

In the code above, the has interception only affects the in operator and does not affect the for...in loop, so properties that don't meet the requirements are not excluded by the for...in loop.

# construct()

The construct method intercepts the new command. Here is how to write the interceptor object.

var handler = {
  construct (target, args, newTarget) {
    return new target(...args);
  }
};
1
2
3
4
5

The construct method accepts two parameters:

  • target: The target object
  • args: The constructor's argument object
  • newTarget: The constructor that the new command acts on when creating the instance (the p in the example below)
var 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
1
2
3
4
5
6
7
8
9
10

The construct method must return an object; otherwise, it will throw an error.

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

new p() // Error
// Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1')
1
2
3
4
5
6
7
8

# deleteProperty()

The deleteProperty method intercepts delete operations. 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

In the code above, the deleteProperty method intercepts the delete operator, throwing an error when trying to delete properties starting with an underscore.

Note that non-configurable properties on the target object cannot be deleted by the deleteProperty method; otherwise, an error will be thrown.

# defineProperty()

The defineProperty method intercepts Object.defineProperty operations.

var handler = {
  defineProperty (target, key, descriptor) {
    return false;
  }
};
var target = {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar' // Has no effect
1
2
3
4
5
6
7
8

In the code above, defineProperty returns false, causing adding new properties to always have no effect.

Note that if the target object is non-extensible, defineProperty cannot add properties that don't exist on the target object; otherwise, an error will be thrown. Also, if the target object has a property that is non-writable or non-configurable, the defineProperty method must not change these two settings.

# getOwnPropertyDescriptor()

The getOwnPropertyDescriptor method intercepts Object.getOwnPropertyDescriptor(), returning a property descriptor 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 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

In the code above, handler.getOwnPropertyDescriptor returns undefined for properties whose names start with an underscore.

# getPrototypeOf()

The getPrototypeOf method is mainly used to intercept operations that get the object's prototype. Specifically, it intercepts the following operations:

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

Here is an example.

var proto = {};
var p = new Proxy({}, {
  getPrototypeOf(target) {
    return proto;
  }
});
Object.getPrototypeOf(p) === proto // true
1
2
3
4
5
6
7

In the code above, getPrototypeOf intercepts Object.getPrototypeOf(), returning the proto object.

Note that getPrototypeOf must return either an object or null; otherwise, it will throw an error. Also, if the target object is non-extensible, getPrototypeOf must return the target object's actual prototype object.

# isExtensible()

The isExtensible method intercepts Object.isExtensible operations.

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

Object.isExtensible(p)
// "called"
// true
1
2
3
4
5
6
7
8
9
10

The code above sets up an isExtensible method that outputs called when Object.isExtensible is invoked.

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

This method has a strong constraint: its return value must be consistent with the target object's isExtensible property; otherwise, it will throw an error.

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

Here 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')
1
2
3
4
5
6
7
8

# ownKeys()

The ownKeys method intercepts reading of the object's own properties. Specifically, it intercepts the following operations:

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

Here 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' ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

The code above intercepts Object.keys() on the target object, returning only the a property out of a, b, and c.

The following example intercepts property names that start with an 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"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Note that when using Object.keys, three types of properties are automatically filtered out by the ownKeys method and will not be returned:

  • Properties that don't exist on the target object
  • Properties whose names are Symbol values
  • Non-enumerable (enumerable) properties
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']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

In the code above, the ownKeys method explicitly returns a non-existent property (d), a Symbol value (Symbol.for('secret')), and a non-enumerable property (key), all of which 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' ]
1
2
3
4
5
6
7
8

for...in loops are also subject to ownKeys interception.

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

for (let key in proxy) {
  console.log(key); // No output
}
1
2
3
4
5
6
7
8
9
10

In the code above, ownKeys specifies that only a and b properties should be returned. Since obj doesn't have these two properties, the for...in loop produces no output.

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 return is not an array at all, an error will be thrown.

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
1
2
3
4
5
6
7
8
9
10

In the code above, although the ownKeys method returns an array, none of the array members are strings or Symbol values, so an error is thrown.

If the target object itself contains non-configurable properties, those properties must be returned by the ownKeys method; otherwise, an error will be thrown.

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'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

In the code above, obj's a property is non-configurable, so the array returned by ownKeys must include a; otherwise, an error is thrown.

Additionally, if the target object is non-extensible, the array returned by ownKeys must contain all properties of the original object and must not contain extra properties; otherwise, an error will be thrown.

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14

In the code above, obj is non-extensible, and the array returned by ownKeys contains b, an extra property not on obj, which causes the error.

# preventExtensions()

The preventExtensions method intercepts Object.preventExtensions(). This method must return a boolean; otherwise, it will be automatically converted to a boolean.

This method has a constraint: only when the target object is non-extensible (i.e., Object.isExtensible(proxy) is false) can proxy.preventExtensions return true; otherwise, an error will be thrown.

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
1
2
3
4
5
6
7
8

In the code above, proxy.preventExtensions returns true, but Object.isExtensible(proxy) would also return true, causing the error.

To prevent this, you should typically call Object.preventExtensions inside the proxy.preventExtensions method.

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

Object.preventExtensions(proxy)
// "called"
// Proxy {}
1
2
3
4
5
6
7
8
9
10
11

# setPrototypeOf()

The setPrototypeOf method is mainly used to intercept Object.setPrototypeOf.

Here 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
1
2
3
4
5
6
7
8
9
10

In the code above, any attempt to modify target's prototype object will throw an error.

Note that this method can only return a boolean; otherwise, the return value will be automatically converted to a boolean. Also, if the target object is non-extensible, setPrototypeOf must not change the target object's prototype.

# 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
1
2
3
4
5
6
7
8
9
10

Proxy.revocable returns an object whose proxy property is the Proxy instance and whose revoke property is a function that can revoke the Proxy instance. In the code above, after the revoke function is called, accessing the Proxy instance throws an error.

A use case for Proxy.revocable is when the target object should not be accessed directly and must be accessed through a proxy, and once access is complete, the proxy access is revoked.

# The this Issue

Although Proxy can proxy access to the target object, it is not a transparent proxy of the target object -- meaning that even without any interception, it cannot guarantee behavior identical to the target object. The main reason is that inside a Proxy, the this keyword inside the target object points to the Proxy.

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

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true
1
2
3
4
5
6
7
8
9
10
11

In the code above, once proxy proxies target.m, the this inside the latter points to proxy, not target.

Here is an example where a change in this pointing causes Proxy to be unable to proxy the target object.

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

In the code above, the name property of the target object jane is actually stored on the external WeakMap object _name, differentiated by the this key. Since this points to proxy when accessed via proxy.name, the value cannot be retrieved, so undefined is returned.

Additionally, some native objects have internal properties that can only be accessed through the correct this, so Proxy cannot proxy these native object properties.

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

proxy.getDate();
// TypeError: this is not a Date object.
1
2
3
4
5
6

In the code above, the getDate method can only be called on a Date object instance. If this is not a Date instance, it will throw an error. In this case, binding this to the original object solves the problem.

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
1
2
3
4
5
6
7
8
9
10
11
12

# Example: Web Service Client

A Proxy object can intercept any property of the target object, making it very suitable for writing web service clients.

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

service.employees().then(json => {
  const employees = JSON.parse(json);
  // ···
});
1
2
3
4
5
6

The code above creates a web service interface that returns various data. Proxy can intercept any property of this object, so you don't need to write an adapter method for each type of data -- just one Proxy interceptor is enough.

function createWebService(baseUrl) {
  return new Proxy({}, {
    get(target, propKey, receiver) {
      return () => httpGet(baseUrl + '/' + propKey);
    }
  });
}
1
2
3
4
5
6
7

Similarly, Proxy can also be used to implement the ORM layer for a database.

Edit (opens new window)
#ES6
Last Updated: 2026/03/21, 12:14:36
Set 和 Map 数据结构
Reflect

← Set 和 Map 数据结构 Reflect→

Recent Updates
01
How I Discovered Disposable Email — A True Story
06-12
02
Animations in Grid Layout
09-15
03
Renaming a Git Branch
08-11
More Articles >
Theme by VDone | Copyright © 2026-2026 Nikolay Tuzov | MIT License | Telegram
  • Auto
  • Light Mode
  • Dark Mode
  • Reading Mode