cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Internal methods & exotic objects

Learn how ECMAScript object methods explain property reads and writes, arrays, arguments, typed arrays, modules, proxies, and their invariants.

By the end, you can
  • 01
    Read the method tableConnect all eleven essential object internal methods to property and prototype operations you can run.
  • 02
    Recognize exoticsPredict how arrays, strings, arguments, typed arrays, namespace objects, bound functions, and proxies differ.
  • 03
    Explain invariantsIdentify when a Proxy result conflicts with fixed properties or an object's non-extensible state.

Objects follow internal rules

In Function calls: [[Call]] & [[Construct]], a function was an object with extra behavior. Here we ask what any object does when code reads a property, changes a prototype, or lists its keys. The specification describes those actions with internal methods: operations written in double brackets.

Start with one property readPop out in the code editor (opens in a new tab)JavaScript
const cart = { price: 3 };
console.log(cart.price);

Line 1 creates a cart whose price is 3. Line 2 reads that property and prints 3. The program does not literally call cart.[[Get]]; the brackets name a specification operation behind the read.

Definition

An exotic object uses a different algorithm for one or more essential internal methods. An ordinary object uses the ordinary algorithms. Both must obey the shared rules called invariants.

We will begin with a plain cart, then compare arrays, String wrappers, arguments, typed arrays, module namespaces, bound functions, and proxies. An exotic object is not an object that looks strange in your app; it is a precise spec category. The current ECMAScript object model names these operations and their shared constraints.

The essential internal methods

Every object has the eleven operations in the table. Their names are not JavaScript properties. Reflect offers public functions that request closely related operations, which makes a short experiment possible.

Read and ask whether a property existsPop out in the code editor (opens in a new tab)JavaScript
const cart = { price: 3 };
console.log(Reflect.get(cart, "price"));
console.log(Reflect.has(cart, "price"));

Line 1 creates the cart. Line 2 asks to read its price and prints 3. Line 3 asks whether a price exists on the cart or its prototype chain and prints true. A read and an existence check are different questions.

The eleven essential internal object methods
MethodQuestion it answersPublic operation to try
[[GetPrototypeOf]]Read the prototypeReflect.getPrototypeOf(cart)
[[SetPrototypeOf]]Try to change the prototypeReflect.setPrototypeOf(cart, null)
[[IsExtensible]]Can new own keys be added?Reflect.isExtensible(cart)
[[PreventExtensions]]Block new own keysReflect.preventExtensions(cart)
[[GetOwnProperty]]Read an own property descriptorReflect.getOwnPropertyDescriptor(cart, 'price')
[[DefineOwnProperty]]Try to create or revise an own descriptorReflect.defineProperty(cart, 'price', { value: 4 })
[[HasProperty]]Look for an own or inherited propertyReflect.has(cart, 'price')
[[Get]]Read a value, including inherited valuesReflect.get(cart, 'price')
[[Set]]Try to write a value through a receiverReflect.set(cart, 'price', 5)
[[Delete]]Try to remove an own propertyReflect.deleteProperty(cart, 'price')
[[OwnPropertyKeys]]List own string and symbol keysReflect.ownKeys(cart)
Try all eleven with one cartPop out in the code editor (opens in a new tab)JavaScript
const cart = { price: 3 };
console.log(Reflect.getPrototypeOf(cart) === Object.prototype);
console.log(Reflect.setPrototypeOf(cart, null));
console.log(Reflect.isExtensible(cart));
console.log(Reflect.preventExtensions(cart));
console.log(Reflect.isExtensible(cart));
console.log(Reflect.getOwnPropertyDescriptor(cart, "price").value);
console.log(Reflect.defineProperty(cart, "price", { value: 4 }));
console.log(Reflect.has(cart, "price"));
console.log(Reflect.get(cart, "price"));
console.log(Reflect.set(cart, "price", 5));
console.log(Reflect.ownKeys(cart).join(","));
console.log(Reflect.deleteProperty(cart, "price"));

Lines 2 to 5 inspect the prototype and extensibility. Lines 6 to 9 inspect a descriptor, define a value, check a key, and read it. Lines 10 to 12 write, list, and delete. The logs are true, true, true, true, false, 3, true, true, 4, true, price, true. Reflect exposes the success Boolean; assignment syntax can behave differently in strict code.

