Internal methods & exotic objects
Learn how ECMAScript object methods explain property reads and writes, arrays, arguments, typed arrays, modules, proxies, and their invariants.
- 01Read the method tableConnect all eleven essential object internal methods to property and prototype operations you can run.
- 02Recognize exoticsPredict how arrays, strings, arguments, typed arrays, namespace objects, bound functions, and proxies differ.
- 03Explain 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.
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.
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.
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.
| Method | Question it answers | Public operation to try |
|---|---|---|
| [[GetPrototypeOf]] | Read the prototype | Reflect.getPrototypeOf(cart) |
| [[SetPrototypeOf]] | Try to change the prototype | Reflect.setPrototypeOf(cart, null) |
| [[IsExtensible]] | Can new own keys be added? | Reflect.isExtensible(cart) |
| [[PreventExtensions]] | Block new own keys | Reflect.preventExtensions(cart) |
| [[GetOwnProperty]] | Read an own property descriptor | Reflect.getOwnPropertyDescriptor(cart, 'price') |
| [[DefineOwnProperty]] | Try to create or revise an own descriptor | Reflect.defineProperty(cart, 'price', { value: 4 }) |
| [[HasProperty]] | Look for an own or inherited property | Reflect.has(cart, 'price') |
| [[Get]] | Read a value, including inherited values | Reflect.get(cart, 'price') |
| [[Set]] | Try to write a value through a receiver | Reflect.set(cart, 'price', 5) |
| [[Delete]] | Try to remove an own property | Reflect.deleteProperty(cart, 'price') |
| [[OwnPropertyKeys]] | List own string and symbol keys | Reflect.ownKeys(cart) |
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.
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.
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.
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.
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.
A replay of instrumented JavaScript, not a view inside the engine.
script
const cart = Object.create(parent);function readPrice(order) { return Reflect.get(order, "price");}console.log(readPrice(cart));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.
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.
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.
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.
A replay of the real changePrice function, not an engine debugger.
script
function changePrice(order, price) { return Reflect.set(order, "price", price);}console.log(changePrice(cart, 4));console.log(cart.price);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.
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.
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.
const cart = {};Object.defineProperty(cart, "price", { value: 3, writable: true });console.log(Reflect.set(cart, "price", 4));console.log(cart.price);true4Reflect.set returns true. The price is 4. Only the writable flag changes.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| Constraint | What must remain true | What to watch for |
|---|---|---|
| Non-extensible | The prototype cannot change, and no new own property may appear | A proxy cannot report extra or missing own keys |
| Non-configurable | A fixed own key cannot disappear from ownKeys or getOwnProperty | A proxy cannot hide a frozen price |
| Non-writable and non-configurable | A data property's reported read must match its actual value | A get trap returning 4 for frozen price 3 throws |
| No setter and non-configurable | A set trap cannot claim success for a fixed accessor with no setter | Reflect.set cannot invent a write |
| Own keys | Return a List of unique string or symbol keys | No 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.
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.
| Object kind | Special rule | One familiar sign |
|---|---|---|
| Ordinary | Usual property and prototype algorithms | A plain cart object |
| Array | Index definitions can grow length; shrinking length deletes later elements | cart[2] or cart.length = 1 |
| String | Indexes expose fixed characters as own properties | new String('tea')[0] |
| Mapped arguments | Some sloppy-call indices share a parameter binding | arguments[0] changes item |
| Integer-indexed | Numeric keys address typed-array elements with special bounds rules | scores[0] |
| Module namespace | Export names are fixed own keys with live exported values | import * as cart |
| Bound function | [[Call]] remembers a target, this value, and arguments | order.bind(user, 'tea') |
| Proxy | A handler may trap operations but must respect invariants | new Proxy(cart, handler) |
| Immutable prototype | Rejects attempts to change its prototype to a different value | Object.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.
- A plain
{ price: 3 } - An object made with
Object.create(parent) - An array with special
lengthupdates - A String wrapper with character indexes
- A
Uint8Arraywith fixed indexed bounds - A
Proxywith agettrap - An imported module namespace
Classify each object by the algorithm that makes it special, or by ordinary behavior.
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.
| Term | What it means | Do not confuse it with |
|---|---|---|
| [[Get]] | An internal operation invoked by language and APIs | A JavaScript method called cart.[[Get]]() |
| Exotic | One or more internal algorithms differ from ordinary defaults | A slow or unusual-looking application object |
| Array length | A special own data property updated by index definitions | The number of existing indexed elements |
| Typed-array index | An integer-indexed element backed by a typed-array view | An ordinary array property that always expands length |
| Proxy trap | A hook into an internal operation, checked against target rules | Permission 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.
A shopping cart removes its last item by shortening length. What does the log print after that change? Type both values in order.
const cart = ["tea", "milk"];
cart.length = 1;
console.log(cart[1], cart.length);const cart = ["tea", "milk"];
cart.length = 1;
console.log(cart[1], cart.length);It prints undefined 1. Shrinking length removes milk; reading the deleted index returns undefined.
The order function receives tea, then changes its arguments index. Predict the item it logs. Say why strict mode would differ.
function order(item) {
arguments[0] = "coffee";
console.log(item);
}
order("tea");function order(item) {
arguments[0] = "coffee";
console.log(item);
}
order("tea");It prints coffee: this non-strict call maps arguments[0] to item.
A cart should allow its price to change. Name the one descriptor flag to switch in the starter code. Then run the worked solution.
const cart = {};
Object.defineProperty(cart, "price", { value: 3, writable: false });
console.log(Reflect.set(cart, "price", 4));const cart = {};
Object.defineProperty(cart, "price", { value: 3, writable: true });
console.log(Reflect.set(cart, "price", 4));Set writable: true when defining the property. Reflect.set then returns true.
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.
const scores = new Uint8Array([3]);
scores[0] = 259;
console.log(scores[0]);const scores = new Uint8Array([3]);
scores[0] = 259;
console.log(scores[0]);It prints 3. The view converts 259 to an unsigned byte; it does not enlarge the element.
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.
const cart = Object.preventExtensions({ price: 3 });
const proxy = new Proxy(cart, { ownKeys() { return []; } });
try { Reflect.ownKeys(proxy); } catch (error) { console.log(error.name); }const cart = Object.preventExtensions({ price: 3 });
const proxy = new Proxy(cart, { ownKeys() { return []; } });
try { Reflect.ownKeys(proxy); } catch (error) { console.log(error.name); }It prints TypeError: a proxy cannot omit a key of a non-extensible target.
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.
const settings = { theme: "light" };
console.log(Reflect.set(settings, "theme", "dark"), settings.theme);const settings = { theme: "light" };
console.log(Reflect.set(settings, "theme", "dark"), settings.theme);It prints true dark. The UI can use the Boolean to confirm the write and the value to render the current theme.
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.
Question 1 of 7Which internal method reads a property value through the prototype chain?
Choose an answer to see the explanation.
Question 2 of 7What does the array shrink example print?
Read the code, then predictconst cart = ["tea", "milk"]; cart.length = 1; console.log(cart[1], cart.length);Choose an answer to see the explanation.
Question 3 of 7When can a sloppy arguments object link an index to its parameter?
Choose an answer to see the explanation.
Question 4 of 7What does this typed array print?
Read the code, then predictconst scores = new Uint8Array([3]); scores[0] = 259; console.log(scores[0]);Choose an answer to see the explanation.
Question 5 of 7What does the non-extensible target and incomplete ownKeys trap print?
Read the code, then predictconst 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.
Question 6 of 7Which statement describes a module namespace object?
Choose an answer to see the explanation.
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.