completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Proxy

Learn how JavaScript Proxy objects intercept reads, writes, calls, construction, object inspection, validation, revocation, and the invariants engines still enforce.

By the end, you’ll be able to
  • 01
    Name the partsExplain what the target, the handler, and a trap are, and what forwarding means.
  • 02
    Pick the right trapSay which of the 13 traps runs for everyday code like proxy.name or delete proxy.name.
  • 03
    Use proxies safelyBuild proxies that check values, watch for changes, and can be turned off, without breaking the language’s rules.

Proxies in one sentence

A Proxy is an object that stands in front of another object. The object behind it is called the target. The target can also be a function. Your code talks to the proxy, not to the target.

The proxy has a handler. The handler is a plain object with rules in it. A rule can catch an action before the action reaches the target. These are some of the actions a rule can catch:

  • reading a property, like proxy.name
  • writing a property, like proxy.name = "Mina"
  • checking a property with in
  • deleting a property
  • listing the keys
  • calling a function, or using new
The main idea

new Proxy(target, handler) makes a proxy. The handler's methods are called traps. Each trap catches one kind of action on the proxy and decides what happens.

Reading proxy.name. Your code asks the proxy. The proxy holds a get trap. The trap passes the read on to the target cart, which holds name Ada.proxy.nameyour codeget trapproxythe handler's trapname: "Ada"target
Your code never touches the target. It asks the proxy, and the proxy's get trap decides what happens. Here the trap passes the read on, so you get "Ada".

This lesson uses ideas from four earlier lessons:

A getter or setter controls one property. A proxy can watch the whole object.

Real-life analogyA proxy is a personal assistant

A busy boss never answers the phone. Every message goes to the assistant first. If the assistant has a rule for that kind of message, the assistant follows the rule. If there is no rule, the message goes straight to the boss.

In real life: The boss
In JavaScript: The target object or function
In real life: The assistant at the door
In JavaScript: The proxy, which your code talks to
In real life: A request to read, write, delete, call, or hire
In JavaScript: An action that runs a trap, such as get, set, deleteProperty, apply, or construct
In real life: The assistant’s rule book
In JavaScript: The handler object full of traps

Code that changes how other code works with an object is called metaprogramming. A proxy is one tool for it. People use proxies to:

  • check values before they are saved
  • write down every action in a log
  • update the page when data changes
  • limit what other code is allowed to do

Proxies are easy to overuse, so this lesson also shows their limits.

Handlers and traps

INTERACTIVE

Every proxy has two parts:

  • The target: the real object the proxy stands in front of.
  • The handler: a plain object with methods in it.

The handler's method names are special. A method named get runs when someone reads a property. A method named set runs when someone writes a property. Each of these methods is called a trap, because the action falls into it before it reaches the target.

Real-life analogyTraps are the assistant’s rule book

The assistant does not invent a new rule for each visitor. The rule book has one page for each kind of request. A handler works the same way: one trap name for each kind of action.

In real life: Rule: screen all phone calls
In JavaScript: apply for function calls
In real life: Rule: check the visitor list
In JavaScript: has for the in operator
In real life: Rule: refuse bad paperwork
In JavaScript: set that checks a new value
In real life: No rule for a request
In JavaScript: The action goes to the target as usual

A proxy is not the only way to control an object. The table compares it with two tools from earlier lessons. Read each row as: "this tool catches this, it is good for this, and watch out for this."

A proxy compared with getters, setters, and property flags
ToolWhat does it catch?What is it good for?What should you watch out for?
Getter or setterIt catches reads or writes of one named property.Values worked out on read, like user.fullName, or checked on write.It can’t see in, delete, Object.keys, calls, or new.
ProxyIt catches many kinds of actions on the whole target.Logging, checking values, watching for changes, and limiting access.It must keep the language’s rules. It can break private fields and Map or Date methods.
Property flagsIt catches nothing. Flags only say if a property can be changed, listed, or removed.Locking or hiding one property.It runs no code of your own.

