The Reference type & this
Learn how ECMAScript Reference Records explain property reads, writes, method-call this, detached functions, missing names, and super calls.
- 01Read a Reference RecordName its four fields and tell a property reference from a binding or missing-name reference.
- 02Predict thisExplain why member calls keep their receiver but comma expressions and saved methods do not.
- 03Trace reads and writesConnect GetValue, PutValue, typeof, and super to small programs with observable results.
A value and a place to find it
When you write cart.price, JavaScript does two small jobs. First it works out where to look. Then it reads what is there. You almost never notice the first job. This lesson is about that first job, and why it decides what this means inside a method.
const cart = { price: 3 };
console.log(cart.price);Line 1 makes a cart that holds a price of 3. Line 2 asks for the cart's price and prints 3. Just before the read, JavaScript knows two things: the object to look in (the cart) and the name to look for (price).
You ask a librarian for a book. She doesn't hand you the book. She writes a slip: "Shelf B, Book 3." The slip is not the book. It only tells you where the book is. Once you have the book, you don't need the slip any more.
- In real life: The slip says Shelf B
- In JavaScript: The object to look in, like
cart - In real life: The slip says Book 3
- In JavaScript: The name to look for, like
price - In real life: You walk over and take the book
- In JavaScript: JavaScript reads the value,
3
Where the analogy stops: You can keep a paper slip in your pocket. JavaScript uses its slip right away and then throws it out; your code never holds it.
A Reference Record is that slip: an address for a value. For cart.price the address is "the property price, inside cart". JavaScript works out the address first, then uses it to read or write. Your code can't see or save the address. For a method call, the address also tells JavaScript which object should become this.
The previous spec types lesson showed that the specification describes JavaScript with records like this one. Here you'll see one record answer an everyday question: why does cart.show() know about the cart, but a saved copy of show does not?
Reference Records
An address has a few parts, like a postal address has a street and a house number. The base is the place to start looking, such as the cart. The referenced name is the thing to find there, such as price. There is also a yes-or-no part for strict mode, and one more part that only super uses. These parts live inside the specification. They are not properties on your cart.
const cart = { price: 3 };
const price = cart.price;
console.log(price);Line 1 makes the cart. Line 2 reads cart.price and keeps the number in a new name, price. Line 3 prints 3. After line 2, price is just the number 3. It does not remember that it came from the cart. The address was used once, for the read, and then it was gone.
Reference Record fields:
[[Base]], [[ReferencedName]], [[Strict]], [[ThisValue]]
// Spec notation, not JavaScript syntax.This is how the specification writes those parts. You don't need to memorize them; read them like a form. [[Base]] is where to look. It can be an object, an Environment Record (the spec's list of variables in a scope), or "unresolvable", which means nothing was found. [[ReferencedName]] is the name to look up. [[Strict]] says whether strict-mode rules apply. [[ThisValue]] is usually empty; only super fills it in. The full wording is in the ECMAScript Reference Record definition. The block above is a summary, not code you can run.
You open your contact list, find Asha, and read her number. You need both the list and the name to find the entry.
- In real life: Open your phone contacts
- In JavaScript: A base gives a place to search
- In real life: Find Asha by name
- In JavaScript: A referenced name selects one entry
- In real life: Read Asha's number
- In JavaScript: GetValue obtains the stored value
- In real life: Change the saved number
- In JavaScript: PutValue writes through a location
Where the analogy stops: A phone contact is a stored item; a Reference Record is temporary spec machinery, not a stored JavaScript value.
The address looks different depending on what you wrote. The table below shows the four cases you'll meet. Read each row as: "when I write this, JavaScript looks here, and this is what I get."
| When you write | Where JavaScript looks | What happens |
|---|---|---|
cart.price, a property | Inside the cart object, for a property called price. | You get 3. If you call a method this way, like cart.show(), the method gets cart as this. |
price, a plain variable | In the current scope, for a variable called price. | You get its value. Calling a plain name like show() gives no object to use as this. |
missingItem, a name that was never created | Everywhere it can, and finds nothing. | Reading it throws a ReferenceError. |
super.show(), inside a class | In the parent class, for a method called show. | The parent's method runs, but this is still your own object. |
The first part of the address, the base, is filled in differently each time. A plain variable such as price is found in a scope, so its base is an Environment Record. You'll meet those in the next lesson. A property such as cart.price uses the cart as its base. A name that doesn't exist anywhere gets the unresolvable base, so JavaScript can report an error instead of guessing.
How a method call chooses this
A member expression is a property access with a dot, like cart.show. Here is the key point. When you write cart.show() with the brackets right after it, JavaScript still has the address in hand when the call starts. The address says "show, inside cart". So the call knows to use cart as this.
In a busy room you say, "Asha, read me your shopping list." Asha knows "your" means her list, because you spoke to her. If you shout "Read me your shopping list!" to nobody, no one knows whose list you mean.
- In real life: Asha, read me your list
- In JavaScript:
cart.show() - In real life: Asha is the person you spoke to
- In JavaScript:
thisiscart - In real life: Shouting the request to nobody
- In JavaScript: Calling
show()on its own
Where the analogy stops: A room full of people might guess. JavaScript never guesses: a call with no object gets undefined, or the global object in older non-strict code.
const cart = {
price: 3,
show() { return this.price; },
};
console.log(cart.show());Line 1 starts the cart. Line 2 gives it a price of 3. Line 3 adds a show method that returns this.price. Line 5 calls cart.show() and prints 3. You called show through the cart, so this is the cart and this.price is 3.
show() function in both. Called from the cart, it works on the cart. Copied into its own box and called alone, it has no cart to work on.Let baseValue be ? Evaluation of MemberExpression.
Let propertyNameValue be ? Evaluation of IdentifierName.
Return a Reference Record whose [[Base]] is baseValue.
// Condensed steps from property access evaluation.The spec's property access steps don't fetch the property straight away. They hand back an address with two parts: the cart and the name show. The outline above skips some details, such as private fields. The address is a way to describe behavior; it is not an object stored in memory.
Let func be ? GetValue(ref).
If IsPropertyReference(thisValueRef) is true,
let thisValue be GetThisValue(thisValueRef).
Otherwise, let thisValue be undefined.
// Condensed from call evaluation and EvaluateCall.Now the call itself. EvaluateCall does two things with the address. It uses the address to fetch the function. It also keeps the address to one side and asks, "Which object was this inside?" For cart.show the answer is the cart, so this becomes the cart. If there is no object in the address, this starts as undefined. A strict function keeps undefined. An older non-strict function swaps it for the global object.
Replay of instrumented example code, not a JavaScript engine debugger.
script
price: 3, show() { return this.price; },};console.log(cart.show());Press Step to walk through the replay. You'll see the cart made, the call begin, and the method receive the cart as this. The last step shows the real value the method returned. This replays the example code; it is not a live view inside the engine.
Parentheses, commas, and detaching
Small changes in how you write a call can keep the address or throw it away. Brackets around cart.show only group it, like brackets in maths. They don't read the value early. So the address survives, and the method still gets the cart.
const cart = {
price: 3,
show() { return this.price; },
};
console.log((cart.show)());Lines 1 to 4 make a cart with a show method. Line 5 calls (cart.show)() and prints 3. The extra brackets passed the same address along, so show still read the cart's price.
Return ? Evaluation of Expression.
// Grouping evaluation preserves the Reference result.The spec's grouping step just hands back whatever was inside. If an address went in, an address comes out. The comma operator is different. It always reads the value on its right before handing it on, so only the value comes out and the address is gone.
"use strict";
const cart = {
price: 3,
show() { return this === undefined; },
};
console.log((0, cart.show)());Line 1 turns on strict mode. Lines 2 to 5 make the cart and a method that checks whether this is undefined. Line 6 works out 0, then cart.show, and calls the function it got. It prints true: the method had no cart, so this was undefined.
Let lval be ? Evaluation of Expression.
Perform ? GetValue(lval).
Let rref be ? Evaluation of AssignmentExpression.
Return ? GetValue(rref).
// Comma operator evaluation.The comma steps read the value on the right. Reading swaps the address for a plain function, and the fact that it came from the cart is lost before the call starts. Saving the method in a variable does the same thing. The variable holds the function, not the place it came from.
A remote works when you point it at the TV. Take it to the kitchen and press the same button: it's the same remote, but there's no TV in front of it. A saved method is the same function. It just isn't pointed at the cart any more.
- In real life: Press the remote in the living room
- In JavaScript:
cart.show()works on the cart - In real life: Carry the remote to the kitchen
- In JavaScript:
const show = cart.show - In real life: Press it; nothing changes
- In JavaScript:
show()has no cart to work on
Where the analogy stops: A real remote still points at one TV. A JavaScript function points at nothing until a call gives it a this.
"use strict";
const cart = {
price: 3,
show() { return this === undefined; },
};
const show = cart.show;
console.log(show());Lines 1 to 5 set up the strict example. Line 6 reads cart.show and saves the function as show. Line 7 calls show() by its new name and prints true. Calling a plain name is not the same as calling through the cart.
Replay of instrumented example code, not a JavaScript engine debugger.
script
"use strict"; price: 3, show() { return this === undefined; },};const show = cart.show;console.log(show());In this replay, watch the function get saved first and then called with no cart. Because the code is strict, this stays undefined. The replay records the real call; it doesn't claim to show the hidden address.
"use strict";const cart = { price: 3, show() { return this === cart; },};console.log(cart.show());trueMemberthis is cart
The property Reference reaches the call. The cart is this.
Try each call shape in the playground. Member and Grouped print true for this === cart. Comma and Saved print false. Reset goes back to Member. The code line changes with your choice, so you can run each version yourself.
| How you call it | Does the call still know about the cart? | So this is |
|---|---|---|
cart.show() | Yes. A dot call carries the address "show, inside cart" all the way to the call. | cart |
(cart.show)() | Yes. Brackets only group; the address passes through them. | cart |
(0, cart.show)() | No. The comma hands back just the function, without its address. | undefined in strict code |
const show = cart.show; show() | No. The variable holds just the function, not where it came from. | undefined in strict code |
GetValue and PutValue
An address can be used in two ways. GetValue uses it to read: go to the place and bring back what's there. PutValue uses it to write: go to the place and put something new there. Think of a locker. The same locker number lets you take your bag out or put a new bag in.
const cart = { price: 3 };
const price = cart.price;
console.log(price);Line 1 makes cart.price with the value 3. Line 2 goes to that place, gets 3, and keeps it in price. Line 3 prints 3. Once the read is done, price is only a number. It has forgotten where it came from.
const cart = { price: 3 };
cart.price = 4;
console.log(cart.price);Line 1 makes the cart. Line 2 puts 4 into cart.price, using the address of that property. Line 3 reads the price again and prints 4. The cart changed. The number 3 itself didn't change; it was replaced.
GetValue(V): if V is not a Reference Record, return V.
If IsUnresolvableReference(V), throw a ReferenceError.
For a property reference, return ? baseObj.[[Get]](name, receiver).
PutValue(V, W): if V is not a Reference Record, throw a ReferenceError.
For a property reference, perform ? baseObj.[[Set]](name, W, receiver).
// Condensed from GetValue and PutValue.The full GetValue and PutValue steps also handle variables, strict mode, proxies, and errors. The short version above shows only the property path. The object used as this during the read or write matters for getters and setters, and especially for super.
const cart = {
get price() { console.log("reading price"); return 3; },
};
console.log(cart.price);Line 1 starts the cart. Line 2 adds a price getter: reading it logs "reading price" and returns 3. Line 4 prints the 3. So a read is not always a simple copy. Sometimes reading runs code, like a shop assistant who checks the stock room before telling you the price.
const cart = { price: 3 };
(cart.price) = 4;
console.log(cart.price);Line 1 makes the cart. Line 2 writes 4 into (cart.price). The brackets still point at the same place. Line 3 prints 4. Brackets don't read early here either, so the write still knows where to go.
Missing names and typeof
Sometimes the name you ask for doesn't exist. Then the base part of the address says "unresolvable": nothing was found. This is an unresolvable Reference. Reading it throws a ReferenceError. But typeof has a special rule for it, so you can safely ask "does this exist?" It's like asking the front desk whether a guest is staying at the hotel, rather than knocking on a room door that isn't there.
console.log(typeof missingItem);
try {
console.log(missingItem);
} catch (error) {
console.log(error.name);
}Line 1 prints undefined. That is the text typeof gives for a missing name. Line 3 tries to read the name directly. Lines 4 and 5 catch the error and print ReferenceError. Only the direct read failed; typeof did not throw.
If IsUnresolvableReference(value) is true, return "undefined".
// typeof evaluation checks before calling GetValue.The spec's typeof steps check for an address that leads nowhere before trying to read it. This rule is narrow. A let or const that exists but hasn't been reached yet (its temporal dead zone) still throws ReferenceError, even with typeof.
"use strict";
try {
missingItem = "tea";
} catch (error) {
console.log(error.name);
}Line 1 turns on strict mode. Line 3 tries to write to a name that doesn't exist. Lines 4 and 5 catch the error and print ReferenceError. Old non-strict code would quietly make a new global variable instead. Don't rely on that; it hides typing mistakes.
Super references
super is the one case where the last part of the address gets used. A super Reference splits two jobs apart: where to find the method, and which object the method should work on. It finds the method on the parent class, but it keeps working on the current object.
You want to bake Mum's cake, so you look up the recipe in her book. But you don't drive to her house to bake it. You follow her steps in your own kitchen, with your own eggs and flour.
- In real life: Look up the recipe in Mum's book
- In JavaScript: Find
showonParent.prototype - In real life: Cook in your own kitchen
- In JavaScript:
thisstays your object - In real life: The cake ends up on your table
- In JavaScript: The parent method reads your
this.name
Where the analogy stops: A recipe is a fixed set of steps. A JavaScript method can be looked up on any parent in the prototype chain.
class Parent {
show() { return this.name; }
}
class Child extends Parent {
show() { return super.show(); }
}
console.log(new Child().show.call({ name: "Asha" }));Lines 1 to 3 make Parent.show, which returns this.name. Lines 4 to 6 make Child.show, which just calls super.show(). Line 7 calls Child's show with an object named Asha as this, and prints Asha. The parent's method worked on that same object.
super changes where JavaScript finds the method, not which object it works on. Parent's show() runs on your object, so it gives Asha.For a super reference, GetThisValue(V) returns V.[[ThisValue]].
Otherwise it returns V.[[Base]].
// GetThisValue, condensed.In the spec's GetThisValue steps, a super address answers "who is this?" from its [[ThisValue]] part, not from its [[Base]] part. [[Base]] says where to find show: Parent.prototype. [[ThisValue]] says who show works on: the Asha object. So super never makes the parent this.
| Question | Answer in the example | Why it matters |
|---|---|---|
| Where is the method found? | On Parent.prototype, the parent class. | That's whose show runs. |
| Which method? | show | It's the name being looked up. |
Who is this? | The object named Asha. | So this.name inside the parent's show gives Asha. |
The same split applies to getters and setters you inherit. They are found on a parent, but they work on your object. Keep this in mind when you use classes that extend other classes.
Fixing a real callback
Here is where this bites in real apps. A callback is a function you hand over to be run later, for example when a button is clicked. If you hand over cart.show on its own, you hand over only the function. When the button runs it later, the cart is gone. The fix is a small wrapper that calls cart.show() at the right moment.
"use strict";
const cart = {
price: 3,
show() { return this.price; },
};
const button = { onclick: () => cart.show() };
console.log(button.onclick());Line 1 turns on strict mode. Lines 2 to 5 make the cart and its show method. Line 6 saves a small arrow function that calls cart.show(). Line 7 runs it and prints 3. The arrow remembers the cart, and when it runs it makes a normal cart.show() call.
A saved phone number can be dialed without opening its contact page. Opening the contact page again keeps the name and number together.
- In real life: Save a person's phone number
- In JavaScript: Save the method function value
- In real life: Keep the contact open
- In JavaScript: Keep the object with the method call
- In real life: Call from the contact page
- In JavaScript: Use cart.show() at call time
Where the analogy stops: A phone cannot model JavaScript's strict-mode this substitution or inherited property lookups.
Want more practice with this? The this keyword and Losing this lessons show common call patterns and fixes. Arrow functions and lexical this explains why the arrow wrapper doesn't change this for show. This lesson gives you the reason underneath all of them.
cart.show()(cart.show)()(0, cart.show)()const show = cart.show; show()const price = cart.pricecart.price = 4
Sort each expression by what reaches the call or whether it reads or writes a property.
Common mix-ups
The word "reference" means two different things, which causes confusion. People often say a variable holds a "reference" to an object; that just means it points at the object. A spec Reference Record is the short-lived address from this lesson. It only says where to find something, and it disappears right after it is used.
const cart = { price: 3 };
console.log((0, cart.price));Line 1 makes a cart with price 3. Line 2 works out 0, then cart.price, and prints 3. The comma doesn't delete anything. It reads the value and hands it on, just without the address.
- "Brackets detach a method." No. (cart.show)() keeps the address, so it still uses the cart.
- "A method always remembers its object." No. The way you call it decides this. A saved copy forgets.
- "A bare call always has undefined this." Only in strict code. Older non-strict code uses globalThis.
- "typeof never throws." It is safe for missing names, but a let or const used too early still throws.
- "super makes the parent this." No. It finds the method on the parent but keeps your object as this.
| Word | What it means | What it does not mean |
|---|---|---|
| Reference Record | A short-lived address that says where to find a name or property. | The object a variable points to. People also call that a "reference", but it's a different thing. |
| GetValue | Use the address to read the value. | Change the object, or remember which object the value came from. |
| GetThisValue | Ask the address which object should be this. | Always use the parent (the prototype) as this. |
| Strict call with no object | this stays undefined. | The call throws an error. It doesn't; it just runs with no object. |
The next two examples show the difference between strict and non-strict code. Both make the same kind of call with no object. Only the strict setting changes, so you can see what that one setting does.
function show() { return this === globalThis; }
const cart = { show };
const detached = cart.show;
console.log(detached());Line 1 makes a non-strict function that checks whether this is globalThis. Line 2 puts it on the cart. Line 3 saves the function. Line 4 calls it with no cart and prints true. Older non-strict functions fill an empty this with the global object.
"use strict";
function show() { return this === undefined; }
const cart = { show };
const detached = cart.show;
console.log(detached());Line 1 turns on strict mode. Lines 2 and 3 make the method and put it on the cart. Line 4 saves the function. Line 5 calls it with no cart and prints true for this === undefined. Strict functions leave this empty.
Practice exercises
Guess what each short program does before you run it. The first ones ask for the output. The later ones ask you to fix a button callback and read a parent method. Try a guess first, then use the hints if you get stuck.
A cart owns a method that reads its price. Predict the single printed number after adding parentheses around the property access. Does grouping change the receiver?
const cart = {
price: 3,
show() { return this.price; },
};
console.log((cart.show)());console.log((cart.show)()); // 3The grouped expression keeps the property Reference and show reads cart.price.
A page checks for a name that was never declared. Write the two printed lines in order, separated by a comma. Explain to yourself why only one read throws.
console.log(typeof missingItem);
try {
console.log(missingItem);
} catch (error) {
console.log(error.name);
}// undefined
// ReferenceErrortypeof missingItem returns the string undefined. Reading missingItem directly throws ReferenceError, which the catch prints.
An order's displayed price changes after a discount. Predict the number printed by the assignment example. Which operation writes, and which one reads?
const cart = { price: 3 };
cart.price = 4;
console.log(cart.price);cart.price = 4;
console.log(cart.price); // 4PutValue changes the cart property to 4. GetValue then reads 4.
Your checkout button needs to show the cart price. It was handed a detached method, so this inside show is wrong. What should the callback call to keep the cart? Write the important expression.
"use strict";
const cart = { price: 3, show() { return this.price; } };
const button = { onclick: cart.show };
// Replace onclick with a callback that keeps the cart.
console.log(button.onclick());button.onclick = () => cart.show();
console.log(button.onclick()); // 3The wrapper performs a fresh member call. The method receives cart, and the console prints 3.
A child class asks its parent method to read a name. The call supplies an object named Asha as this. Whose name does super.show() return, and why?
class Parent {
show() { return this.name; }
}
class Child extends Parent {
show() { return super.show(); }
}
console.log(new Child().show.call({ name: "Asha" }));console.log(new Child().show.call({ name: "Asha" })); // AshaThe parent method reads this.name on the receiver supplied to the call.
A strict function is saved from a cart and called by its new name. Predict whether it prints true, false, or throws. Then change the call to cart.show() and check the difference.
"use strict";
function show() { return this === undefined; }
const cart = { show };
const detached = cart.show;
console.log(detached());console.log(detached()); // trueThe bare call passes undefined this. Strict code keeps it, so the comparison is true.
Check your understanding
For each question, ask one thing first: does the call still have the address (a dot call like cart.show()), or just a plain function? For reads and writes, ask whether the code takes a value out or puts one in. Each answer explains why it's right or wrong.
Question 1 of 7Which field holds the property name in a Reference Record?
Choose an answer to see the explanation.
Question 2 of 7What does the parenthesized call print?
Read the code, then predictconst cart = { price: 3, show() { return this.price; }, }; console.log((cart.show)());Choose an answer to see the explanation.
Question 3 of 7What does the comma call print?
Read the code, then predict"use strict"; const cart = { price: 3, show() { return this === undefined; }, }; console.log((0, cart.show)());Choose an answer to see the explanation.
Question 4 of 7What does the property assignment print?
Read the code, then predictconst cart = { price: 3 }; cart.price = 4; console.log(cart.price);Choose an answer to see the explanation.
Question 5 of 7What does typeof a missing identifier do here?
Read the code, then predictconsole.log(typeof missingItem); try { console.log(missingItem); } catch (error) { console.log(error.name); }Choose an answer to see the explanation.
Question 6 of 7What does the strict bare call print?
Read the code, then predict"use strict"; function show() { return this === undefined; } const cart = { show }; const detached = cart.show; console.log(detached());Choose an answer to see the explanation.
Question 7 of 7What does the super call print?
Read the code, then predictclass Parent { show() { return this.name; } } class Child extends Parent { show() { return super.show(); } } console.log(new Child().show.call({ name: "Asha" }));Choose an answer to see the explanation.
Key takeaways
A method doesn't carry its object around. The way you call it decides whether JavaScript still knows which object to use as this.
- A Reference Record is a short-lived address that says where to find a name or property: [[Base]], [[ReferencedName]], [[Strict]], and [[ThisValue]].
- GetValue uses the address to read. PutValue uses it to write.
- In cart.show(), the address reaches the call, so this is the cart.
- Brackets keep the address. A comma, or saving the method in a variable, throws it away.
- With no object, strict code leaves this undefined; older non-strict code uses globalThis.
- typeof is safe for missing names, and super finds the method on the parent but keeps your object as this.
Remember the one-liner.
Call a method with a dot, and it knows its object. Save it first, and it forgets.
Coming next: Environment Records: how scope is specified. It explains where plain variable names, like price, are found.