Tutorials

JavaScript ES6 Objects

Key Takeaways

  • JavaScript objects group related data and methods under string or Symbol property keys.
  • Objects can have their own properties and inherit others through a prototype. Object.hasOwn() checks only own properties; the in operator also checks inherited ones.
  • Assigning an object to another variable shares the same object. Separately created objects remain unequal even when their properties match.
  • Object.assign({}, source) and object spread create shallow copies: nested objects remain shared.

JavaScript objects group related data and behavior as properties: key/value pairs whose values can include functions. They're similar to dictionaries or hash maps, but ordinary objects also have a prototype for inherited properties and property descriptors—settings that control property behavior.

Here's an object containing two data properties and a method, which is a function-valued property. Run each JavaScript block in this guide separately; the later examples require an environment that provides Object.hasOwn() and structuredClone().

const person = {
  name: "Sam",
  age: 33,
  printName() {
    console.log(`Hello, my name is ${this.name}.`);
  },
};

console.log(person.name); // Sam
console.log(person.age); // 33
person.printName(); // Hello, my name is Sam.

In the call person.printName(), this refers to person. That receiver—the object before the dot—is important when a method uses the object's other properties.

Creating JavaScript Objects

Use object literals for records

An object literal defines an object with curly braces. Prefer it for records: related fields such as a person's name and age. Calling the Object constructor without arguments also creates an empty object, but the literal is shorter.

const myObject = {};
const anotherObject = new Object();

Use constructor functions for repeated initialization

A constructor function lets you initialize multiple objects using the same procedure. In this example, new creates an object linked to Person.prototype, calls Person with that object as this, and returns the initialized object.

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

const jeff = new Person("Jeff", 25);

console.log(jeff.name); // Jeff
console.log(jeff.age); // 25
console.log(Object.getPrototypeOf(jeff) === Person.prototype); // true

Constructor functions remain valid JavaScript. For another way to organize constructors and shared methods, see our introduction to JavaScript classes.

Use Object.create() to choose a prototype

Object.create(proto) creates an object whose prototype is proto; it does not copy that object's properties. An own property belongs directly to an object, while an inherited property is found through its prototype chain.

const person = { name: "Sam", age: 50 };
const sam = Object.create(person);
const fred = Object.create(person);

console.log(sam.name); // Sam
console.log(Object.hasOwn(sam, "name")); // false

fred.name = "Fred";
console.log(fred.name); // Fred
console.log(Object.hasOwn(fred, "name")); // true
console.log(person.name); // Sam

sam.name is inherited from person. Assigning fred.name creates an own property that shadows, or hides, the inherited name without changing person.name. For these objects, property lookup follows this chain:

sam  (no own properties) ---> person { name: "Sam", age: 50 }
                                      |
fred { name: "Fred" } -------> same person
                                      |
                                      v
                               Object.prototype ---> null

Choose Object.create() when you specifically want prototype delegation. It isn't a cloning shortcut.

Accessing JavaScript Object Properties

Dot notation and bracket notation both access properties. Dot notation uses a fixed identifier; bracket notation evaluates an expression to get the key.

const myObj = {
  prop1: "hello",
  prop2: "goodbye",
  "display-name": "Sam",
};
const key = "prop1";

console.log(myObj.prop1); // hello
console.log(myObj["prop1"]); // hello
console.log(myObj[key]); // hello
console.log(myObj["display-name"]); // Sam
console.log(myObj.key); // undefined

Use brackets when a key is stored in a variable, contains spaces or hyphens, or is a Symbol. Object property keys are strings or Symbols. The ToPropertyKey conversion first converts an input to a primitive; if that primitive is a Symbol, it remains a Symbol key, otherwise it becomes a string. For example, Object(Symbol()) can produce a Symbol key, and conversion can throw. For example, a numeric key 1 becomes "1".

Checking Own Versus Inherited Properties