A trap often needs to do the normal action too, after its own work. The Reflect object has one method for each trap, with the same name. Each method does the normal action. For example, Reflect.get(target, property, receiver) does a normal property read. Handing the action on to the target like this is called forwarding. The next lesson, Reflect, covers these methods.

A get trap that passes the read onPop out in the code editor (opens in a new tab)JavaScript
const account = {
  first: "Asha",
  last: "Khan",
  get label() {
    return this.first + " " + this.last;
  },
};
const proxy = new Proxy(account, {
  get(target, property, receiver) {
    return Reflect.get(target, property, receiver);
  },
});
console.log(proxy.label);

A get trap receives three values:

  • target: the real object, here account.
  • property: the name being read, here "label".
  • receiver: the object the read started from. Here it is the proxy.

Line 4 makes a getter: a method that runs when you read a property. It uses this. Line 10 passes the receiver on, so inside the getter this is the proxy. Line 13 prints Asha Khan.

If you leave the receiver out, this inside the getter becomes the target. Then the reads inside the getter skip the proxy and its traps.

All 13 traps and the code that runs them

TRAP LOGGER

JavaScript has exactly 13 traps. The first 11 work on any object. The last two only work when the target is a function:

  • apply runs when you call the proxy, like proxy(2, 3).
  • construct runs when you use new on the proxy, like new proxy("Ada").

Read each row of the table as: "this trap runs when you write this code, and it must give back this."

The 13 traps and the code that runs each one
TrapWhat code runs it?What must the trap give back?
getReading a property: proxy.name. Calling a method reads it first, too.The value to read.
setWriting a property: proxy.name = value.true or false. In strict mode, false throws.
hasChecking with in: "name" in proxy.true or false.
deletePropertyDeleting: delete proxy.name.true or false.
ownKeysListing keys: Object.keys, Reflect.ownKeys, or spreading { ...proxy }.A list of keys (strings or symbols).
getOwnPropertyDescriptorReading a property’s settings: Object.getOwnPropertyDescriptor. Object.keys runs it too.The settings object, or undefined.
definePropertyAdding a property with settings: Object.defineProperty.true or false.
getPrototypeOfAsking for the prototype: Object.getPrototypeOf.An object, or null.
setPrototypeOfChanging the prototype: Object.setPrototypeOf.true or false.
isExtensibleAsking if new properties can be added: Object.isExtensible.true or false, the same answer the target gives.
preventExtensionsStopping new properties: Object.preventExtensions.true or false. If true, the target must really stop taking new properties.
applyCalling the proxy: proxy(...).The result of the call. The target must be a function.
constructUsing new: new proxy(...).An object. The target must work with new, like a class.
Give missing settings a defaultPop out in the code editor (opens in a new tab)JavaScript
const settings = { theme: "light" };
const proxy = new Proxy(settings, {
  get(target, property, receiver) {
    return Reflect.has(target, property)
      ? Reflect.get(target, property, receiver)
      : "not set";
  },
});
console.log(proxy.theme);
console.log(proxy.language);

The get trap first checks if the setting exists. Reflect.has does the same check as the in operator.

  • Line 9 reads theme. It exists, so the trap passes the read on, and the line prints light.
  • Line 10 reads language. It does not exist, so the trap gives back not set instead.

The target never gets a language property. The value only shows up when you read it through the proxy.

The playground below is the main experiment of this lesson. Its handler has all 13 traps. Each trap writes a note, then passes the action on. Press a button to run one action and see which traps ran, in order.

Some actions run more than one trap. For example, Object.keys(proxy) first runs ownKeys to get the list of keys. Then it runs getOwnPropertyDescriptor for each key. That reads the key's settings, so Object.keys can keep only the keys that show up in loops. Those keys are called enumerable.

