Object properties

What are properties?

Object properties are basically JavaScript variables, except they are attached to objects. The property name is called a key (which involves more than just an identifier, as explained below). The assigned value is either a primitive or an object. Unassigned properties are initialized with the value undefined. When using the same key for multiple properties, the last property will overwrite the rest.

The most common ways to assign a property to an object is within an object literal (via key: value,) or by using the dot-notation (object.identifier = value;). Both will be explained in detail in this and next chapters.

A property with a function value is called a "method", which, in JavaScript, is merely a name for this kind of properties. In JavaScript, a method is a property that acts just as any other property.

Property identifiers

Valid identifiers are only allowed to contain letters, $, _, and digits (0-9), but may not start with a digit. However, property names (keys) do not have to be valid identifiers: keys may include spaces, hyphens, they can start with a number or even consist of only digits. In JavaScript all keys are converted to strings (unless they are symbols). A key can be any string (case-sensitive), including an empty string.

In an object literal we can use any valid key. In a dot-notation only valid identifiers are allowed.


// object literal:
const myObject = {
  myProp: 1,
  myProp: "Hello y'all",
  propObj: {myProp: true},
  a: 1,
  "a": 2,
  313: "Some value",
  "my prop": null
}

console.log(myObject.myProp); // logs: "Hello y'all"
console.log(myObject.propObj.myProp); // logs: true
console.log(myObject.unassignedProp); // logs: undefined
console.log(myObject.a); // logs: 2

// console.log(myObject.9InvalidIdentifier); // SyntaxError: identifier starts immediately after numeric literal

In the object literal in the example above keys a and "a" refer to the same property. You may enclose keys in quotes, but common practice is to omit them, unless the key is not a valid identifier: then quotation marks are required. An exception is a key being a number. Although not valid identifiers, number keys do not require quotation marks, in fact, common practice is to omit quotation marks, just like with valid identifiers.

Creating properties

Properties can be created and assigned to an object directly at creation of the object (explained in the next chapter), by using Object.defineProperty() (explained below), or by using the dot-notation or square bracket notation, as shown in the next example.


const bird = {};

const name = "Tweety";
function fly() {
  return "Flap flap";
};

bird.plumageColor = "yellow"; // dot-notation
bird["species"] = "canary"; // square bracket notation
bird.name = name;
bird.fly = fly;

console.log(bird.plumageColor); // logs: "yellow"
console.log(bird.species); // logs: "canary"
console.log(bird.name); // logs: "Tweety"
console.log(bird.fly()); // logs: "Flap flap"

Accessing properties

The dot-notation (objectName.propertyName) or square bracket notation (objectName[propertyName]) can be used to create properties, but they can also be used to access properties, i.e., to read them or to assign (new) values to them.

The dot-notation requires that the property name is a valid identifier (and not enclosed in quotation marks). To access properties with non-valid identifiers, you need to use the square bracket notation instead of the dot-notation. The key in the square brackets needs to be in quotation marks (a string literal), except when the key is a number. If the key is a valid identifier and not in quotation marks, it is interpreted as a variable reference: thus the bracket notation can also be used to assign variable values to property names. The square bracket notation can also be used to access computed keys.


const myObject = {};

myObject["my property"] = "Hello World!";
myObject.myProperty = "My property name is a valid variable identifier";
// myObject[myProperty] = "Hello World!"; // ReferenceError: myProperty is not defined
myObject[""] = "I'm a property without a name";
myObject[3] = 3;
console.log(myObject[3] === myObject["3"]); // logs: true
console.log(myObject[4 - 1]); // logs: 3

let myVariable = {};
myObject[myVariable] = true;
myVariable = null;
myObject[myVariable] = false;

console.log(myObject[myVariable] === myObject["null"]); // logs: true
console.log(myObject.myVariable); // logs: undefined

for (key in myObject) {
    console.log(`${key} (${typeof key}) : ${myObject[key]} (${typeof myObject[key]})`);
}
/* logs (in arbitrary order):"
// 3 (string) : 3 (number)
// my property (string) : Hello World! (string)
// myProperty (string) : My property name is a valid variable identifier (string)
// (string) : I'm a property without a name (string)
// [object Object] (string) : true (boolean)
// null (string) : false (boolean)
"*/

If the key is an array, it will convert to a string by concatenating the array elements, separated by commas, to one string (like method array.join(',') would do).


const myObject = {};
let myVariable = ["A", true, 2];
myObject[myVariable] = 1;