Object.hasOwn(obj, key) checks whether a property belongs directly to an object, even when its value is undefined. By contrast, key in obj searches both the object and its prototype chain.

const record = Object.create({ role: "guest" });
record.name = "Sam";
record.nickname = undefined;

console.log(Object.hasOwn(record, "name")); // true
console.log(Object.hasOwn(record, "role")); // false
console.log("role" in record); // true
console.log(Object.hasOwn(record, "nickname")); // true

Prefer Object.hasOwn() to calling obj.hasOwnProperty(): it works even when the object has no prototype or has a property that overrides that method. A truthiness check such as if (obj[key]) isn't a presence check; existing values such as 0, false, and undefined won't pass it.

Changing Object Properties

For the ordinary writable properties shown here, assignment updates the property's value. Either access notation works.

const person = { name: "Steve", age: 35 };

person.name = "Sam";
console.log(person.name); // Sam

person["name"] = "Ted";
console.log(person.name); // Ted

const prevents reassigning the variable; it doesn't make the object's properties immutable. Our guide to JavaScript let, const, and var explains that distinction in more detail.

Adding Object Properties

For an extensible ordinary object, assigning a key that has no own or inherited property adds an own property. If the key is inherited, an inherited setter can intercept the assignment, while an inherited non-writable data property or getter-only accessor can prevent it. Properties added through ordinary assignment are writable, enumerable, and configurable by default: you can change them, include them in enumeration-based operations, and delete them.

const person = { name: "Steve", age: 35 };
person.lastName = "Smith";

console.log(person.lastName); // Smith
// person now has name, age, and lastName as own properties.

Deleting Object Properties

The delete operator removes a configurable own property. It doesn't remove an inherited property, and deleting an own property can expose an inherited property with the same name.

const person = { name: "Steve", age: 35 };
delete person.name;
console.log("name" in person); // false

const inherited = Object.create({ name: "Sam" });
inherited.name = "Fred";
delete inherited.name;
console.log(inherited.name); // Sam

console.log(delete inherited.name); // true
console.log(inherited.name); // Sam (still inherited)

Comparing Objects and Sharing References

When both operands are objects, == and === compare identity, not property contents. Assigning an object to another variable copies the reference, so both variables access the same object.

const myObj = { a: 1, b: 2 };
const myObj2 = { a: 1, b: 2 };
const objRef = myObj2;

console.log(myObj == myObj2); // false
console.log(myObj === myObj2); // false
console.log(objRef == myObj2); // true
console.log(objRef === myObj2); // true

objRef.a = 9;
console.log(myObj2.a); // 9
console.log(myObj.a); // 1
myObj  --------> { a: 1, b: 2 }
myObj2 --------> { a: 9, b: 2 }
objRef --------> same object as myObj2

Prefer === for normal comparisons. Comparing contents requires an explicit comparison strategy; JavaScript doesn't provide a general deep-equality operator.

Copying JavaScript Objects

Object.assign() and object spread make shallow copies

For a flat record, Object.assign({}, source) and { ...source } create a new object with the source's enumerable own properties. Both include string and Symbol keys, but exclude inherited and non-enumerable properties. Enumerable means a property's enumeration flag is enabled; properties created through ordinary assignment or object literals are enumerable by default.

const myObj = { a: 1, b: 2 };
const copy1 = Object.assign({}, myObj);
const copy2 = { ...myObj };

console.log(copy1); // { a: 1, b: 2 }
console.log(copy2); // { a: 1, b: 2 }
console.log(copy1 === myObj); // false
console.log(copy2 === myObj); // false

These are shallow copies: the outer object is new, but nested object values remain shared references.

const original = { address: { city: "Boston" } };
const assigned = Object.assign({}, original);
const spread = { ...original };

console.log(assigned.address === original.address); // true
console.log(spread.address === original.address); // true

spread.address.city = "Chicago";
console.log(original.address.city); // Chicago
console.log(assigned.address.city); // Chicago

