Function calls: [[Call]] & [[Construct]]
Trace how ECMAScript calls and constructs functions, binds this, initializes parameters, handles classes and bound functions, and routes Proxy traps.
- 01Trace a callExplain context preparation, this binding, parameter instantiation, and body evaluation.
- 02Trace constructionTell base and derived constructors apart, and explain new.target and instance prototypes.
- 03Recognize wrappersPredict bound-function and Proxy behavior with calls, new, and Reflect.
Two ways to invoke a function
In the previous execution contexts lesson, a call put a new context on the stack. Here we ask what starts that call. The specification gives a function object an internal method called [[Call]]. A function that can be used with new also has [[Construct]].
function greet(user) {
return "Hi, " + user;
}
console.log(greet("Asha"));Line 1 defines greet with one parameter. Line 2 returns a greeting made with that parameter. Line 4 calls it with "Asha" and prints Hi, Asha. The call starts greet's evaluation; the source does not invoke [[Call]] by name.
An internal method is a specification operation on an object, written in double brackets. Ordinary function calls use [[Call]]; construction uses [[Construct]] when that method exists. Neither is a JavaScript property you can read as greet["[[Call]]"].
These are two paths through function behavior, not two kinds of parentheses. An ordinary call can return a number, string, or object. Construction must return an object or throw. We will use tiny programs to observe the results, while keeping hidden spec state separate from values available to your code.
Function objects and their internal slots
A function object holds callable behavior plus state the specification needs. An internal slot is a named piece of that spec state. Its name is not a normal property key. A regular function declaration also gets construction behavior; not every callable function does.
function Order(item) {
this.item = item;
}
console.log(typeof Order, new Order("tea").item);Line 1 declares Order. Line 2 assigns its item when the function runs with an object as this. Line 4 prints function tea: typeof Order is function, and new Order("tea") creates an instance whose item is tea. That output shows behavior, not the contents of any hidden slot.
| Slot | Why the spec keeps it | What not to assume |
|---|---|---|
| [[Environment]] | The environment captured when the function was created. | Not a property you can read from the function. |
| [[FormalParameters]] | The parsed parameter list used when a call starts. | Not the arguments array or the public length property. |
| [[ECMAScriptCode]] | The parsed body selected for evaluation. | Not a string that is evaluated anew on each call. |
| [[ThisMode]] | LEXICAL, STRICT, or GLOBAL selection for this. | Not the caller's strictness or the function's name. |
| [[ConstructorKind]] | BASE or DERIVED when the function is constructed. | Not the public constructor property on an instance. |
The current ECMAScript function-object table also lists [[Realm]], [[ScriptOrModule]], and [[IsClassConstructor]]. The captured environment helps a function find outer names. Parsed parameters and code tell the call which body to evaluate. These are spec devices, not a promise about a particular engine's memory layout.
A cook knows a recipe before an order arrives. Each order gives ingredients for one dish. A function similarly keeps its code between calls, while each call supplies new inputs.
- In real life: The cook knows a recipe
- In JavaScript: A function stores code to run
- In real life: An order lists ingredients
- In JavaScript: Arguments provide inputs
- In real life: The cook makes the dish
- In JavaScript: A call evaluates the body
Where the analogy stops: The cook is a person; an internal slot is specification state and need not be a stored object.
Prepare a call and bind this
PrepareForOrdinaryCall sets up a new function execution context and local function environment. The caller waits while the callee runs. OrdinaryCallBindThis then handles the value of this, depending on the function's [[ThisMode]]. Only after that does the call evaluate the body.
function addFee(price) {
return price + 2;
}
console.log(addFee(3));Line 1 defines addFee with a parameter named price. Line 2 adds 2 and returns the sum. Line 4 passes 3 and prints 5. In spec terms, preparing the call gives this invocation its own place to bind price; it does not reuse another call's local parameter.
"Let calleeContext be PrepareForOrdinaryCall(func, undefined)."
"Perform OrdinaryCallBindThis(func, calleeContext, thisArg)."
"Let result be Completion(OrdinaryCallEvaluateBody(func, argList))."These selected lines come from the ordinary function [[Call]] algorithm. The surrounding steps handle class calls, completion records, and restoring the caller. The excerpt is spec text, not JavaScript to run. In particular, a thrown error also restores the caller's context.
Replay of the lesson's instrumented addFee call; this is not an engine debugger.
script
function addFee(price) { return price + 2;}Step through the real addFee invocation. First the caller calls, then price is stored, then the body returns 5, and finally the caller prints it. This is a replay of instrumented lesson code, not a debugger reading the engine's context stack. Back shows the earlier saved snapshot rather than a later value.
const cart = { price: 3, show() { return this.price; } };
console.log(cart.show());
console.log(Reflect.apply(cart.show, { price: 5 }, []));Line 1 makes a cart with a method that reads this.price. Line 2 calls it through cart and prints 3. Line 3 passes another object as the receiver to Reflect.apply and prints 5. The method's call still runs the same body; its supplied this changes.
Three this modes
STRICT keeps the supplied this unchanged. A bare strict call receives undefined. GLOBAL, the mode for an ordinary non-strict function, replaces null or undefined with its realm's global this and wraps other primitive receivers. LEXICAL means an arrow does not bind a new this at all; it uses the enclosing one.
function strictThis() {
"use strict";
return this;
}
console.log(strictThis() === undefined);Line 1 defines strictThis. Line 2 switches its body to strict mode. Line 3 returns this unchanged; line 5 prints true because the unbound call has undefined as its receiver. The check avoids relying on a browser-specific global object name.
function looseThis() {
return this;
}
console.log(looseThis.call(null) === globalThis);
console.log(typeof looseThis.call(3));Lines 1 to 3 define a non-strict function that returns its receiver. Line 4 passes null and prints true: the function uses globalThis. Line 5 passes 3 and prints object, because the number is boxed. The replacement happens when the callee binds this, not when .call receives the input.
const cart = { price: 3, makeReader() { return () => this.price; } };
const readPrice = cart.makeReader();
console.log(readPrice.call({ price: 5 }));Line 1 defines a cart whose method makes an arrow. Line 2 calls that method through the cart and saves the arrow. Line 3 tries to supply a different receiver, but prints 3. The arrow reads the cart from its enclosing method call. Its lexical this is why changing the later call receiver has no effect.
See OrdinaryCallBindThis and OrdinaryFunctionCreate for the exact branches. The modes describe the function being called, not whether its caller happened to write "use strict" elsewhere.
OrdinaryCallEvaluateBody selects the body
OrdinaryCallEvaluateBody hands the function's parsed [[ECMAScriptCode]] to the EvaluateBody operation. EvaluateBody has different cases for plain functions, arrows, generators, and async functions. We start with the plain case so each step has one job.
function getItem(item) {
return item;
}
console.log(getItem("tea"));Line 1 declares getItem with a parameter. Line 2 returns that parameter. Line 4 calls it with "tea" and prints tea. To reach line 2, JavaScript must already have given the parameter its value. The function body is evaluated for this invocation, not stored as a string and reparsed by the example.
"Return ? EvaluateBody of func.[[ECMAScriptCode]] with arguments func and argList."
"Perform ? FunctionDeclarationInstantiation(funcObj, argList)."OrdinaryCallEvaluateBody delegates to the appropriate EvaluateBody operation. For a plain function, EvaluateFunctionBody performs FunctionDeclarationInstantiation before body statements. The short excerpt quotes one step from each algorithm; the full spec also specifies returns and abrupt completion.
This distinction helps when reading a more advanced algorithm. The call method sets up the context and this. EvaluateBody selects the right kind of body. Binding parameters and declarations is part of entering that body. None of those names is a JavaScript API your app should call.
FunctionDeclarationInstantiation: hoisting, precisely
FunctionDeclarationInstantiation prepares bindings for a particular invocation before its ordinary body statements run. It initializes parameters and function declarations, creates var bindings initialized to undefined, and creates lexical bindings that remain uninitialized until their declarations execute. That is the useful meaning behind the informal word hoisting.
function receipt(item) {
console.log(label(item), fee);
var fee = 2;
function label(value) { return "Item: " + value; }
}
receipt("tea");Line 1 starts the function. Line 2 calls label(item) and reads fee. Line 3 assigns 2 to fee, but has not run when line 2 logs. Line 4 declares label; it was initialized during setup. Line 6 passes tea. The output is Item: tea undefined.
The specification's FunctionDeclarationInstantiation distinguishes parameter names, var names, lexical names, and function declarations. It processes function declarations so the last declaration for a duplicate name wins where duplicates are permitted. That is more precise than saying all code "moves to the top": the assignment on line 3 stays in its original execution order.
function total(price = 3) {
return price + 2;
}
console.log(total());Line 1 gives price a default of 3. Line 2 returns the value plus 2. Line 4 calls without an argument, so the default runs while parameters are initialized and the output is 5. When default parameter expressions exist, the spec may create a separate environment for body declarations. A closure made in a default initializer should not quietly see a later body declaration.
function readEarly() {
console.log(typeof price);
let price = 3;
}
try { readEarly(); } catch (error) { console.log(error.name); }Line 1 declares readEarly. Line 2 attempts to inspect price before line 3 initializes it. Line 5 calls the function and catches the error, printing ReferenceError. Even typeof cannot read a local lexical binding before initialization. This is different from the var fee case above.
Look at parameter setup first, then which declarations get bindings, then which bindings get values before the body. After that, read the statements in source order. A binding can exist before the statement that assigns it.
[[Construct]] and new.target
A constructor is a function object with a [[Construct]] method. A new expression uses that method and supplies a newTarget: the constructor selected for this construction. For an ordinary base constructor, the spec creates an instance first, binds it as this, and then runs the body.
function Order(item) {
this.item = item;
}
console.log(new Order("tea").item);Line 1 declares Order. Line 2 puts the argument on this.item. Line 4 constructs it with "tea" and prints tea. The object is not created by line 2: construction supplies an object for that line to update. A plain call to this non-strict function would not create the same instance.
"Let kind be func.[[ConstructorKind]]."
"Let thisArg be ? OrdinaryCreateFromConstructor(newTarget, "%Object.prototype%")."
"Let calleeContext be PrepareForOrdinaryCall(func, newTarget)."The function [[Construct]] algorithm branches on [[ConstructorKind]]. For a base constructor it uses OrdinaryCreateFromConstructor, which reads the prototype property from newTarget. If that property is not an object, a realm-specific intrinsic prototype is used. This excerpt omits instance-field initialization and return-completion rules.
Replay of the lesson's instrumented Order constructor; it does not inspect internal slots.
script
function Order(item) { this.item = item;}console.log(order.item);Follow the instrumented construction: line 4 starts it, line 1 receives the argument, line 2 writes to the new object, and line 5 reads its property. The actual Order constructor runs when the recording is made. The frames explain the observable program, not a direct inspection of [[Construct]] or engine memory.
function Order(item) { this.item = item; }
const order = new Order("tea");
console.log(order.item);"tea"The input replaces the string on line 2 of the displayed source.
new Order("tea") stores "tea" on the instance.
Change only the item above, then compare the instance's property. Reset restores tea. The visible source shows the initial value on line 2; the control substitutes your input for that one value when it constructs another real instance. Empty text is still a valid item and appears as an empty string in the result.
function Order(item) {
this.item = item;
this.createdBy = new.target.name;
}
console.log(new Order("tea").createdBy);Line 1 defines Order. Line 2 writes its item; line 3 records new.target.name. Line 5 constructs Order and prints Order. For this direct new expression, new.target and the function whose body runs are the same function. That need not hold when another newTarget is supplied.
function Order(item) { this.item = item; }
function SpecialOrder() {}
SpecialOrder.prototype.kind = "gift";
const order = Reflect.construct(Order, ["tea"], SpecialOrder);
console.log(order.item, order.kind, order instanceof SpecialOrder);Line 1 defines Order. Line 2 defines SpecialOrder. Line 3 gives its prototype kind: "gift". Line 4 uses Reflect.construct with Order as the body and SpecialOrder as newTarget. Line 5 prints tea gift true: the body wrote tea, while the new instance inherits kind through SpecialOrder.prototype.
You choose a shopping-list format before writing tea on it. The format and the item answer different questions. Construction similarly separates the chosen instance prototype from the body that writes properties.
- In real life: Choose a list format
- In JavaScript: newTarget supplies a prototype
- In real life: Write tea on the list
- In JavaScript: The constructor body writes item
- In real life: Read the finished list
- In JavaScript: Construction returns an object
Where the analogy stops: A real shopping list has no prototype chain; the analogy only separates the chosen format from what gets written.
Base and derived class constructors
A base class constructor has the BASE constructor kind. Like a regular base constructor, it receives an object before its body assigns properties. A derived class constructor, created with extends, has the DERIVED kind: its this starts uninitialized until super() constructs the parent and establishes the binding.
class Order {
constructor(item) { this.item = item; }
}
console.log(new Order("tea").item);Line 1 declares Order. Line 2 receives item and writes it to this. Line 4 constructs an instance and prints tea. The class definition is strict code, but strictness does not prevent its constructor from using this when it is properly constructed.
class Order { constructor(item) { this.item = item; } }
class GiftOrder extends Order {
constructor(item) {
super(item);
this.gift = true;
}
}
console.log(new GiftOrder("tea").item);Line 1 defines the base Order. Line 2 extends it. Line 4 calls super(item), which makes the base instance. Line 5 can then write gift on the resulting this. Line 8 constructs the derived class and prints tea. The newTarget remains the derived class through the super construction.
In the ordinary [[Construct]] algorithm, only the BASE branch makes an object before evaluating its own body. The derived function environment begins with an uninitialized this binding. The super call constructs the superclass with the same newTarget and binds the resulting object.
class Order {}
class GiftOrder extends Order {
constructor() {
this.gift = true;
super();
}
}
new GiftOrder();Line 1 defines a base class. Line 2 declares its subclass. Line 4 tries to use this before line 5 calls super(). Line 8 starts construction. The operation throws a ReferenceError before the assignment can finish. The exact message varies by engine, so rely on the error type.
Callable is not the same as constructable
A class constructor has a [[Call]] method, but that method throws a TypeError for an ordinary call without new. An arrow or a concise method can be called, but lacks [[Construct]]. Trying new on either throws a TypeError before its function body starts. The error messages are not standardized text.
class Order {}
Order();Line 1 declares the class. Line 2 invokes it without new, so it throws TypeError. This is not the same as lacking [[Call]]: the class call method itself detects that it must reject a call.
const makeOrder = (item) => ({ item });
new makeOrder("tea");Line 1 makes a callable arrow. Line 2 attempts to construct it and throws TypeError. The arrow's ordinary call would return an object, but returning an object does not install [[Construct]] on the function.
const cart = { show() { return "tea"; } };
console.log(cart.show());
console.log(Object.hasOwn(cart.show, "prototype"));Line 1 defines a concise show method. Line 2 calls it and prints tea. Line 3 checks for an own prototype property and prints false. The method is not a constructor; do not confuse this with a regular function expression merely stored as a property. The method-definition algorithms do not make this concise method a constructor.
Bound function exotic objects
Function.prototype.bind creates a bound function exotic object: a wrapper around a callable target. It remembers a [[BoundTargetFunction]], a [[BoundThis]] value, and [[BoundArguments]]. An ordinary call prepends those arguments and forwards the saved receiver.
function show(item) { return this.user + ": " + item; }
const forAsha = show.bind({ user: "Asha" }, "tea");
console.log(forAsha.call({ user: "Ravi" }));Line 1 defines show, which reads this.user. Line 2 binds Asha as the receiver and tea as the first argument. Line 3 tries to call with another receiver but prints Asha: tea. The later receiver does not displace the bound receiver.
"Let args be the list-concatenation of boundArgs and argList."
"If SameValue(func, newTarget) is true, set newTarget to target."
"Return ? Construct(target, args, newTarget)."The bound function algorithms give the wrapper [[Construct]] only when its target is constructable. During construction, the saved receiver is not used. If the wrapper itself is newTarget, the algorithm replaces it with the target before forwarding. These quoted steps describe the construction path, not the ordinary call path shown above.
function Order(item) { this.item = item; }
const TeaOrder = Order.bind({ item: "ignored" }, "tea");
const order = new TeaOrder();
console.log(order.item, order instanceof Order);Line 1 defines Order. Line 2 binds tea and an ignored receiver. Line 3 constructs via TeaOrder. Line 4 prints tea true. The item was prepended, while the new instance inherited from Order.prototype. The bound wrapper itself does not have an own prototype property.
const makeOrder = (item) => ({ item });
const TeaOrder = makeOrder.bind(null, "tea");
try { new TeaOrder(); } catch (error) { console.log(error.name); }Line 1 defines an arrow. Line 2 binds a first argument to it. Line 3 tries new but catches the error, printing TypeError. Binding does not grant [[Construct]] to a target that lacks it. This also shows why the presence or absence of a public prototype property is not a reliable constructor test.
Proxy traps and Reflect forwarding
A Proxy can intercept a callable target through an apply trap. That trap receives the target, this argument, and argument array. A constructable target can also have a construct trap, which receives the target, argument array, and newTarget. Proxy creation cannot add either ability to a target that lacks it.
function greet(user) { return "Hi, " + user; }
const traced = new Proxy(greet, {
apply(target, thisArg, args) {
return Reflect.apply(target, thisArg, args).toUpperCase();
},
});
console.log(traced("Asha"));Line 1 defines greet. Line 2 wraps it. Line 3 handles ordinary calls; line 4 forwards them with Reflect.apply and uppercases the result. Line 7 calls the proxy and prints HI, ASHA. Forwarding keeps the original function's arguments and receiver intact until the trap deliberately transforms the result.
"Let trap be ? GetMethod(handler, "apply")."
"Let trap be ? GetMethod(handler, "construct")."
"If newObj is not an Object, throw a TypeError exception."These lines come from Proxy internal methods. The apply trap implements Proxy [[Call]] when present; construct implements Proxy [[Construct]]. If a trap is absent, the relevant target method is invoked instead. A revoked Proxy throws before forwarding to either one.
function Order(item) { this.item = item; }
const traced = new Proxy(Order, {
construct(target, args, newTarget) {
return Reflect.construct(target, args, newTarget);
},
});
console.log(new traced("tea").item);Line 1 defines Order. Line 2 wraps it. Line 3 intercepts new, and line 4 forwards the arguments and newTarget. Line 7 constructs and prints tea. The trap does not need to create an instance by hand: Reflect.construct does the requested construction.
const Order = new Proxy(function () {}, {
construct() { return 3; }
});
new Order();Line 1 wraps a constructable function. Line 2 returns the primitive 3 from the construct trap. Line 4 starts construction and throws TypeError. The rule is about the trap's returned type; the engine's exact error message is not part of our example.
Reflect.apply and Reflect.construct
Reflect.apply performs a call with an explicit this value and an array-like argument list. Reflect.construct performs construction with arguments and an optional different newTarget. These are real JavaScript APIs. The internal methods they request are still not directly readable properties.
function show(item) { return this.user + ": " + item; }
console.log(Reflect.apply(show, { user: "Asha" }, ["tea"]));Line 1 defines show, which reads its receiver's user. Line 2 gives it Asha and an argument array holding tea. It prints Asha: tea. Compare this with the earlier Reflect.construct(Order, ["tea"], SpecialOrder) example: the latter chooses an instance prototype through newTarget, rather than choosing this for an ordinary call.
Calls in a checkout page
Imagine a checkout page with a cart price and a small fee. A method can read its receiver's price, then pass that value to a plain function that computes the total. You do not need to write any internal method yourself; knowing the difference between a method receiver and a parameter makes the app code easier to debug.
function showTotal(price) { return price + 2; }
const checkout = { price: 3, show() { return showTotal(this.price); } };
console.log(checkout.show());Line 1 defines showTotal to add 2. Line 2 creates checkout with a price of 3 and a method that passes this.price to that function. Line 3 calls the method through checkout and prints 5. The method call supplies checkout as this; the nested function receives 3 as price.
Passing checkout.show directly to another API could lose that receiver. If the callback must keep checkout, pass an arrow that calls checkout.show() or bind the method to checkout. There is no need for a Proxy just to preserve a callback's this. Proxy traps are better saved for cases that actually need to intercept calls.
- An arrow such as
item => item - The concise method
cart.show - A
function Order(item) {}declaration Order.bind(null, "tea")- A class
Order - A class
GiftOrder extends Order
Sort each function by its call and construction behavior. Think about the internal method, not typeof.
A class belongs in the third category even though its class constructor has a [[Call]] method: that method immediately throws. The sorter describes useful successful behavior rather than pretending the method is absent. The bound constructor shows the opposite surprise: no own prototype property, but new works.
Easy mistakes to avoid
A value may look like a function without supporting every kind of invocation. Start with what the object can do, then ask what this particular call attempts. Public properties can help explain a familiar case, but they cannot replace the spec's internal-method distinction.
const cart = { show() { return "tea"; } };
console.log(cart.show());
console.log(Object.hasOwn(cart.show, "prototype"));Line 1 defines the method. Line 2 calls it normally and prints tea. Line 3 prints false for an own prototype property. That fact is consistent with a concise method lacking [[Construct]], but the bound constructor above shows why the property check is not a universal test.
- "Every callable works with new." Arrows and concise methods are callable but not constructors.
- "A class has no [[Call]]." Its [[Call]] exists and throws TypeError when invoked without new.
- "No own prototype means new must fail." Bound functions can construct if their target can.
- "Hoisting runs the assignment early." A var binding gets undefined before its assignment runs.
- "A Proxy trap can make any target callable." The target determines the proxy's call capability.
| Idea | Meaning | Important limit |
|---|---|---|
| Callable | Has [[Call]] | May lack [[Construct]], as arrows and methods do. |
| Constructable | Has [[Construct]] | Class constructors can have [[Call]] yet reject ordinary calls. |
| prototype property | Usually supplies an instance prototype for normal constructors | Absence alone cannot prove non-constructability: bound constructors lack it. |
| new.target | The constructor originally selected for construction | Not always the function currently executing as the body. |
If new unexpectedly fails, verify the target function's kind. If an instance has the wrong prototype, check the newTarget passed through Reflect.construct or a Proxy construct trap. If a method reads the wrong property, check which this argument the call actually supplies.
Practice exercises
Read each snippet first, then run it and enter the printed output. The tasks move from one call to declaration setup, construction, and a small checkout scenario. Use the hints one at a time; the worked solution shows the code and the reason behind its result.
A checkout helper adds a small fee to a price. Read the call at the bottom, then trace the return statement. What single number appears in the console?
function addFee(price) {
return price + 2;
}
console.log(addFee(3));function addFee(price) { return price + 2; }
console.log(addFee(3));The price parameter is 3 and the body returns 5, which the caller prints.
A receipt helper uses label before its declaration and reads fee before its assignment. Type the entire console line. Explain why one name works while the other still has its initial value.
function receipt(item) {
console.log(label(item), fee);
var fee = 2;
function label(value) { return "Item: " + value; }
}
receipt("tea");function receipt(item) {
console.log(label(item), fee);
var fee = 2;
function label(value) { return "Item: " + value; }
}
receipt("tea");The output is Item: tea undefined. The function declaration is ready; var fee is still undefined.
A callback needs to know whether an unbound call has a receiver. Run the strict-mode example and type the boolean it prints. Which part of the call process decides that value?
function strictThis() {
"use strict";
return this;
}
console.log(strictThis() === undefined);function strictThis() {
"use strict";
return this;
}
console.log(strictThis() === undefined);The comparison prints true because a bare strict call receives undefined as this.
Imagine an order card created for tea. Follow new Order and identify which object line 2 changes. What does the final property read print?
function Order(item) {
this.item = item;
}
console.log(new Order("tea").item);function Order(item) {
this.item = item;
}
console.log(new Order("tea").item);The new object receives item tea, and the final property read prints tea.
A page preselects tea before creating an order. Predict both values printed by the bound constructor example. Does the saved receiver object become the new instance?
function Order(item) { this.item = item; }
const TeaOrder = Order.bind({ item: "ignored" }, "tea");
const order = new TeaOrder();
console.log(order.item, order instanceof Order);function Order(item) { this.item = item; }
const TeaOrder = Order.bind({ item: "ignored" }, "tea");
const order = new TeaOrder();
console.log(order.item, order instanceof Order);new TeaOrder() passes tea to Order and makes an Order instance. It prints tea true.
Your checkout page displays the final price from a cart method. Trace the receiver on the method call, then the argument passed into the helper. What number will the customer see in the console?
function showTotal(price) { return price + 2; }
const checkout = { price: 3, show() { return showTotal(this.price); } };
console.log(checkout.show());function showTotal(price) { return price + 2; }
const checkout = { price: 3, show() { return showTotal(this.price); } };
console.log(checkout.show());checkout.show reads price 3 and calls showTotal(3), which returns 5.
Check your understanding
Decide whether each example makes an ordinary call or constructs an instance. Then resolve parameters and this before reading the body. For class and Proxy questions, distinguish the capability of the target from what its call or construct path does with that capability.
Question 1 of 8Which internal method starts an ordinary function call?
Choose an answer to see the explanation.
Question 2 of 8What does this ordinary call print?
Read the code, then predictfunction addFee(price) { return price + 2; } console.log(addFee(3));Choose an answer to see the explanation.
Question 3 of 8When does FunctionDeclarationInstantiation happen for a plain body?
Choose an answer to see the explanation.
Question 4 of 8What does this hoisting example print?
Read the code, then predictfunction receipt() { console.log(fee); var fee = 2; } receipt();Choose an answer to see the explanation.
Question 5 of 8What does this construction check print?
Read the code, then predictfunction Order() { this.item = "tea"; } console.log(new Order().item);Choose an answer to see the explanation.
Question 6 of 8What happens when a derived constructor reads this before super()?
Choose an answer to see the explanation.
Question 7 of 8What does binding a constructable Order preserve?
Choose an answer to see the explanation.
Question 8 of 8What must a Proxy construct trap return?
Choose an answer to see the explanation.
A wrong answer is a useful clue: look at the exact point where the two paths diverge. For example, a var binding initialized to undefined is not the same as a lexical binding awaiting initialization, and a class call throwing is not the same as a missing [[Call]] method.
Key takeaways
An invocation is easier to read when you separate its setup from its body. The call or construct method chooses the path; environment setup gives each invocation its bindings; the body then runs with those values. Wrappers may forward a path without changing the target's original capability.
- [[Call]] prepares a function context, binds this according to [[ThisMode]], and evaluates the body.
- FunctionDeclarationInstantiation sets up parameters, var, lexical, and function bindings before statements run.
- [[Construct]] receives newTarget; a base constructor creates its instance before evaluating the body.
- A derived constructor needs super before using this; a class rejects ordinary calls without new.
- Arrows and concise methods have no [[Construct]]. Bound wrappers preserve construction only when their targets have it.
- Proxy apply and construct traps intercept different paths. Reflect can forward each path with explicit inputs.
Remember the one-liner.
[[Call]] invokes; [[Construct]] builds an object with a newTarget.
Coming next: Internal methods & exotic objects. We will use the same internal-method vocabulary to understand property access and unusual objects. Return to execution contexts whenever the call setup itself feels unfamiliar.