Real-life analogyA phone contact list

You open your phone contacts, look up Asha, and read her number. You may change the number or list all saved names.

In real life: Check if Asha is saved
In JavaScript: [[HasProperty]] checks for a key
In real life: Read Asha's number
In JavaScript: [[Get]] reads a value
In real life: Change the saved number
In JavaScript: [[Set]] tries to write
In real life: List saved contacts
In JavaScript: [[OwnPropertyKeys]] lists own names

Where the analogy stops: A phone list does not have JavaScript prototypes, descriptors, or Proxy invariants.

Ordinary [[Get]] follows the prototype

An own property belongs directly to an object. When an ordinary object lacks that property, its prototype may supply the value. The read keeps a receiver, the original object used for the access, so an inherited getter can use the right this.

Read an own pricePop out in the code editor (opens in a new tab)JavaScript
const cart = { price: 3 };
console.log(cart.price);

Line 1 creates a cart with an own price. Line 2 reads it and prints 3. No prototype search is necessary because the property is on the cart itself.

Read a price from a parentPop out in the code editor (opens in a new tab)JavaScript
const parent = { price: 3 };
const cart = Object.create(parent);
console.log(cart.price);
console.log(Object.hasOwn(cart, "price"));

Line 1 makes a parent with price 3. Line 2 creates a cart whose prototype is that parent. Line 3 prints 3 after looking upward; line 4 prints false because the cart does not own the key. Object.hasOwn answers a narrower question than Reflect.has or a read.

Selected OrdinaryGet steps, condensed spec outlineText
1. Let desc be ? O.[[GetOwnProperty]](P).
2. If desc is undefined, let parent be ? O.[[GetPrototypeOf]]().
3. If parent is null, return undefined; otherwise return ? parent.[[Get]](P, Receiver).

The OrdinaryGet algorithm first asks for the own descriptor, then tries the parent if none exists. An accessor descriptor instead calls a getter using the receiver. The outline above omits that branch; it is a teaching summary, not a verbatim complete algorithm.

Replay an inherited property read
Step 0 of 5Ready
Your turn: follow the blue line

A replay of instrumented JavaScript, not a view inside the engine.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const cart = Object.create(parent);function readPrice(order) {  return Reflect.get(order, "price");}console.log(readPrice(cart));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
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.

The replay invokes a real readPrice function and returns 3. Its frames annotate example code; they do not show the engine's private call stack. Step and Back let you compare the cart's missing own key with its inherited value.

Ordinary [[Set]] writes through a receiver

A property write asks whether a value can be changed. Ordinary [[Set]] returns a Boolean to its caller. If it finds a writable inherited data property, the resulting own write is made on the receiver, not on the prototype.

Change one cart pricePop out in the code editor (opens in a new tab)JavaScript
const cart = { price: 3 };
cart.price = 4;
console.log(cart.price);

Line 1 creates a cart at price 3. Line 2 assigns 4. Line 3 reads the updated value and prints 4. This is the simplest successful write.

An inherited setter uses the receiverPop out in the code editor (opens in a new tab)JavaScript
const parent = { set price(value) { this.saved = value; } };
const cart = Object.create(parent);
cart.price = 3;
console.log(cart.saved, Object.hasOwn(parent, "saved"));

Line 1 creates a parent setter; this.saved means the object receiving the assignment. Line 2 makes the cart inherit it. Line 3 calls that setter through the cart. Line 4 prints 3 false: saved belongs to the cart, not the parent. See Reflect for passing a receiver explicitly.

Selected OrdinarySetWithOwnDescriptor steps, condensed outlineText
1. Let ownDesc be ? O.[[GetOwnProperty]](P).
2. If ownDesc is undefined, consult the parent; otherwise use the property descriptor.
3. For an accessor descriptor, call its setter with Receiver as this.

The ordinary set algorithm distinguishes data descriptors from accessor descriptors. It returns false when a non-writable data property blocks the change; an inherited setter receives the original receiver as its this.

Replay a property write
Step 0 of 5Ready
Your turn: follow the blue line

A replay of the real changePrice function, not an engine debugger.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function changePrice(order, price) {  return Reflect.set(order, "price", price);}console.log(changePrice(cart, 4));console.log(cart.price);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
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.