Object.assign(target, source) modifies and returns its target. With multiple sources or spread properties, later values overwrite earlier ones for matching keys. Both approaches can read source getters; Object.assign() can also invoke target setters, whereas object spread defines properties without invoking target setters.

Use structuredClone() for supported nested data

structuredClone(value) creates a deep copy of supported values, including nested plain objects, arrays, Date, Map, and Set. It also supports circular references.

const original = { address: { city: "Boston" } };
const copy = structuredClone(original);

copy.address.city = "Chicago";
console.log(original.address.city); // Boston
console.log(copy.address === original.address); // false

ES6 Object-Literal Shortcuts

ES6 is the sixth edition of ECMAScript, also called ES2015. It introduced shorthand property names, concise method syntax, computed property names, and Object.assign(); object spread and Object.hasOwn() are later additions. These conveniences don't change the underlying reference and prototype rules.

const name = "John";
const field = "role";
const id = Symbol("id");

const person = {
  name,
  [field]: "developer",
  [id]: 42,
  greet() {
    return `Hello, ${this.name}`;
  },
};

console.log(person.name); // John
console.log(person.role); // developer
console.log(person[id]); // 42
console.log(person.greet()); // Hello, John
  • name is shorthand for name: name.
  • [field] computes a property name from the value of field.
  • [id] defines a Symbol-keyed property.
  • greet() defines a method without writing the function keyword.

The object-initializer reference covers these forms in detail. For more on defining and calling behavior, continue with JavaScript functions.

When to Use an Object Instead of Map

Use an ordinary object for a record with named fields. Prefer Map for a collection that needs arbitrary key types or frequent additions and removals; don't assume either structure is universally faster for every workload.

RequirementOrdinary objectMap
Typical useRelated fields such as name and ageA dynamic collection of key/value entries
Key typesStrings and SymbolsAny value, including objects
Membership checksObject.hasOwn(obj, key) for own propertiesmap.has(key) for stored entries
const person = { name: "Sam" };
const visits = new Map();
visits.set(person, 3);

console.log(visits.get(person)); // 3
console.log(visits.has(person)); // true
console.log(visits.has({ name: "Sam" })); // false

A plain {} inherits properties such as toString; it isn't a dictionary with no inherited keys. If you need a string/Symbol-keyed dictionary without a prototype, Object.create(null) provides one, and Object.hasOwn() still works with it.

Common Mistakes

  • Using Object.create() as a copy. It links objects through inheritance instead of duplicating properties.
  • Losing a method's receiver. Retrieving a method doesn't permanently bind it to its object. When you need to preserve a call such as person.printName() in a callback, use a wrapper such as () => person.printName().
  • Writing arbitrary external keys into application objects. Validate allowed keys and value types before dynamic assignments. Object.hasOwn() checks ownership, not whether input is safe; unsafe object writes can enable prototype pollution, where unexpected prototype changes affect property lookup. See MDN's prototype-pollution defenses.

FAQ

How do you create an object in JavaScript?

Use an object literal such as const person = { name: "Sam" } for a record. Constructor functions initialize repeated instances, while Object.create(proto) creates an object that inherits from a specified prototype.

What is the difference between dot notation and bracket notation?

Dot notation accesses a fixed property name written as an identifier. Bracket notation evaluates an expression, so use it for variable keys, Symbol keys, or names containing spaces or hyphens.

How do you check whether a JavaScript object has its own property?

Use Object.hasOwn(obj, key). It checks direct ownership even when the property's value is undefined; the in operator also searches inherited properties.

Does object spread create a deep copy?

No. Object spread and Object.assign({}, source) create shallow copies, leaving nested objects shared. Use structuredClone() for supported nested data, or an application-specific copy strategy when functions, prototypes, or property descriptors matter.

Why are two objects with the same properties not equal in JavaScript?

Object equality compares identity rather than property contents. Two separately created objects are distinct, while two variables referencing the same object compare equal with ===.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. Author of Mastering the Tech Interview. More about me