console.log(myObject); // logs: { "A,true,2": 1 }
console.log(myObject["A,true,2"]); // logs: 1

myVariable = [];
myObject[myVariable] = 2;
console.log(myObject[""]); // logs: 2

// .join(',') also works but is redundant:
myVariable = ["A", true, 2].join(',');
myObject[myVariable] = 3;
console.log(myObject["A,true,2"]); // logs: 3

Using the dot-notation is often referred to as chaining. An expression like a.b.c.b is a "chain" of objects being each others properties. Chaining is very common in JavaScript (and many other languages).


const a = {
  b: function() {
    return { c: {d: 313}}
  }
}
console.log(a.b().c.d); // logs: 313

Optional chaining (?.)

Declaring a property without explicit initialization assigns the default value undefined (as with variables). Trying to access a property of a property that has a nullish value (null or undefined), will throw an error. Calling a method (or any function) that does not exist throws an error.


const myObj = {}

console.log(myObj.prop); //logs: undefined // (declaration without explicit initialization)
console.log(myObj.prop.a); // TypeError: myObj.prop is undefined
console.log(myObj.method()); // TypeError: myObj.method is not a function

We can use optional chaining (?.) to prevent the errors. If the value of the property before ?. is nullish, the expression short circuits and returns undefined instead of throwing an error. When used with function calls, and the function does not exist, it returns undefined instead of throwing an error.


const myObj = {}

console.log(myObj.prop); //logs: undefined
console.log(myObj.prop?.a); //logs: undefined
console.log(myObj.method?.()); //logs: undefined

Using ?. in an optional function call is not really "chaining". So, to be more precise, the ?. operator is used for optional function calls and for optional chaining.

If, in optional chaining, the directly preceding operand is nullish, the evaluation of the chaining expression will be terminated. Subsequent property accesses or method calls in the chaining will be ignored.


const myObj = null;
let x = 0;
const prop1 = myObj?.doSomething(x++);
console.log(x); // logs: 0 // x was not incremented

const prop2 = myObj?.a.b;
// This does not throw an error, because evaluation has already stopped at
// the first optional chain

Optional chaining cannot be used on non-declared objects and you cannot assign a value to an optional chaining expression.


const myNullish = null;
console.log(myNullish?.prop); //logs: undefined

console.log(myObj?.prop); // ReferenceError: myObj is not defined

const someObj = { prop: {} }
someObj.prop?.a = 2; // SyntaxError: invalid assignment left-hand side

The optional chaining operator can also be used with bracket notation.


const myObj = {}
console.log(myObj.prop?.["my prop"]); //logs: undefined
console.log(myObj[313]?.["my prop"]); //logs: undefined

let myArray;
console.log(myArray?.[1]); // logs: undefined
console.log(myArray[1]); // TypeError: myArray is undefined

Suppose we want to get an HTML element's text content and store it in a constant.


const text = document.querySelector('#my_elem').textContent;
// TypeError: document.querySelector(...) is null

If that HTML element does not exist, querySelector returns null. Accessing the textContent property subsequently throws an error. To avoid the error, we need to confirm document.querySelector(...) to be non-nullish before accessing the textContent property.


const elm = document.querySelector('#my_elem');
const text =
  (elm === null || elm === undefined) ? undefined : elm.textContent;
console.log(text); // logs: undefined

Or with optional chaining:


const text = document.querySelector('#my_elem')?.textContent;
console.log(text); // logs: undefined

In the next example the nullish coalescing operator (??) is used after optional chaining in order to provide a default value for when the optional chaining returns undefined.


function getUserName(user) {
  const userName = user?.name ?? "No name available";
  return userName;
}

let undef;
console.log(getUserName(undef));  // logs: "No name available"
console.log(getUserName(5)); // logs: "No name available"
console.log(getUserName()); // logs: "No name available"

console.log(getUserName({
  name: "Jake",
  country: "UK",
})); // logs: "Jake"

// The function also provides the default value "No name available"
// if "user" is an object without the "name" property, although this is not because of the
// optional chaining. This is because declaring a property without explicit initialization
// assigns the default value "undefined".
console.log(getUserName({
  country: "The Netherlands",
})); // logs: "No name available"

Property attributes and Object.defineProperty()

Properties have internal attributes or flags, often denoted in two pairs of square brackets. Property attributes are for instance [[enumerable]], [[configurable]] or [[writable]] , all three set to true by default, or the property's [[value]], set to undefined by default (as we have seen).