This second instrumented replay calls the real changePrice function. It reports true for the successful write and then 4 for the new price. These are snapshots of our function, not an engine debugger.

Ordinary [[DefineOwnProperty]] checks descriptors

A property descriptor describes a property's value or getter, plus flags such as writable, enumerable, and configurable. Ordinary [[DefineOwnProperty]] checks a proposed descriptor against the current one and the object's extensibility. A failed definition returns false through Reflect.defineProperty.

Define a quiet pricePop out in the code editor (opens in a new tab)JavaScript
const cart = {};
Object.defineProperty(cart, "price", { value: 3, writable: false });
console.log(cart.price, Object.keys(cart).length);

Line 1 creates an empty cart. Line 2 defines price 3 with writable: false; omitted flags default to false here. Line 3 prints 3 0: the value exists, but Object.keys does not list a non-enumerable property.

Selected OrdinaryDefineOwnProperty steps, condensed outlineText
1. Let current be ? O.[[GetOwnProperty]](P).
2. Let extensible be ? O.[[IsExtensible]]().
3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current).

The ordinary definition algorithm reads the existing descriptor and extensibility, then validates the change. It does not assume that a property called price can always be replaced. The spec outline above omits the validation details.

Playground: allow or block a price change
Current property rulePop out in the code editor (opens in a new tab)JavaScript
const cart = {};Object.defineProperty(cart, "price", { value: 3, writable: true });console.log(Reflect.set(cart, "price", 4));console.log(cart.price);
Current result
Reflect.settrue
cart.price4
Try it yourself

Reflect.set returns true. The price is 4. Only the writable flag changes.

This runs Object.defineProperty and Reflect.set, not a model of an engine. Reset restores writable: true.

Start with the checkbox on: the source prints true then 4. Turn it off: it prints false then 3. Only writable changes. Reset restores the initial state.

Arrays and String wrappers have special indexes

An Array exotic object connects array-index property definitions to its own length property. A hole is a missing property, not a stored undefined. Writing an index past the end grows length; shortening length attempts to delete the indexes above it.

Create one hole in an arrayPop out in the code editor (opens in a new tab)JavaScript
const cart = ["tea"];
cart[2] = "water";
console.log(cart.length, 1 in cart);

Line 1 creates an array with tea at index zero. Line 2 writes water at index two, leaving index one absent. Line 3 prints 3 false: length reaches past the last index, but the hole is not an existing property.

Shortening length removes later itemsPop out in the code editor (opens in a new tab)JavaScript
const cart = ["tea", "milk", "water"];
cart.length = 1;
console.log(cart.length, cart[2]);

Line 1 creates three items. Line 2 sets length to one. Line 3 prints 1 undefined because the later indexes are deleted. If a non-configurable element blocks deletion, the length-setting algorithm cannot finish at the requested size; this is why length is not merely a number that is recomputed after every read.

Selected Array [[DefineOwnProperty]] steps, condensed outlineText
1. If P is "length", return ArraySetLength(A, Desc).
2. If P is an array index, define the property, then update "length" if necessary.

The array definition algorithm handles length and index keys separately. For everyday array operations, see the published Arrays lesson.

A String exotic object is an object wrapper around a string. Its character indexes behave like non-writable, non-configurable own properties. The primitive string has similar visible indexed reads, but new String makes the exotic wrapper easy to inspect.

Inspect a String wrapper's indexesPop out in the code editor (opens in a new tab)JavaScript
const word = new String("tea");
console.log(word[0], Object.keys(word).join(","));

Line 1 wraps tea. Line 2 reads index zero and lists enumerable own keys. It prints t 0,1,2. These character indexes are not ordinary writable slots attached by your code.

Character index cannot be removedPop out in the code editor (opens in a new tab)JavaScript
const word = new String("tea");
console.log(Object.getOwnPropertyDescriptor(word, "0").writable);
console.log(Reflect.deleteProperty(word, "0"));

Line 1 makes the same wrapper. Line 2 prints false for the first index's writable flag. Line 3 prints false when deletion is requested. The String exotic [[GetOwnProperty]] algorithm synthesizes those character descriptors.

Arguments can alias; typed arrays have fixed indexes