Trap playground: which traps run?
The handler has all 13 trapsPop out in the code editor (opens in a new tab)JavaScript
const target = { name: "Ada", role: "admin" };const handler = {  get(target, property, receiver) {    return Reflect.get(target, property, receiver);  },  set(target, property, value, receiver) {    return Reflect.set(target, property, value, receiver);  },  has(target, property) {    return Reflect.has(target, property);  },  deleteProperty(target, property) {    return Reflect.deleteProperty(target, property);  },  ownKeys(target) {    return Reflect.ownKeys(target);  },  getOwnPropertyDescriptor(target, property) {    return Reflect.getOwnPropertyDescriptor(target, property);  },  defineProperty(target, property, descriptor) {    return Reflect.defineProperty(target, property, descriptor);  },  getPrototypeOf(target) {    return Reflect.getPrototypeOf(target);  },  setPrototypeOf(target, prototype) {    return Reflect.setPrototypeOf(target, prototype);  },  isExtensible(target) {    return Reflect.isExtensible(target);  },  preventExtensions(target) {    return Reflect.preventExtensions(target);  },  apply(target, thisArg, args) {    return Reflect.apply(target, thisArg, args);  },  construct(target, args, newTarget) {    return Reflect.construct(target, args, newTarget);  },};const proxy = new Proxy(target, handler);
The action you just ranPop out in the code editor (opens in a new tab)JavaScript
proxy.name;
Trap log1 call
  1. get gets: "name" returns: "Ada"
Result of the action"Ada"
Target's own data[["name","Ada"],["role","admin"]]

All 13 traps: get, set, has, deleteProperty, ownKeys, getOwnPropertyDescriptor, defineProperty, getPrototypeOf, setPrototypeOf, isExtensible, preventExtensions, apply, construct. A handler can leave any of them out. Then that action goes to the target and works as usual.

Try it yourself

Read proxy.name ran these traps, in order: get.

Each button makes a fresh target and proxy, runs one real action, and lists the traps that ran, in order.

A handler doesn't need all 13 traps. If a trap is missing, the action goes to the target and works as usual. Real proxy code often has just one or two traps, and uses Reflect inside them.

Write down every property readPop out in the code editor (opens in a new tab)JavaScript
const user = { name: "Asha", role: "editor" };
const accessLog = [];
const proxy = new Proxy(user, {
  get(target, property, receiver) {
    accessLog.push(String(property));
    return Reflect.get(target, property, receiver);
  },
});
proxy.name;
proxy.role;
console.log(accessLog.join(", "));

Each read runs the get trap. Line 5 adds the property name to accessLog. Line 6 passes the read on. Lines 9 and 10 read name and role, so line 11 prints name, role.

A log like this helps you find reads you didn't expect. But a trap that runs on every read also makes the code slower and harder to debug.

Which group does each line of code belong to?
  • proxy.total
  • proxy.total = 3
  • "total" in proxy
  • delete proxy.total
  • Object.keys(proxy)
  • Object.defineProperty(proxy, "x", desc)
  • Object.getPrototypeOf(proxy)
  • Object.preventExtensions(proxy)
  • proxy(2, 3)
  • new proxy("Ada")
Try it yourself
0 of 10 correct

Sort each line by the kind of action it does to the proxy.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Checking values before they are saved

STEP THROUGH

The set trap runs before a new value goes into the object. So it is a good place to check the value first. Checking values like this is called validation.

A set trap must also report if the write worked:

  • Return true when the write worked.
  • Return false when it did not.

In strict mode, a false answer makes JavaScript throw a TypeError. Strict mode is the stricter version of JavaScript that the line "use strict" turns on. Forgetting to return anything counts as false too. The trap then returns undefined, which is falsy: a value that acts like false.

The code below checks age in a set trap. Choose a value, then step through it.

Step through a set trap
Step 0 of 8Ready
Your turn: follow the blue line

Choose a value, then step through the set trap. The replay is recorded from a real run.

Running in
  1. script
Next: line 2
"use strict";const userProxy = new Proxy(user, {  set(target, property, value, receiver) {    if (property === "age") {      if (typeof value !== "number") {        throw new TypeError("age must be a number");      }      if (value < 0) {        throw new TypeError("age must be non-negative");      }    }    return Reflect.set(target, property, value, receiver);  },}); userProxy.age = 36;console.log(user.age);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the value line 17 writes

