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); // trueConstructor 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); // Samsam.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 ---> nullChoose 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); // undefinedUse 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")); // truePrefer 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); // Tedconst 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); // 1myObj --------> { a: 1, b: 2 }
myObj2 --------> { a: 9, b: 2 }
objRef --------> same object as myObj2Prefer === 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); // falseThese 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); // ChicagoObject.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); // falseES6 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, Johnnameis shorthand forname: name.[field]computes a property name from the value offield.[id]defines a Symbol-keyed property.greet()defines a method without writing thefunctionkeyword.
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.
| Requirement | Ordinary object | Map |
|---|---|---|
| Typical use | Related fields such as name and age | A dynamic collection of key/value entries |
| Key types | Strings and Symbols | Any value, including objects |
| Membership checks | Object.hasOwn(obj, key) for own properties | map.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" })); // falseA 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
- MDN: Working with objects — object creation, properties, and methods.
- MDN: Object.create() — explicit prototypes and property descriptors.
- MDN: Object.hasOwn() — safe own-property checks.
- MDN: Enumerability and ownership — which properties different operations visit.
- MDN: Object.assign() — copying rules, mutation, and accessors.
- MDN: Spread syntax — shallow copies and property overwrites.
- MDN: The structured clone algorithm — supported types and copying limitations.
- MDN: Map — collection APIs and comparison with objects.