A mapped arguments exotic object connects an index with a parameter binding in eligible non-strict functions with a simple parameter list. The connection is not universal. Strict mode and non-simple parameter lists use unmapped arguments, and deleting the mapped index breaks that index's connection.

A sloppy parameter follows its arguments indexPop out in the code editor (opens in a new tab)JavaScript
function order(item) {
  arguments[0] = "coffee";
  console.log(item);
}
order("tea");

Line 1 declares the item parameter. Line 2 writes coffee to index zero. Line 3 reads the linked parameter and prints coffee. Line 5 passes tea as the original argument. This example intentionally has no "use strict" directive.

Strict arguments are not mappedPop out in the code editor (opens in a new tab)JavaScript
function order(item) {
  "use strict";
  arguments[0] = "coffee";
  console.log(item);
}
order("tea");

Line 2 enables strict mode in the function. Line 3 still changes arguments[0], but line 4 prints tea from the unchanged parameter. In the next example, deletion also removes the sloppy mapping.

Deleting an index breaks its mappingPop out in the code editor (opens in a new tab)JavaScript
function order(item) {
  delete arguments[0];
  arguments[0] = "coffee";
  console.log(item);
}
order("tea");

Line 2 deletes the mapped index. Line 3 adds a new property with the same name. Line 4 prints tea because that replacement property no longer aliases the parameter. The arguments exotic algorithms track the map so an ordinary-looking indexed write can have this extra effect.

An integer-indexed exotic object underlies a typed array such as Uint8Array. Its numeric index follows view bounds and element-conversion rules rather than growing a JavaScript array. Other non-index keys may still be ordinary properties.

A typed array does not grow from index onePop out in the code editor (opens in a new tab)JavaScript
const scores = new Uint8Array([3]);
console.log(Reflect.set(scores, "1", 4), scores.length);
console.log(Reflect.defineProperty(scores, "1", { value: 4 }));

Line 1 creates a one-element typed array. Line 2 tries an out-of-bounds write: Reflect.set reports true here, while the length stays 1; the write does not create index one. Line 3 prints false because defining that invalid numeric index fails. The two APIs report different success conditions; do not use the Reflect.set Boolean to infer that an element was added.

An existing byte is converted and cannot be deletedPop out in the code editor (opens in a new tab)JavaScript
const scores = new Uint8Array([3]);
scores[0] = 259;
console.log(scores[0], Reflect.deleteProperty(scores, "0"));

Line 1 creates a byte view. Line 2 stores 259, converted to 3 for an unsigned byte. Line 3 prints 3 false: the existing integer index cannot be removed with Reflect. The integer-indexed rules distinguish an existing element from an invalid numeric index.

Module namespaces and bound functions

A module namespace exotic object exposes exported names as fixed keys. Its reads reach the current exported bindings, so exported values can change without adding or replacing keys on the namespace. The namespace is non-extensible, and its prototype is null.

A tiny module namespace, main.mjsJavaScript
import { price } from "./cart.mjs";
import * as cart from "./cart.mjs";
console.log(cart.price, Object.isExtensible(cart));

Line 1 imports the live price binding; line 2 gets the namespace. Line 3 reads the current price and extensibility and prints 4 false. The second file below changes its exported binding before this code runs. These modules cannot run as standalone classic scripts in the lesson editor.

The exported binding, cart.mjsJavaScript
export let price = 3;
price = 4;

Line 1 exports price 3. Line 2 updates it to 4. The namespace reads the latest binding, even though its export name is fixed. A writable-looking descriptor on a namespace is not permission for an importer to assign a new exported value. See Module Records for the module side of this relationship.

A bound function exotic object remembers a target function, a bound this, and leading arguments. Calling it uses those saved values. Constructing a bound constructor ignores the bound this and creates a new instance instead.

Bind one order to AshaPop out in the code editor (opens in a new tab)JavaScript
function order(item) { return this.name + ":" + item; }
const serve = order.bind({ name: "Asha" }, "tea");
console.log(serve());

Line 1 defines a function that combines its receiver's name and an item. Line 2 binds Asha and tea. Line 3 calls the bound function and prints Asha:tea. This is the extra [[Call]] behavior described by the bound function exotic object.