Changing the value restarts the replay. Each replay is recorded from a real run.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Left: userProxy.age = 36 goes through the set trap into the user cart, which now holds age 36. Right: userProxy.age = -1 stops at the set trap with a TypeError, and the user cart still holds age 0.userProxy.age = 36set trapage: 36usersaveduserProxy.age = -1set trapTypeErrorage: 0userage stays 0
Both writes go through the same set trap. 36 passes the checks and is saved; -1 throws a TypeError before the target changes.

This is like a setter from the Getters & setters lesson, but it can do more. One set trap can check many property names, log every change, or refuse names it doesn't know. The cost: someone who reads userProxy.age = 36 can't tell that a trap runs there.

Use Reflect.set unless you have a reason not to

Reflect.set does the normal write and returns true or false. That is exactly the answer a set trap must give. It also runs setters correctly, even ones the object gets from its prototype. The next lesson covers Reflect, because passing actions on by hand often causes bugs.

A proxy that refuses every writePop out in the code editor (opens in a new tab)JavaScript
"use strict";
const profile = { name: "Asha" };
const readOnly = new Proxy(profile, {
  get(target, property, receiver) {
    return Reflect.get(target, property, receiver);
  },
  set() {
    return false;
  },
});
console.log(readOnly.name);
try {
  readOnly.name = "Mina";
} catch (error) {
  console.log(error.name);
}

The set trap on line 7 always returns false. So every write is refused, and profile does not change. The code is in strict mode, so line 13 throws a TypeError. The catch block prints its name. The output is Asha, then TypeError.

A read-only proxy is useful when you want one on purpose. If only one property needs protection, a property flag (writable: false) or a copy of the data is often clearer.

Watching an object for changes

INTERACTIVE

A proxy can watch for writes and deletes, then tell another part of your program about them. An object watched like this is called an observable object.

The lab below puts a proxy in front of a cart. Each change goes into a change log, and the cart summary is drawn again.

Watch a cart for changes
A proxy that records writes and deletesPop out in the code editor (opens in a new tab)JavaScript
const cart = { apples: 1, pears: 2 };const changes = [];const observed = new Proxy(cart, {  set(target, property, value, receiver) {    const before = target[property];    const ok = Reflect.set(target, property, value, receiver);    changes.push({ type: "set", property, before, after: value });    render();    return ok;  },  deleteProperty(target, property) {    const before = target[property];    const ok = Reflect.deleteProperty(target, property);    changes.push({ type: "delete", property, before });    render();    return ok;  },});
Cart summary3 items
apples1
pears2

Change log

No changes yet. Try a button.

Try it yourself

The proxy writes each change into the log. Then the cart summary is drawn again from the changed target.

A small teaching version. Real libraries also group changes, track which parts of the page use which data, and add many safety checks.

Real libraries that update a page when data changes do more than this lab:

  • They track which parts of the page use which properties.
  • They group many changes into one update.
  • They stop a change from setting off another change, over and over.

But they start with the same three steps as the lab:

  1. Catch the action in a trap.
  2. Pass it on to the target.
  3. Tell the code that cares about the change.

Proxies you can turn off

INTERACTIVE

Sometimes a proxy should stop working. For example, a plugin is removed, a permission runs out, or a short-term view of an object must be closed. Proxy.revocable(target, handler) makes a proxy you can turn off. It returns an object with two properties:

  • proxy: the proxy to use.
  • revoke: a function that turns the proxy off.

After revoke() runs, any action on the proxy throws a TypeError. You can't turn it back on.

Turn a proxy off
A proxy you can turn offPop out in the code editor (opens in a new tab)JavaScript
const target = { secret: "draft" };const { proxy, revoke } = Proxy.revocable(target, {  get(target, property, receiver) {    return Reflect.get(target, property, receiver);  },}); console.log(proxy.secret);revoke();console.log(proxy.secret);
Resultready
Before revokenot run
After revokenot run
Try it yourself

Press the button. It reads through the proxy, calls revoke(), then tries the same read again.

