The `Object` class in JavaScript
This article explains the Object class in JavaScript.
This article explains the Object class in JavaScript, including practical examples.
YouTube Video
The Object class in JavaScript
Object is a built-in object that serves as the base for all JavaScript objects. Many of the language's core features, such as property management, inheritance (prototype chain), enumeration, cloning, and freezing, are provided through the behavior of Object.
Object.create
There are multiple ways to create objects, and you should use them appropriately depending on your purpose.
Object literal (most common)
The following code shows the simplest and most readable way to create an object.
1// Create an object using object literal
2const user = {
3 name: "Alice",
4 age: 30,
5 greet() {
6 return `Hello, I'm ${this.name}`;
7 }
8};
9
10console.log(user.greet()); // "Hello, I'm Alice"
- In this example, properties and methods are defined using literals. It is simple and generally offers superior performance.
new Object() constructor
The Object constructor is rarely used, but it's helpful to understand its behavior.
1// Create an object using the Object constructor
2const objFromCtor = new Object();
3objFromCtor.x = 10;
4objFromCtor.y = 20;
5
6console.log(objFromCtor); // { x: 10, y: 20 }
new Object()returns an empty object, but the literal{}is shorter and more common.
Specifying the prototype with Object.create
Object.create is used to create an object with a specified prototype.
1// Create an object with a specified prototype
2const proto = { hello() { return "hi"; } };
3const obj = Object.create(proto);
4obj.name = "Bob";
5
6console.log(obj.hello()); // "hi"
7console.log(Object.getPrototypeOf(obj) === proto); // true
Object.createis ideal for inheritance-based object design, allowing you to precisely control the prototype chain.
Property attributes and descriptors
Properties have attributes like 'value', 'writable', 'enumerable', and 'configurable', which can be controlled in detail with Object.defineProperty.
Basic example of using defineProperty
Next is an example of defining non-enumerable and read-only properties using defineProperty.
1// Define a non-enumerable read-only property
2const person = { name: "Carol" };
3
4Object.defineProperty(person, "id", {
5 value: 12345,
6 writable: false,
7 enumerable: false,
8 configurable: false
9});
10
11console.log(person.id); // 12345
12console.log(Object.keys(person)); // ["name"] — "id" is non-enumerable
13person.id = 999; // silently fails or throws in strict mode
14console.log(person.id); // still 12345
- Using
definePropertyallows you to precisely control property behavior such as enumeration, rewriting, and deletion.
Accessor properties (getter / setter)
With accessors, you can inject logic into property reads and writes.
1// Use getter and setter to manage internal state
2const data = {
3 _value: 1,
4 get value() {
5 return this._value;
6 },
7 set value(v) {
8 if (typeof v === "number" && v > 0) {
9 this._value = v;
10 } else {
11 throw new Error("value must be a positive number");
12 }
13 }
14};
15
16console.log(data.value); // 1
17data.value = 5;
18console.log(data.value); // 5
19// data.value = -1; // would throw
- With
getterandsetter, you can treat property access like an external API and add validation or side effects.
Prototype and inheritance (prototype / __proto__ / Object.getPrototypeOf)
Inheritance in JavaScript is based on the prototype chain, not on classes. Objects can refer to other objects as their prototypes.
Object.getPrototypeOf and Object.setPrototypeOf
The following example shows how to inspect and set prototypes.
1// Inspect and change prototype
2const base = { speak() { return "base"; } };
3const derived = Object.create(base);
4console.log(Object.getPrototypeOf(derived) === base); // true
5
6const other = { speak() { return "other"; } };
7Object.setPrototypeOf(derived, other);
8console.log(derived.speak()); // "other"
Object.getPrototypeOfretrieves the prototype of an object.Object.setPrototypeOfchanges the prototype of an existing object, but you should use it carefully since it can affect performance.
Important built-in methods
We will clearly explain the most commonly used and important methods selected from the instance methods provided by Object.prototype, as well as the static methods owned by Object.
hasOwnProperty, isPrototypeOf, toString, valueOf
hasOwnProperty, isPrototypeOf, toString, and valueOf define the basic behavior of objects.
1// Demonstrate prototype methods
2const base = { greet() { return "hello"; } };
3const child = Object.create(base);
4const a = { x: 1 };
5
6console.log(a.hasOwnProperty("x")); // true
7console.log(a.hasOwnProperty("toString")); // false — toString is inherited
8
9console.log(a.toString()); // "[object Object]" by default
10
11console.log(base.isPrototypeOf(child)); // true
12console.log(Object.prototype.isPrototypeOf(child)); // true
hasOwnPropertyis an essential method for checking if a property is directly on the object.isPrototypeOfchecks whether the target object has itself as a prototype.
Object.keys, Object.values, Object.entries
Object.keys, Object.values, and Object.entries return lists of an object's own enumerable properties. They are useful for iteration and transformation.
1// Keys, values and entries
2const item = { id: 1, name: "Widget", price: 9.99 };
3
4// ["id", "name", "price"]
5console.log(Object.keys(item));
6
7// [1, "Widget", 9.99]
8console.log(Object.values(item));
9
10// [["id",1], ["name","Widget"], ["price",9.99]]
11console.log(Object.entries(item));- These are frequently used for iterating and transforming objects.
Object.assign
Object.assign is used for shallow copying and merging. Note that prototypes and accessor properties are not copied.
1// Shallow copy / merge using Object.assign
2const target = { a: 1 };
3const source = { b: 2 };
4const result = Object.assign(target, source);
5
6console.log(result); // { a: 1, b: 2 }
7console.log(target === result); // true (merged into target)
- For nested objects, only the references are copied, so you need a different implementation for deep cloning.
Object.freeze, Object.seal, Object.preventExtensions
Object.freeze, Object.seal, and Object.preventExtensions control the mutability of objects.
1// Freeze vs seal vs preventExtensions
2const obj = { a: 1 };
3Object.freeze(obj);
4obj.a = 2; // fails silently or throws in strict mode
5delete obj.a; // fails
6
7const obj2 = { b: 2 };
8Object.seal(obj2);
9obj2.b = 3; // allowed
10// delete obj2.b; // fails
11
12const obj3 = { c: 3 };
13Object.preventExtensions(obj3);
14obj3.d = 4; // fails
freezeis the strictest; it prevents any changes to the object's properties.sealprevents adding or deleting properties, but allows changing the values of existing properties.preventExtensionsonly prevents the addition of new properties; existing properties can still be changed or deleted.
Object enumerability, order, and for...in / for...of
for...in enumerates enumerable property names, but it also includes properties on the prototype chain, so it's often used in combination with hasOwnProperty. Combining Object.keys() with for...of is safer and makes your intentions clearer.
1// Safe enumeration
2const obj = Object.create({ inherited: true });
3obj.own = 1;
4
5for (const key in obj) {
6 if (obj.hasOwnProperty(key)) {
7 console.log("own prop:", key);
8 } else {
9 console.log("inherited prop:", key);
10 }
11}
12
13for (const key of Object.keys(obj)) {
14 console.log("key via Object.keys:", key);
15}- The rules for property enumeration are defined in the ECMAScript specification, and there are cases where the enumeration order is guaranteed and others where it is not. As a general rule, keys interpreted as numbers are ordered in ascending order, while other keys follow the insertion order.
Cloning and deep copying
There are two types of object copying: shallow copying using Object.assign or the spread syntax, and deep copying using recursive methods. It is important to use them appropriately depending on the situation.
Shallow copying (spread syntax / Object.assign)
1// Shallow copy with spread operator
2const original = { a: 1, nested: { x: 10 } };
3const shallow = { ...original };
4shallow.nested.x = 99;
5console.log(original.nested.x); // 99 — nested object is shared
- With shallow copying, nested objects share references, so changes in the original object may affect the copy.
Simple deep copy (with caveats)
Using the JSON trick is a quick way to create a deep copy, but it has drawbacks such as losing functions, Date, circular references, and undefined values. For true deep cloning, you need to use a dedicated library.
1// Deep clone using JSON methods — limited use-cases only
2const source = { a: 1, d: new Date(), nested: { x: 2 } };
3const cloned = JSON.parse(JSON.stringify(source));
4console.log(cloned); // ok for plain data, but Date becomes string, functions lost
- JSON-based methods are convenient for quickly handling simple data, but in common cases, their behavior can break.
Mixins and object composition
Instead of multiple inheritance, the pattern of composing behaviors using mixins is often used.
1// Simple mixin function
2const canEat = {
3 eat() { return `${this.name} eats`; }
4};
5const canWalk = {
6 walk() { return `${this.name} walks`; }
7};
8
9function createPerson(name) {
10 const person = { name };
11 return Object.assign(person, canEat, canWalk);
12}
13
14const p = createPerson("Dana");
15console.log(p.eat()); // "Dana eats"
16console.log(p.walk()); // "Dana walks"
- Mixins are flexible, but you need to be careful about property name collisions and testability.
Common pitfalls and best practices
Here are some common pitfalls and best practices.
-
Mutability Objects are mutable by default. In applications with state management, consider immutable data structures using
Object.freezeor an immutable library. -
Prototype pollution Merging external data into an object directly with
Object.assignor loops can cause unexpected side effects with special properties such as__proto__orconstructor, creating security risks. Filter user input before directly merging it. -
Pitfalls of
for...infor...inenumerates prototype properties as well, so check withhasOwnProperty. UsingObject.keysis clearer. -
Misuse of shallow copy Consider whether you need deep copying to prevent changes in nested objects from affecting the original object.
Practical Example: Immutable Object Update Pattern
Patterns that return a new object without directly modifying the state are commonly used in React and similar libraries.
1// Immutable update example
2const state = { todos: [{ id: 1, text: "Buy milk", done: false }] };
3
4// Toggle todo done immutably
5function toggleTodo(state, todoId) {
6 return {
7 ...state,
8 todos: state.todos.map(t => t.id === todoId ? { ...t, done: !t.done } : t)
9 };
10}
11
12const newState = toggleTodo(state, 1);
13console.log(state.todos[0].done); // false
14console.log(newState.todos[0].done); // true
- This code is an example of creating a new state object without directly modifying the original
stateobject. ThetoggleTodofunction copies thetodosarray and returns a new object with only the target element modified, so the originalstateremains unchanged. - Immutable updates reduce side effects and make state management easier.
Practical Example: Safe Merging (Be Careful of Prototype Pollution)
When merging external JSON, ignore __proto__ to prevent prototype pollution.
1// Safe merge ignoring __proto__ keys
2function safeMerge(target, source) {
3 for (const key of Object.keys(source)) {
4 if (key === "__proto__" || key === "constructor") continue;
5 target[key] = source[key];
6 }
7 return target;
8}
9
10const target = {};
11const source = JSON.parse('{"a":1,"__proto__":{"polluted":true}}');
12safeMerge(target, source);
13console.log(target.polluted); // undefined — safe
14console.log({}.polluted); // undefined — prototype not polluted
- This kind of protection is also important in libraries and frameworks.
Performance considerations
With regard to performance, consider the following points:.
- Avoid frequent prototype changes (
Object.setPrototypeOf) or dynamic addition/removal of properties, as these hinder engine optimizations. - When generating many small objects, optimization becomes more effective if you use objects with a uniform structure (the same set of properties).
- Deep copying is costly. Minimize their use or consider using diff-based updates.
Summary
Object is central to JavaScript, providing a variety of features such as object creation, property control, inheritance, copying, and mutability management. It is important to understand APIs such as Object.defineProperty, Object.assign, and Object.freeze, and to design with care to avoid pitfalls like prototype pollution and shallow copying.
You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.