Construct through a bound functionPop out in the code editor (opens in a new tab)JavaScript
function Order(item) { this.item = item; }
const TeaOrder = Order.bind(null, "tea");
console.log(new TeaOrder().item);

Line 1 makes a constructor that saves an item. Line 2 binds tea as its first argument. Line 3 uses new and prints tea. The new object becomes the instance; the bound null is not treated as that instance. A bound callable target that is not a constructor cannot become constructible by binding.

Proxies intercept; immutable prototypes refuse changes

A Proxy exotic object sends supported internal operations to handler traps when they exist. Its target still sets limits. The public Proxy and Reflect lessons cover all traps and forwarding in greater depth.

Forward a simple proxy readPop out in the code editor (opens in a new tab)JavaScript
const cart = new Proxy({ price: 3 }, {
  get(target, key, receiver) { return Reflect.get(target, key, receiver); },
});
console.log(cart.price);

Line 1 creates a target cart and wraps it. Line 2 forwards the read with the same target, key, and receiver. Line 4 reads price through the proxy and prints 3. Forwarding retains the ordinary behavior unless the trap deliberately changes it within the permitted rules.

A proxy cannot lie about a frozen pricePop out in the code editor (opens in a new tab)JavaScript
const cart = Object.freeze({ price: 3 });
const proxy = new Proxy(cart, { get() { return 4; } });
try {
  console.log(proxy.price);
} catch (error) {
  console.log(error.name);
}

Line 1 freezes price 3, making it non-configurable and non-writable. Line 2 creates a trap that falsely returns 4. Line 4 attempts the read; line 6 catches the required TypeError and prints its name. This deliberately invalid trap is caught so the snippet remains runnable.

Selected Proxy [[Get]] steps, condensed outlineText
1. Let trapResult be ? Call(trap, handler, « target, P, Receiver »).
2. Let targetDesc be ? target.[[GetOwnProperty]](P).
3. If targetDesc is non-configurable and non-writable, check trapResult against its value.

The Proxy [[Get]] algorithm checks fixed target properties after calling the trap. Other proxy traps have their own checks too. The outline above captures one branch; it is not a complete copy of the algorithm.

An immutable prototype exotic object will accept its current prototype as an unchanged request, but reject a different one. Object.prototype is the everyday example. This is separate from Object.freeze, which controls own properties and extensions.

Object.prototype refuses a new prototypePop out in the code editor (opens in a new tab)JavaScript
const cart = Object.prototype;
console.log(Reflect.setPrototypeOf(cart, null));
console.log(Reflect.setPrototypeOf(cart, {}));

Line 1 refers to Object.prototype. Line 2 asks to keep its existing null prototype and prints true. Line 3 asks for a different object as prototype and prints false. The immutable prototype rule explains why the two requests differ.

Internal methods must agree with each other

An invariant is a rule that every object's essential methods must preserve, even when an exotic object chooses different algorithms. A non-extensible object cannot gain new own properties or change its prototype. A non-configurable own key cannot vanish from a list of own keys.

A fixed key cannot disappearPop out in the code editor (opens in a new tab)JavaScript
const cart = Object.preventExtensions({ price: 3 });
const proxy = new Proxy(cart, { ownKeys() { return []; } });
try {
  console.log(Reflect.ownKeys(proxy));
} catch (error) {
  console.log(error.name);
}

Line 1 prevents extensions on a cart with price. Line 2 makes a proxy that falsely lists no keys. Line 4 asks for the keys; line 6 catches and prints TypeError. The engine checks the trap's answer against the real target rather than returning a misleading empty list.

Five key constraints on essential internal methods
ConstraintWhat must remain trueWhat to watch for
Non-extensibleThe prototype cannot change, and no new own property may appearA proxy cannot report extra or missing own keys
Non-configurableA fixed own key cannot disappear from ownKeys or getOwnPropertyA proxy cannot hide a frozen price
Non-writable and non-configurableA data property's reported read must match its actual valueA get trap returning 4 for frozen price 3 throws
No setter and non-configurableA set trap cannot claim success for a fixed accessor with no setterReflect.set cannot invent a write
Own keysReturn a List of unique string or symbol keysNo duplicate keys or numbers