Proxy.revocable returns an object with two properties: proxy and revoke. Once you call revoke(), the proxy stays off.
Top row, before revoke(): proxy.secret goes through the proxy box to the target cart, which holds secret draft. Bottom row, after revoke(): proxy.secret reaches the proxy box and throws a TypeError.proxy.secretbefore revoke()proxysecret: "draft"targetproxy.secretafter revoke()proxyTypeError
Before revoke(), the proxy passes the read on and you get "draft". After it, the same read throws a TypeError, but the target is still there.
Real-life analogyTurning off a proxy is like firing the assistant

Once the assistant is fired, calling the desk no longer reaches the boss. The boss is still in the building. People who have the boss's own number can still call.

In real life: The assistant works at the desk
In JavaScript: You use proxy as usual
In real life: The company ends the job
In JavaScript: You call revoke()
In real life: Callers to the desk reach no one
In JavaScript: Any later action on the proxy throws TypeError

Rules a proxy can't break

REAL ERRORS

A proxy is powerful, but it must keep some promises the language makes. These promises are called invariants: rules JavaScript always checks, even when a trap runs. Here are two of them:

  • If a property on the target is locked, the get trap must give back its real value. Locked means two things: it can't be changed (writable: false), and it can't be removed or reset (configurable: false).
  • If the target can't get new properties, the traps can't make up extra keys. Such a target is called non-extensible.

When a trap breaks one of these rules, JavaScript throws a TypeError.

Real-life analogyInvariants are company policy

The assistant can screen requests, but can't change the company's locked records. If the file says a fact is final, the assistant must not tell visitors something else.

In real life: The official file says a fact is locked
In JavaScript: A property with writable: false and configurable: false
In real life: The assistant claims a different fact
In JavaScript: A trap returns a different value
In real life: Policy beats the assistant
In JavaScript: JavaScript throws TypeError

The lab below runs four cases. Each one shows something a proxy can't change. Each case is explained again after the lab.

Four things a proxy can't change
A locked value can't be changed by a trapPop out in the code editor (opens in a new tab)JavaScript
const target = {};Object.defineProperty(target, "answer", {  value: 42,  writable: false,  configurable: false,});const liar = new Proxy(target, {  get() {    return 43;  },});liar.answer;
ResultTypeError
error.nameTypeError
outputnone
Try it yourself

Lie about a locked value: this throws a TypeError. JavaScript's own rules protect the target or the built-in object.

The lab shows error.name, because the full error message is different in each browser.

A trap can't lie about a locked property

The trap says 43, but answer is locked at 42Pop out in the code editor (opens in a new tab)JavaScript
const target = {};Object.defineProperty(target, "answer", {  value: 42,  writable: false,  configurable: false,});const liar = new Proxy(target, {  get() {    return 43;  },});liar.answer;

Lines 2 to 6 lock answer at 42. The get trap on line 8 returns 43. Line 11 reads liar.answer, and JavaScript throws a TypeError.

Reading liar.answer. The liar proxy's trap returns 43. The target cart holds answer 42, locked. The two don't match, so JavaScript throws a TypeError.liar.answerreturn 43liarthe trap says 43TypeErroranswer: 42targetlocked at 42
The target locks answer at 42. The trap says 43, so JavaScript throws a TypeError instead of letting the wrong value through.

Private fields don't work through a proxy

A method reads a private field through a proxyPop out in the code editor (opens in a new tab)JavaScript
class SecretBox {  #secret = "safe";  read() {    return this.#secret;  }}const box = new SecretBox();const proxy = new Proxy(box, {});proxy.read();

A private field, like #secret, lives only on the real object. Line 9 calls read() through the proxy, so this is the proxy. Line 4 reads this.#secret. The proxy has no such field, so it throws a TypeError.

Map methods need the real Map

Fix Map methods by binding them to the real MapPop out in the code editor (opens in a new tab)JavaScript
const map = new Map([["score", 10]]);const proxy = new Proxy(map, {});proxy.get("score"); const fixed = new Proxy(map, {  get(target, property, receiver) {    const value = Reflect.get(target, property, receiver);    return typeof value === "function" ? value.bind(target) : value;  },});fixed.get("score");

A Map keeps its entries in hidden storage that is built into every real Map. The spec calls this storage an internal slot. A proxy doesn't have it.

