let person = {
// The "name" property is another object
name: {
first: "Joe",
last: "Bloggs"
},
age: 30
};
// Access the first name with dots
console.log(person.name.first); // Joe
// Access the first name with strings
console.log(person["name"]["first"]); // Joe
This allows us to build more complex data structures. Everything can be organised in to properties and sub-properties, instead of having to have lots of properties all on the same object.
null
We previously covered undefined
, a special value JavaScript provides when something is empty or missing. Somewhat confusingly, JavaScript provides another special empty value: null
.
Why are there two special empty values? This is commonly regarded as a design mistake in JavaScript. Most other programming languages only have a single special empty value, usually also named null
, or something similar like nil
. A good convention is to always use null
whenever you as a programmer intentionally want to have an empty or unspecified value, and only use undefined
where it's the JavaScript language that gives you that value. In other words you can think of undefined
as something that is entirely missing, like a non-existent property, and null
as something that is there, but holds an empty value, like a property that exists but is set to an empty value.
let myObject = {
// Prefer to use null for empty properties
myProperty: null
};
Like undefined
, null
is also falsy, meaning in an if statement, or when converting to a boolean, it counts as false
. The JavaScript language refers to both undefined
and null
as nullish values, as they are both kinds of empty value.
Trying to access a property of something that is nullish will cause an error.
let obj = null;
// TypeError: Cannot read properties of null (reading 'name')
console.log(obj.name);
Note this can also happen through nested references, as shown below.
let myObject = {
myProperty: null
};
// TypeError: Cannot read properties of null (reading 'anotherProperty')
console.log(myObject.myProperty.anotherProperty);
Since myObject.myProperty
is null
, the next attempt to access a property fails - in this case when trying to access anotherProperty
.
Optional chaining
Instead of dots, you can use the optional chaining operator ?.
- this allows you to read a property off something nullish, and it won't cause an error. It will just give you undefined
at the end. Of course if a property does exist, it still returns its value. For example the following code logs undefined
rather than causing an error.
let myObject = {
myProperty: null
};
console.log(myObject?.myProperty?.anotherProperty); // undefined
This works with entire chains of null values, hence the "chaining" in the name. The following code still logs undefined
rather than causing an error, even though there isn't an object at all and none of the properties exist.
let obj = null;
console.log(obj?.firstProperty?.secondProperty?.thirdProperty);
To demonstrate how this is a useful shortcut, what if we wanted to run the above code without using optional chaining and without causing an error? We'd have to use a big 'if' statement that checks every single property exists along the way, which is a lot more code to write.
let result;
let obj = null;
// Check the object and every single expected property exists
if (obj &&
obj.firstProperty &&
obj.firstProperty.secondProperty &&
obj.firstProperty.secondProperty.thirdProperty)
{
// All properties exist: read the result
result = obj.firstProperty.secondProperty.thirdProperty;
}
console.log(result);
The optional chaining syntax also works with string properties, although it uses a slightly odd looking ?.["property"]
syntax.
let myObject = {
myProperty: null
};
console.log(myObject?.["myProperty"]?.["anotherProperty"]);
Finally, this also extends to calling functions with func?.()
. This can be used to call a function on something nullish, and instead of causing an error trying to call the function it will just return undefined
.
let someFunction = null;
// Not an error even though someFunction is null,
// because optional ?.() syntax used
console.log(someFunction?.());
These are useful ways to attempt to access properties if you don't mind if any of them exist and can accept getting a nullish value instead. In general though you should know if your program expects properties to exist or not, and use the standard syntax if they should exist. So don't just go and use ?.
everywhere - it won't make your code any better. Just use it in specific cases when missing properties is not a problem and the convenience is helpful.