Topic 6.2
Reading and Changing Properties
In one line
You read and write properties with a dot (obj.name) or square brackets (obj[key]), add them by assigning, remove them with delete, test for them with in or Object.hasOwn, and read deep values safely with optional chaining ?..
Think of it like this
A row of school lockers with name tags. To open Asha's locker you can walk to the tag that says "Asha" (that's the dot: you know the name while writing the code), or a teacher can hand you a slip of paper with a name on it and you go to whichever locker the slip says (that's square brackets: the name is only known while the program runs). Asking for a locker that doesn't exist gives you nothing (undefined). Asking for a locker in a building that doesn't exist is a real problem (an error).
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Dot notation
- Reading or writing a property by writing its name after a dot:
user.name. - Bracket notation
- Reading or writing a property with an expression in square brackets:
user["name"]oruser[key]. - Own property
- A property stored on the object itself, not borrowed from its prototype.
- Prototype
- A parent object that an object looks in when it doesn't have a property itself (Phase 7.3).
- Inherited property
- A property an object can use because its prototype has it, like
toString. - delete
- An operator that removes a property from an object completely.
- Optional chaining
?.: read a property or call a method only if the value on the left isn'tnullorundefined.- TypeError
- The error you get when you use a value the wrong way, like reading a property of
undefined.
Step by step
01Read with a dot
When you know the key while writing the code and it's a valid name, use a dot. It's the shortest and easiest to read.
const book = { title: "Malgudi Days", pages: 256 };
console.log(book.title); // Malgudi Days02Read with brackets when the key varies
Here the key comes from a variable. book.field would look for a property literally called field; book[field] uses the variable's value.
const field = "pages";
console.log(book[field]); // 256
console.log(book.field); // undefined03Add, change and delete
Assigning to a key that doesn't exist creates it. Assigning to one that exists replaces its value. delete removes it.
book.author = "R. K. Narayan"; // added
book.pages = 260; // changed
delete book.pages; // removed
console.log(book); // { title: 'Malgudi Days', author: 'R. K. Narayan' }04Check whether a key exists
Comparing with undefined can't tell "missing" from "present but undefined". Use Object.hasOwn for the object's own keys and in when inherited ones count too.
console.log(Object.hasOwn(book, "title")); // true
console.log("toString" in book); // true (inherited)
console.log(Object.hasOwn(book, "toString")); // false05Follow a chain safely
Each ?. checks the value on its left. The first one that is null or undefined stops the chain and the result is undefined. Nothing to the right of it runs.
Try it yourself
- 1
Read a key from a variable
In the first example, change
keyto"page count". Predict whatbook[key]prints now, then run. - 2
Undefined versus missing
In the second example, add
console.log(Object.keys(cart));before and after thedelete. Predict both lists. - 3
Give Ravi an address
In the third example, add
address: { city: "Kochi" }to Ravi. Predict every line of the console, including the last one.
Code & diagrams
`book.key` looks for a property named `key`, which doesn't exist. `book.author` doesn't exist either: missing properties read as `undefined`, without an error.
Preview
Console
Nothing logged yet.Expected console output
Wings of Fire
180
1999
undefined
undefinedReading `cart.cherries` gives `undefined` both before and after `delete`; only `in` (or `Object.hasOwn`) can tell that the key was there. `Object.hasOwn` is ES2022; the rest is much older.
Preview
Console
Nothing logged yet.Expected console output
{ apples: 5, bananas: 6, cherries: undefined }
true undefined
false undefined
true false
true`u.greet?.()` calls the method only if it exists. The last block shows what happens without `?.`: Ravi has no `address`, so reading `.city` of `undefined` throws.
Preview
Console
Nothing logged yet.Expected console output
Asha Pune Pune Hi, I'm Asha
Ravi undefined city unknown undefined
undefined
TypeError: Cannot read properties of undefined (reading 'city')Both objects turned into the same string key, `"[object Object]"`, so the second write replaced the first. A `Map` (Phase 14.3) can use objects as real keys.
Preview
Console
Nothing logged yet.Expected console output
number one
for b
[ '1', '[object Object]' ]Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Read deep into something missing
In the third example, replace u.address?.city with u.address.city in the line that sets city.
Break #2
Dot with a variable
In the first example, write book.key where you meant the value of key.
Myth vs fact
Myth
If obj.x === undefined, the object has no x property.
Fact
The property may exist and hold undefined. Use Object.hasOwn(obj, "x") or "x" in obj to check existence.
Myth
delete frees memory immediately.
Fact
delete only removes a property from one object. Memory is freed later by the garbage collector, and only if nothing else refers to the value.
Myth
Optional chaining should go everywhere to prevent errors.
Fact
It hides bugs when a value should always be there. Use it where missing data is a normal case (optional fields, API data), not to silence real mistakes.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
deletecan push a V8 object into dictionary mode (a slower hash-table storage), because the object no longer matches a shared shape. In hot code, prefer building a new object without the key (rest destructuring, Topic 6.3) or setting it toundefinedif the key may stay. - ▸
?.short-circuits the whole chain: ina?.b.c.d, ifais nullish none of.b.c.druns. Parentheses end the chain:(a?.b).cthrows ifais nullish. You can't assign through it:a?.b = 1is a SyntaxError. - ▸
inalso works for arrays (2 in ["a", "b", "c"]istrue, checking indexes) and is the standard way to feature-detect:"IntersectionObserver" in window. - ▸
In strict mode (modules and classes),
deleteon a non-configurable property throws a TypeError instead of returningfalse, anddelete someVariableis a SyntaxError.
Remember this
- 1
Dot notation,
obj.key, is the everyday way. The key must be a valid name, written directly in the code. Bracket notation,obj[expression], works out the expression first and uses the result, turned into a string, as the key. You need brackets when the key is in a variable (obj[field]), has spaces or dashes (obj["first-name"]), starts with a digit, or is built from pieces (obj["item" + i]).obj.fieldandobj[field]are different: the first looks for a key literally namedfield. - 2
Reading a property that doesn't exist is not an error: you get
undefined. That's handy and dangerous at once: a typo likeuser.nmaegivesundefinedsilently. But reading a property ofundefinedornullthrows:undefined.citygivesTypeError: Cannot read properties of undefined (reading 'city'). This is the most common error in JavaScript. - 3
Assignment both changes and adds:
obj.age = 15changesageif it exists and creates it if it doesn't. **delete obj.key** removes the property completely and returnstrue. Setting a property toundefinedis different: the key still exists, it only holdsundefined. Usedeletewhen the key must really disappear (for example, before sending data or listing keys). - 4
To ask "does this property exist?", there are two tools. The **
inoperator**,"key" in obj, istrueif the object has the key or inherits it from its prototype (a parent object it looks things up in; every plain object inherits methods such astoStringfromObject.prototype, explained in Phase 7.3). **Object.hasOwn(obj, "key")** (ES2022) istrueonly for the object's own properties. The older form isobj.hasOwnProperty("key");Object.hasOwnis preferred because it also works on objects that have no prototype or that overridehasOwnProperty. - 5
Optional chaining
?.(ES2020) reads a property only if the thing on its left is notnullorundefined; otherwise the whole expression stops and givesundefinedinstead of throwing. It has three forms:user?.address(property),user?.[key](brackets) anduser.greet?.()(call the method only if it exists). Pair it with??(Phase 2) to give a fallback:user.address?.city ?? "unknown". - 6
Keys are always converted to strings (unless they are symbols).
obj[1]andobj["1"]are the same property, andobj[true]uses the key"true". Using an object as a key is a classic bug: it becomes the string"[object Object]", so every object key collides. When keys must be objects, use aMap(Phase 14.3).
Explain it without notes
When must you use bracket notation instead of dot notation?
What's the difference between in, Object.hasOwn and comparing with undefined?
What does optional chaining do, and where is it the wrong tool?
Why does obj[{}] usually cause bugs?
Practice
Write getField(obj, field) that returns the value of any field name passed in, or the text "missing" if the object doesn't have that own property. Test it with { a: 1, b: undefined } and the fields a, b and c.
Given const order = { customer: { name: "Asha" } }, log the customer's phone number, or "no phone", in one expression without errors. Then do the same for const order2 = {}.
Start with const settings = { theme: "dark", debug: true }. Remove debug, add fontSize: 16, and log the result.
Trade-offs
- ↔
Dot notation is clearer and lets editors catch typos and autocomplete; bracket notation is flexible but hides which keys are used, so prefer dots unless the key really varies.
- ↔
Optional chaining makes code that handles missing data short and safe, at the cost of hiding mistakes; validating data once at the edge (when it arrives) often beats sprinkling
?.everywhere.
Done when you can
I can choose between dot and bracket notation and explain why.
I can add, change and delete properties.
I can check for a property with
inandObject.hasOwnand explain the difference.I can read nested values safely with
?.and give a fallback with??.I know that keys become strings and why object keys collide.