  • Line 3 calls get through the proxy. Inside it, this is the proxy, so it throws a TypeError.
  • Line 8 fixes this. When the trap returns a function, it binds the function to the real Map first. bind makes a copy of a function whose this is always the object you give it.
  • Line 11 then works and returns 10.

Date methods need the real Date too

Fix Date methods the same wayPop out in the code editor (opens in a new tab)JavaScript
const date = new Date("2026-01-01T00:00:00Z");const proxy = new Proxy(date, {});try {  console.log(proxy.getUTCFullYear());} catch (error) {  console.log(error.name);}const fixed = new Proxy(date, {  get(target, property, receiver) {    const value = Reflect.get(target, property, receiver);    return typeof value === "function" ? value.bind(target) : value;  },});console.log(fixed.getUTCFullYear());

A Date keeps its time in an internal slot too. Line 4 throws, so the catch block prints TypeError. Line 11 binds the method to the real Date, so line 14 prints 2026.

A proxy is a different object from its target

Compare the proxy with its targetPop out in the code editor (opens in a new tab)JavaScript
const target = { id: 1 };const proxy = new Proxy(target, {});console.log(proxy === target);

The handler is empty, so every action goes to the target. Still, the proxy is its own object. Line 3 prints false.

Where you’ll use this

Most app code doesn't need proxies. You will mostly meet them inside libraries:

  • state libraries that update the page when data changes
  • clients that talk to a server's API
  • code that checks values before they are saved
  • plugin systems and testing helpers

Some security tools build a membrane: a wall of proxies around a group of objects. Outside code only gets proxies, never the real objects. So every action that crosses the wall can be logged, limited, or turned off.

These are the four jobs proxies do most often:

  • Checking values: refuse bad writes before they reach an object that other code shares.
  • Watching: record every write and delete, so the page can update.
  • Made-up values: work out a value only when someone reads it, or show data that is stored somewhere else.
  • Limiting access: show other code only a safe part of a bigger object, sometimes with Proxy.revocable.

Every trap adds extra work to each action. It also makes error messages longer and harder to read. Keep code that runs very often simple. Measure real use before you swap plain objects for proxies.

Try simpler tools first

If one property needs to be worked out when read, use a getter. If one property needs checks when written, use a setter. If one property needs to be locked or hidden, use property flags. Use a Proxy only when you need to control the whole object.

Read an array from the endPop out in the code editor (opens in a new tab)JavaScript
const drinks = ["tea", "coffee", "water"];
const proxy = new Proxy(drinks, {
  get(target, property, receiver) {
    const index = Number(property);
    if (Number.isInteger(index) && index < 0) {
      return Reflect.get(target, target.length + index, receiver);
    }
    return Reflect.get(target, property, receiver);
  },
});
console.log(proxy[-1]);
console.log(proxy[-2]);

Property names reach the trap as strings, so -1 arrives as "-1".

  • Line 4 turns the property name into a number.
  • Line 5 checks for a whole number below 0.
  • Line 6 adds it to the length. For -1, that is 3 + (-1) = 2, the last item.

So line 11 prints water, and line 12 prints coffee. The array never gets a -1 property. Keep a trick like this small and well explained, so it doesn't surprise other people who read your code.

Common misconceptions

“A proxy is a copy of the target.”

No. A proxy is a separate object that stands in front of the target. When its traps pass actions on, they change the real target.

“A trap can return anything it wants.”

No. Each trap has rules about what it returns. For example, set, defineProperty, deleteProperty, setPrototypeOf, isExtensible, and preventExtensions must return true or false. Then JavaScript checks the answer against the invariants.

“Object.keys only runs ownKeys.”

No. It also reads each key's settings with getOwnPropertyDescriptor, to keep only the enumerable keys. The trap playground above shows both.

“Private fields and Map entries are just properties.”

No. A private field lives only on the real object. A Map's entries live in its hidden storage. A proxy that passes every action on can still break both.

“Revoking deletes the target.”

No. Revoking turns off that one proxy. Other code that holds the target can still use it.

Practice: proxies at the keyboard

5 EXERCISES
Exercise 1 · Warm-upPredict a get trap

Predict the two lines this code prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const target = { name: "Ada" };
const proxy = new Proxy(target, {
  get(target, property, receiver) {
    console.log("get " + String(property));
    return Reflect.get(target, property, receiver);
  },
});
console.log(proxy.name);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticePredict a set trap