With method Object.defineProperty() you can define a new property on an object or modify an existing property on an object and, at the same instant, set or change the attributes of that property.


const myObject = {};

Object.defineProperty(myObject, 'someProperty', {
  value: 313,
  writable: true,
  enumerable: true,
  configurable: true
});

console.log(myObject.someProperty); // logs: 313

BTW. Also method Object.defineProperties() is available to define multiple properties with their attributes.

Once you defined a property as non-configurable, you cannot use Object.defineProperty() again to change the property's attributes (you'll get an error). You cannot even change the property to configurable.


const myObject = {};

Object.defineProperty(myObject, 'someProperty', {
  value: 313,
  configurable: false
});

Object.defineProperty(myObject, 'someProperty', {
   value: 314
}); // logs: TypeError: can't redefine non-configurable property "someProperty"

Object.defineProperty(myObject, 'someProperty', {
  configurable: true
}); // logs: TypeError: can't redefine non-configurable property "someProperty"

The attributes of a property are listed as properties in an object: {value: 313, writable: true, enumerable: true, configurable: true}. This object is called the property's descriptor. Each property is "under the hood" associated with its own descriptor. You can use method Object.defineProperty() to set the property's descriptor (as shown above), and you can use method Object.getOwnPropertyDescriptor() to return the property's descriptor.


const myObject = {};

Object.defineProperty(myObject, 'someProperty', {
  value: 313
});

console.log(myObject.someProperty = 314); // logs: 314

const descriptor = Object.getOwnPropertyDescriptor(myObject, 'someProperty');
console.log(descriptor); // logs: { value: 313, writable: false, enumerable: false, configurable: false }

Note that changing the property's value through regular dot notation or bracket notation does not change its attribute [[value]]. Conversely, changing the [[value]] attribute does change the property's value. Also note that, contrary to defining a property via simple assignment or via an object literal, using Object.defineProperty() to define a property will set [[enumerable]], [[configurarable]] and [[writable]] to default value false.

Enumerability of properties

The [[enumerable]] attribute determines whether or not a property is visited when the object's properties are traversed or enumerated, such as in for...in loops or the Object.keys method.

Traversing object properties may also use the object's iterator, such as in for...of loops. Objects with an iterator are called iterables. Iterating over iterables is different from enumerating object properties. Loops and methods that enumerate properties can generally not be used to iterate over properties using the iterator, and vice versa. Unlike iterating over iterables, enumerating properties happens in an arbitrary order that is browser/platform dependent. Iterating iterables will be explained later this tutorial.


// create an object with { b: 2 } as its prototype:
const myObject = Object.create({ b: 2 });
myObject.a = 1;

Object.defineProperty(myObject, 'c', {
    enumerable: false,
    value: 3
});

console.log(myObject.c); // logs: 3

// next for...in loop traverses all of the enumerable properties,
// including those in the prototype chain.
// Property c is skipped because it is not enumerable.
for (let propertyName in myObject) {
   console.log(propertyName + ": " + myObject[propertyName]);
}
// logs:
// "a: 1"
// "b: 2"

// Object.keys returns an array with the names ("keys")
// of only the enumerable own properties,
// so not of those in the prototype chain.
console.log(Object.keys(myObject)); // logs: [ "a" ]

Note that all properties, enumerable or not, string or symbol, own or inherited, can be accessed with dot notation or bracket notation.

Next to for...in and Object.keys, JavaScript provides a number of built-in methods to query or traverse object properties. Whether a querying method returns true or false or whether a property may be visited when traversing the properties, depends on the property being enumerable or not, on having a string key or symbol key, and on the property being the object's own or inherited. MDN web docs article "Enumerability and ownership of properties" provides an overview of all methods and how they treat the different types of properties.

To see if a specified property is in a specified object, you can use the in operator, or, if you want to check for only non-inherited properties, you can use the Object.hasOwn() method.


const name = {
  firstName: "John",
  lastName: "Doe",
  fullName: function() {
    return this.firstName+" "+this.lastName;
  }  
}

console.log('firstName' in name); // logs: true
console.log('toString' in name); // logs: true // toString is an inherited method

console.log(Object.hasOwn(name, 'firstName')); // logs: true
console.log(Object.hasOwn(name, 'toString')); // logs: false

Accessor functions