[[GetOwnProperty]] cannot claim a non-configurable own property disappeared, and [[DefineOwnProperty]] cannot add a key to a non-extensible object. [[GetPrototypeOf]] must return an object or null; when extensions are blocked its answer must match the actual prototype. [[OwnPropertyKeys]] must return distinct strings or symbols and include every non-configurable key.

[[SetPrototypeOf]] may refuse a request, and cannot report success changing a non-extensible object's prototype. [[IsExtensible]] must report the target's real state for a Proxy; after [[PreventExtensions]] reports success, that state must be false. [[HasProperty]] cannot hide a fixed own key, while [[Delete]] cannot claim to remove one. [[Get]] and [[Set]] cannot contradict a frozen data value or a fixed accessor with no getter or setter.

Use the right operation in app code

When a settings page writes a property, it may need to know whether the write worked. Reflect.set gives a Boolean; a plain assignment returns the assigned expression value and, in strict mode, can throw if the write fails. Choose the operation that answers the question your code actually asks.

Check whether a cart update workedPop out in the code editor (opens in a new tab)JavaScript
const cart = { price: 3 };
console.log(Reflect.set(cart, "price", 4));
console.log(cart.price);

Line 1 creates a cart. Line 2 attempts a price update and prints true. Line 3 reads the cart and prints 4. Checking the success flag and checking the current value are related but distinct.

Which object behavior is this?
Object kindSpecial ruleOne familiar sign
OrdinaryUsual property and prototype algorithmsA plain cart object
ArrayIndex definitions can grow length; shrinking length deletes later elementscart[2] or cart.length = 1
StringIndexes expose fixed characters as own propertiesnew String('tea')[0]
Mapped argumentsSome sloppy-call indices share a parameter bindingarguments[0] changes item
Integer-indexedNumeric keys address typed-array elements with special bounds rulesscores[0]
Module namespaceExport names are fixed own keys with live exported valuesimport * as cart
Bound function[[Call]] remembers a target, this value, and argumentsorder.bind(user, 'tea')
ProxyA handler may trap operations but must respect invariantsnew Proxy(cart, handler)
Immutable prototypeRejects attempts to change its prototype to a different valueObject.prototype

On a website, this knowledge helps with validation proxies, editable settings, imported configuration, and typed-array data. Use Object.hasOwn when ownership matters; use Reflect.has when inherited keys count. Read a descriptor before assuming a field can be changed or deleted.

Sort object behavior
  • A plain { price: 3 }
  • An object made with Object.create(parent)
  • An array with special length updates
  • A String wrapper with character indexes
  • A Uint8Array with fixed indexed bounds
  • A Proxy with a get trap
  • An imported module namespace
Try it yourself
0 of 7 correct

Classify each object by the algorithm that makes it special, or by ordinary behavior.

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

Common mix-ups

An internal-method name describes an operation, not a JavaScript method you can call directly. Exotic is a behavioral classification in the specification, not a synonym for complicated. A plain object with a getter is still ordinary: its ordinary [[Get]] algorithm invokes that getter.

  • "Every exotic is a Proxy." Arrays, String wrappers, mapped arguments, and namespace objects have their own rules.
  • "Array length counts present items." An array can have holes: length can be 3 while index 1 is absent.
  • "Arguments always follow parameters." The mapping applies only to eligible sloppy calls and can be broken.
  • "Reflect.set true means the value changed." Invalid typed-array indexes show why that is not a general test.
  • "A Proxy can return any answer." The target's fixed properties and extensibility still constrain traps.
Terms that sound alike but ask different questions
TermWhat it meansDo not confuse it with
[[Get]]An internal operation invoked by language and APIsA JavaScript method called cart.[[Get]]()
ExoticOne or more internal algorithms differ from ordinary defaultsA slow or unusual-looking application object
Array lengthA special own data property updated by index definitionsThe number of existing indexed elements
Typed-array indexAn integer-indexed element backed by a typed-array viewAn ordinary array property that always expands length
Proxy trapA hook into an internal operation, checked against target rulesPermission to invent impossible results

A module namespace has live export reads, but that does not let an importer assign to the exported binding. A bound function can be used with new when its target is constructible; binding does not make a non-constructor callable as a constructor. For each case, name the object kind before predicting behavior.

Practice exercises