    Predict the two lines this strict-mode code prints.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    "use strict";
    const target = { count: 0 };
    const proxy = new Proxy(target, {
      set(target, property, value, receiver) {
        console.log("set " + String(property) + "=" + value);
        return Reflect.set(target, property, value, receiver);
      },
    });
    proxy.count = 2;
    console.log(target.count);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the bug in a set trap

      Line 9 throws a TypeError, even though 12 is a fine age. What should the trap return after a write that worked?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      "use strict";
      const user = { age: 0 };
      const proxy = new Proxy(user, {
        set(target, property, value) {
          if (property === "age" && value < 0) return false;
          target[property] = value;
        },
      });
      proxy.age = 12;

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeRecord one change

        What change does this code print?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const changes = [];
        const cart = { apples: 1 };
        const observed = new Proxy(cart, {
          set(target, property, value, receiver) {
            const before = target[property];
            const ok = Reflect.set(target, property, value, receiver);
            changes.push(String(property) + ":" + before + "->" + value);
            return ok;
          },
        });
        observed.apples = 3;
        console.log(changes[0]);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeUse a proxy, then turn it off

          What is the second line printed, after the proxy is turned off?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const target = { secret: "draft" };
          const { proxy, revoke } = Proxy.revocable(target, {});
          console.log(proxy.secret);
          revoke();
          try {
            console.log(proxy.secret);
          } catch (error) {
            console.log(error.name);
          }

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Try each answer before you read the explanations. Small details matter with proxies.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What is a Proxy in JavaScript?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const proxy = new Proxy({ name: "Ada" }, {
                get(target, property, receiver) {
                  console.log("trap " + String(property));
                  return Reflect.get(target, property, receiver);
                },
              });
              console.log(proxy.name);

              Choose an answer to see the explanation.

            3. Question 3 of 7Which traps does Object.keys(proxy) usually run?

              Choose an answer to see the explanation.

            4. Question 4 of 7This set trap returns false in strict mode. What does the code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              "use strict";
              const proxy = new Proxy({}, {
                set() {
                  return false;
                },
              });
              try {
                proxy.x = 1;
              } catch (error) {
                console.log(error.name);
              }

              Choose an answer to see the explanation.

            5. Question 5 of 7Why can’t a get trap give a different value for a locked property?

              Choose an answer to see the explanation.

            6. Question 6 of 7What does Proxy.revocable return?

              Choose an answer to see the explanation.

            7. Question 7 of 7Which of these is true?

              Choose an answer to see the explanation.

            Key takeaways

            • A proxy stands in front of a target. A handler object holds its rules.
            • The handler's methods are called traps. Each trap catches one kind of action. If a trap is missing, the action goes to the target as usual.
            • There are 13 traps: 11 for any object, plus apply and construct for functions.
            • Use Reflect inside a trap to do the normal action and give back the right answer.
            • Traps must keep the language's rules (invariants). If a trap lies about a locked property, JavaScript throws a TypeError.
            • Private fields, Map and Date methods, and === still see the proxy as a different object from its target.

            Remember the one-liner.
            A Proxy stands in front of a target, and its traps catch actions on it, but the traps must keep the language's rules.

            Up next: Reflect, the object with one method for each trap. It makes passing actions on to the target simple and safe.

            CompleteFrontend Clear concepts. Working examples.