9 Objects, prototypes, and classes
This chapter explores how JavaScript objects can be shaped far beyond simple key-value bags. It shows how properties can be controlled with getters and setters, how descriptors can make values read-only, hidden from iteration, or permanently fixed, and how private fields add a strong layer of encapsulation inside classes. It also introduces proxies as a way to intercept reads, writes, deletions, and other operations, giving developers a flexible mechanism for logging, validation, testing, and other advanced behaviors.
A major theme is that JavaScript inheritance is built on prototypes, where property lookup falls back through a chain of linked objects. The chapter explains how this lookup works, how constructor functions create objects whose prototype is set automatically, and why class syntax is mostly a cleaner shorthand over the same underlying model. It also highlights practical concerns such as distinguishing own properties from inherited ones, understanding enumerability, and recognizing the performance and maintenance costs of runtime prototype changes or proxies.
The final part connects these ideas to modern object-oriented programming with classes and inheritance. Classes are shown as a more readable way to define constructors, instance methods, static members, and child classes with extends and super. The chapter emphasizes that class-based syntax still relies on prototypes under the hood, while also offering conveniences like static methods and private elements. Overall, it builds a complete mental model for working with objects in JavaScript, from low-level property control to high-level class design.
The syntax for defining getters and setters. Prefix the property name with either the get or the set keyword.
Accessing the name property directly (on the left) and indirectly, through a proxy (on the right)
Initially, each object has access to only its own properties.
When we access a property that the object doesn’t have, the object’s prototype is searched for that property. Here, we can access hattori’s sneak property through yoshi, because hattori is yoshi’s prototype.
The search for a particular property stops when there are no more prototypes to explore. Accessing yoshi.creep triggers the search first in yoshi, then in hattori, and finally in kuma.
Every function, when created, gets a new prototype object. When we use a function as a constructor, the constructed object’s prototype is set to the function’s prototype.
Properties assigned to an object in the constructor are direct properties of that object. Properties from class prototypes are inherited as fallbacks, following the same rules we saw earlier for prototype inheritance.
Summary
- We can use accessor methods (getters and setters) to run functions when properties are accessed or assigned.
- Accessor properties can be defined by using the built-in Object.defineProperty method or with a special get and set syntax as parts of object literals or ES6 classes.
- We can also use Object.defineProperty to control whether properties are writable or enumerable.
- With proxies, we can define custom actions that will be executed when an object is interacted with in any way.
- Proxy handlers for specific actions are called “traps.”
- The Reflect.get and Reflect.set methods allow us to perform property access from within a proxy trap while preserving the this context.
- Every object can have a prototype, an object to which we delegate the search for a particular property, if the object itself doesn’t have the searched-for property. An object’s prototype can have its own prototype, and so on, forming a prototype chain.
- Prototypes are closely linked to constructor functions. Every function has a prototype property that’s set as the prototype of objects that it instantiates.
- A function’s prototype object has a constructor property pointing back to the function itself. This property is accessible to all objects instantiated with that function and, with certain limitations, can be used to find out whether an object was created by a particular function.
- With the class keyword, we can define a constructor function and a prototype at the same time.
- The extends keyword creates a prototype chain where the parent class’s prototype is the fallback from the child class’s.
- Within a child class, the parent can be referenced with super.
- Fields whose names start with # are called private elements. They can only be referenced by using the dot syntax from within the same class in which they’re defined.
FAQ
```htmlWhat is the difference between a normal object property and a getter/setter property in JavaScript?
Ordinary properties store and return values directly, while getter and setter properties run functions when the property is read or assigned. This lets you compute values on access, validate input, log changes, or block direct modification.How do you define a getter and setter inside an object literal?
Use theget and set keywords before the property name in an object literal. Reading the property calls the getter, and assigning to it calls the setter.Why would you use Object.defineProperty instead of object literal syntax?
Object.defineProperty lets you add or redefine a property after an object already exists. It is especially useful for dynamically creating getters, setters, non-enumerable properties, or read-only properties.Why doesn’t Object.keys show properties created with Object.defineProperty by default?
Properties created with Object.defineProperty are non-enumerable by default. Non-enumerable properties are skipped by Object.keys, Object.values, Object.entries, loops like for...in, JSON serialization, and common copy operations unless you explicitly set enumerable: true.What are accessor descriptors and data descriptors?
An accessor descriptor defines a property usingget and/or set functions. A data descriptor defines a property with a direct value, and can also control whether it is writable, enumerable, or configurable.What is a proxy in JavaScript?
A proxy is an object that wraps another object and intercepts operations performed on it. Through traps likeget, set, deleteProperty, and ownKeys, a proxy can customize how property access, assignment, deletion, and enumeration behave.Why is the Reflect API used with proxies?
Reflect provides standard versions of object operations such as getting and setting properties. It is commonly used inside proxy traps to forward behavior to the original target object in a safe, consistent way.How does prototype inheritance work in JavaScript?
If a property is not found directly on an object, JavaScript looks for it on the object’s prototype. If needed, it continues up the prototype chain until the property is found or the chain ends.How do constructor functions and the new keyword use prototypes?
When a function is used with new, the created object inherits from that function’s prototype object. This is why methods placed on Constructor.prototype are available to all instances created from that constructor.How do JavaScript classes relate to prototypes and inheritance?
Classes are syntax built on top of prototypes. Instance methods are stored on the class prototype, theconstructor initializes each instance, and extends plus super provide inheritance across classes.
Secrets of the JavaScript Ninja, Third Edition ebook for free