Start by predicting observable output. Then use the descriptor, index, or target rule that explains it. The final exercise applies a success check to settings on a real website.

Exercise 1 · Warm-upPredict an array shrink

A shopping cart removes its last item by shortening length. What does the log print after that change? Type both values in order.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const cart = ["tea", "milk"];
cart.length = 1;
console.log(cart[1], cart.length);

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

    Exercise 2 · Warm-upPredict a mapped parameter

    The order function receives tea, then changes its arguments index. Predict the item it logs. Say why strict mode would differ.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function order(item) {
      arguments[0] = "coffee";
      console.log(item);
    }
    order("tea");

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

      Exercise 3 · PracticeAllow a price update

      A cart should allow its price to change. Name the one descriptor flag to switch in the starter code. Then run the worked solution.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const cart = {};
      Object.defineProperty(cart, "price", { value: 3, writable: false });
      console.log(Reflect.set(cart, "price", 4));

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

        Exercise 4 · PracticeConvert a typed-array byte

        A score is stored in a one-byte typed array. Predict the result after assigning 259 at the existing index. Do not confuse this with adding an array element.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const scores = new Uint8Array([3]);
        scores[0] = 259;
        console.log(scores[0]);

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

          Exercise 5 · ChallengeFind a proxy invariant failure

          A proxy tries to hide a key from a settings object. What error name is logged? Explain why returning an empty list would contradict the target.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const cart = Object.preventExtensions({ price: 3 });
          const proxy = new Proxy(cart, { ownKeys() { return []; } });
          try { Reflect.ownKeys(proxy); } catch (error) { console.log(error.name); }

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

            Exercise 6 · ChallengeUpdate a website setting

            A website saves a theme choice in a settings object. Predict the two values logged after the update. In an app, check the success flag before showing a saved confirmation.

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            const settings = { theme: "light" };
            console.log(Reflect.set(settings, "theme", "dark"), settings.theme);

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

              Check your understanding

              For each question, first decide whether it asks about an own key, a property read, a successful write, or an exotic rule. In code questions, predict the actual JavaScript output before naming the internal method.

              Internal methods and exotic objects quiz · 7 questionsScore: first tries count
              1. Question 1 of 7Which internal method reads a property value through the prototype chain?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does the array shrink example print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const cart = ["tea", "milk"];
                cart.length = 1;
                console.log(cart[1], cart.length);

                Choose an answer to see the explanation.

              3. Question 3 of 7When can a sloppy arguments object link an index to its parameter?

                Choose an answer to see the explanation.

              4. Question 4 of 7What does this typed array print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const scores = new Uint8Array([3]);
                scores[0] = 259;
                console.log(scores[0]);

                Choose an answer to see the explanation.

              5. Question 5 of 7What does the non-extensible target and incomplete ownKeys trap print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const cart = Object.preventExtensions({ price: 3 });
                const proxy = new Proxy(cart, { ownKeys() { return []; } });
                try { Reflect.ownKeys(proxy); } catch (error) { console.log(error.name); }

                Choose an answer to see the explanation.

              6. Question 6 of 7Which statement describes a module namespace object?

                Choose an answer to see the explanation.

              7. Question 7 of 7What does Reflect.set report for a non-writable own data property?

                Choose an answer to see the explanation.

              Key takeaways

              The double-bracket names give you a vocabulary for behavior you can observe without exposing engine internals. Start with the ordinary rule; then ask whether the object is exotic and what it changes.

              • Eleven essential internal methods cover prototypes, extensibility, descriptors, membership, values, deletion, and keys.
              • Ordinary [[Get]] can follow the prototype; ordinary [[Set]] uses the receiver; [[DefineOwnProperty]] checks the descriptor.
              • Arrays tie indexes to length; String wrappers expose fixed characters; mapped arguments can alias parameters.
              • Typed-array indexes have bounds and conversion rules; namespace reads follow live exported bindings.
              • Bound functions remember call inputs; proxies intercept methods but cannot break target invariants.

              Remember the one-liner.
              An exotic object changes an internal algorithm, not the shared rules that keep object behavior consistent.

              Coming next: A tour of the abstract operations, the reusable steps behind conversions, comparisons, and iteration.

              CompleteFrontend Clear concepts. Working examples.