Accessor functions (not to be confused with "property accessors" being the dot notation or the bracket notation) are also called "accessor methods" or simply "accessors". There are two types of accessor functions; getters and setters, the latter also called mutator methods.

We call a property with a setter and/or getter a accessor property. Accessor properties are not methods, although they look like methods. Accessor properties do not have a function value, in fact, they do not have a value at all, as will be explained below. A property may have a setter, but not a getter, or vice versa, but most often a setter is used in conjunction with a getter on the same property, as will be explained below.

In previous examples, objects were presented that had a property storing the first name and a property storing the last name of a person and a fullName method that returned the combination of both first and last name. Instead of a method fullName we could also use a getter, which is kind of similar to using a method, but with a few distinctive differences. Attempting to directly assign a value to a getter property will not change the property: the property will always and only return the return value of the getter function. If fullName in the example below would have been a method, it could have been overwritten, which would have destroyed the construct that this property's value (only) depends on two other properties. You can say that property fullName (that only has a getter but no setter) is now read-only (which could also have been achieved by changing a method's attributes [[writable]] or [[configurable]]).


const person = {
  firstName: "John",
  lastName: "Doe",
  // define a getter by prefixing the getter function with keyword "get":  
  get fullName() {  
    return `${this.firstName} ${this.lastName}`;
  }
}
console.log(person.fullName); // logs: "John Doe"

person.fullName = "Jane Roe"; // In strict mode this statement throws: TypeError: setting getter-only property "fullName"
console.log(person.fullName); // logs: "John Doe"

person.fullName(); // throws a TypeError: person.fullName is not a function

PS: Note that setting a property with only a getter throws a TypeError in strict mode. In non-strict mode, the assignment (person.fullName = "Jane Roe";) is silently ignored.

Note that person.fullName is not a method call; in fact a method call person.fullName() throws an error. Also note that the getter function name refers to the name of the property (the key), not to the name of the getter itself, which is rather confusing. Furthermore, a getter function cannot have any parameters.

A setter function must have precisely one parameter. Assigning a value to a setter property always invokes the setter function. In most cases a setter function is used in conjunction with a getter function for the same property, to get the property's value. This creates a "pseudo-property", as if the property is a regular property, but with the possibility of returning a computed, composed or conditional value. Something like the example below could have been achieved with separate getName and setName methods instead, but not as compact and intuitive as with getter and setter.


const person = {
  get name() {
    return this._name;
  },
  set name(value) {
    value = value.trim(); // removing possible whitespace from both ends of the string
	if (value === '') { return }  
    this._name = value;
  }  
}

console.log(person.name); // logs: undefined
person.name = "John Doe"; // Assigning a value to a setter property invokes the setter function.
console.log(person.name); // logs: "John Doe"

person.name = " ";
console.log(person.name); // logs: "John Doe"

person.name("John Doe"); // logs: TypeError: person.name is not a function

PS: Note that a class would be more appropriate for person than an object. In fact, getter/setter constructions are most suitable in classes. Later this tutorial more about classes.

Again, person.name is not a method call, in fact, person.name("John Doe") throws an error.

Note that the setter implicitly introduces a "hidden" third property: _name. This property is said to be private or protected (as opposed to public properties): the property is not supposed to be accessed from outside the object, although JavaScript is not enforcing inner access in the object only. In classes, JavaScript enforces inner access though. A widespread convention is that such properties in objects are prefixed with an underscore _. What will console.log(person._name); log to the console?

As with a getter, it is not possible to have a setter on a property that, at the same time, holds an actual value. Accessing a property with a setter bound, and not a getter, always returns undefined.


const myObj = {
  set prop(value) { }  
}

myObj.prop = "Hello"; 

console.log(myObj.prop); // logs: undefined

Be aware that something like the next example creates infinite recursion: this.prop = value invokes the setter again and again and...


const myObj = {
  set prop(value) {
    this.prop = value;
  }  
}

myObj.prop = "Hello"; // InternalError: too much recursion 

Technically you can use a setter to set an other property than the one the setter is bound to, but it is probably more appropriate to use a method for cases like this.


const myObj = {
  prop: 2,
  set setProp(value) {
    this.prop = this.prop * value;
  }  
}

myObj.setProp = 2;
console.log(myObj.prop); // logs: 4

Data descriptor & accessor descriptor

So, accessor properties do not have a value, hence, they cannot have a [[value]] attribute. Any property's property descriptor must be of one of the next two flavors:

A property with a data descriptor is a data property and a property with an accessor descriptor is an accessor property.

You can use method Object.defineProperty() to set the property's descriptor, and you can use method Object.getOwnPropertyDescriptor() to return the property's descriptor. This provides an other way to define a setter and getter on a property than defining them within an object literal, as used so far.


function Person(age) {
  this.age = age;
}

const person1 = new Person(45);

// "person1" is an object created with constructor function "Person"
// Object "person1" received data property "age" via this constructor function
console.log(person1.age); // logs: 45

Object.defineProperty(person1, 'name', {
  get() { return this._name; },
  set(value) {
    value = value.trim();
	if (value === '') { return }  
    this._name = value;
  },
  enumerable: true,
  configurable: true
});

console.log(person1.name); // logs: undefined
person1.name = "John Doe";
console.log(person1.name); // logs: "John Doe"

person1.name = " ";
console.log(person1.name); // logs: "John Doe"

console.log(Object.getOwnPropertyDescriptor(person1, 'age')); // logs: { value: 45, writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptor(person1, 'name')); // logs: { get: get(), set: set(value), enumerable: true, configurable: true }

PS: Constructor functions will be explained in the next chapter.

Deleting properties

With the delete operator you can delete an own property of an object, including getter and getter properties. To delete a property, you can use a statement in which the delete operator is followed by the property in dot-notation or in square bracket notation.


const User = {
  name: "full name",
  avatar: "nickname"
};

const user1 = Object.create(User);
user1.name = 'Marge Bouvier';
user1.nComments = 2;

console.log(user1.nComments); // logs: 2

delete user1.nComments;
console.log(user1.nComments); // logs: undefined

// after deletion of an own property the object will use the property from the prototype chain, if it exists: 
delete user1["name"];
console.log(user1["name"]); // logs: 'full name'

// 'delete' only deletes own properties:
delete user1.avatar;
console.log(user1.avatar); // logs: 'nickname'

After deletion, both the value and the property are removed. Non-configurable properties cannot be deleted (in strict mode it throws an error).


const myObject = {a: 1, b: 2};

delete myObject.a;
myObject.b = undefined;

console.log(myObject.a); // logs: undefined
console.log(myObject.b); // logs: undefined

console.log('a' in myObject); // logs: false
console.log('b' in myObject); // logs: true

Object.defineProperty(myObject, 'c', {
  value: 313,
  configurable: false
});

// in strict mode next will throw a TypeError.
delete myObject.c;
console.log(myObject.c); // logs: 313

Deleting from the global object

Generally, variables and constants are not properties of an object, so they cannot be deleted (in strict mode it throws an error). However, var variable declarations in global scope do create properties of the global object, but they also cannot be deleted: they are set non-configurable at creation. The same thing holds for function declarations in global scope.


let a = 2;
console.log('a' in globalThis); // logs: false
console.log(Object.getOwnPropertyDescriptor(globalThis, 'a')); // logs: undefined

delete a; // this has no effect; in strict mode this will throw a TypeError.
console.log(a); // logs: 2

var a = 2;
console.log('a' in globalThis); // logs: true
console.log(Object.getOwnPropertyDescriptor(globalThis, 'a')); // logs: { value: 2, writable: true, enumerable: true, configurable: false }
delete globalThis.a; // this has no effect; in strict mode this will throw a TypeError.
console.log(globalThis.a); // logs: 2

function f() {};
console.log('f' in globalThis); // logs: true
console.log(Object.getOwnPropertyDescriptor(globalThis, 'f')); // logs: { value: f(), writable: true, enumerable: true, configurable: false }
delete f; // this has no effect; in strict mode this will throw a TypeError.
console.log('f' in globalThis); // logs: true

PS: In the above examples globalThis is used to reference the global object. The globalThis reference is the modern and now standard way to access the global object across environments, whereas in the past, accessing the global object required different references in different JavaScript environments (window in a browser, global in Node.js etc.).

In non-strict mode, creating an implicit global variable (assigning a value to an undeclared variable) also creates a property of the global object. But now the property is configurable (it is not a var variable), so now you can delete the property from the global object. Of course it is also possible to directly assign a property to the global object.


// In strict mode, "a = 5" in the function declaration throws an error.

(function f() { a = 5; })();

console.log('a' in globalThis); // logs: true
delete a;
console.log('a' in globalThis); // logs: false

globalThis.b = 2;
console.log('b' in globalThis); // logs: true
delete globalThis.b;
console.log('b' in globalThis); // logs: false