Prototypes & Classes

Every property access in JavaScript is a walk up a chain of linked objects — `class` syntax just wires those links politely. Ten thousand meshes can afford one shared `raycast` because a method lives once, on the prototype, not once per object.

Core Idea

The chain above is not a diagram — it is the real prototype chain of a real new THREE.Mesh(), constructed on this page and introspected link by link with Object.getPrototypeOf. When you write mesh.position, the engine checks the object itself; when you write mesh.add(child), it checks the object, misses, checks Mesh.prototype, misses, and finds add two links up on Object3D.prototype. That is the whole mechanism: a property access is a walk up a chain of objects, stopping at the first link that owns the name, or falling off the end to undefined. Everything else — class, extends, super, instanceof — is notation for building and asking about that chain.

Two Words, One Ancient Confusion

JavaScript uses the word prototype for two different things, and the collision causes most of the confusion in the language:

new is the bridge between them: new Creature() creates a fresh object whose [[Prototype]] is set to Creature.prototype, then runs the constructor with that object as this.

const mesh = new THREE.Mesh()
Object.getPrototypeOf(mesh) === THREE.Mesh.prototype   // true — that's all `new` wired
mesh.prototype                                          // undefined — instances don't have one

The instance card in the instrument is honest about the consequence: position, visible, geometry are own properties, assigned onto this inside the constructor — per-instance state. The methods live higher up, each on the prototype of the class that contributes them.

Class Is Spelling, Not Machinery

class arrived in ES2015 and changed nothing about the object model — it is a politer spelling for wiring that was always there. The two panels above are the same machine: the constructor body is just a function called with a fresh this; extends is a single prototype link (Creature.prototype’s own [[Prototype]] becomes Base.prototype, so failed lookups fall through); super(name) runs the parent constructor against the same this — no copy of the base class is ever made; and each method lands once, on the prototype, not on every instance.

This is why the hero chain has the shape it does. Three.js’s source says class Mesh extends Object3D and class Object3D extends EventDispatcher — two extends, two links, and every capability of a mesh sits at exactly one address along the walk.

One Function, Ten Thousand Meshes

The prototype chain is not trivia — it is a memory and performance decision you inherit for free. A scene with ten thousand meshes holds ten thousand position vectors, because position is per-instance state — but exactly one raycast function, one add, one updateMatrixWorld, shared by every mesh through two links of chain. Behavior costs nothing per instance.

You can opt out of the sharing, deliberately or by accident:

// deliberate: shadowing — the standard Three.js selective-raycast idiom
mesh.raycast = () => {}          // THIS mesh is now invisible to the raycaster;
                                 // the walk finds the own property first and stops

// accidental: a class field holding an arrow function
class Probe extends THREE.Mesh {
  raycast = () => { /* … */ }    // one fresh closure allocated PER INSTANCE —
}                                // ten thousand probes, ten thousand functions

The first line is a feature: assigning onto the instance shadows the prototype’s method for that one object, which is exactly how you exempt a helper mesh from picking. The second is the same mechanism as a footgun — class fields assign onto this, so an arrow-function “method” manufactures a closure per object, with the allocation and garbage-collection pressure that implies. Methods belong on the prototype; per-instance functions should be a choice, not a habit.

instanceof Walks the Same Chain

mesh instanceof Object3D does not check a type tag — there are no type tags. It walks mesh’s prototype chain and asks whether Object3D.prototype appears as a link. That is why an object can honestly be an instance of four things at once (Mesh, Object3D, EventDispatcher, Object), and why Three.js often prefers duck-typed flags like .isMesh — an own-property read beats a chain walk in a hot loop, and it survives objects crossing between two copies of the library, where instanceof silently fails because each copy has its own Mesh.prototype.

Connections

The chain is the reason Three.js’s API feels uniform: scene.add, camera.lookAt, mesh.traverse are all the same functions on Object3D.prototype, met at different distances along different walks. The cost model it implies — state per instance, behavior shared — is the same discipline that shows up as pooling in garbage collection: the set of functions is fixed; only data varies. And the lookup walk is the mental model to keep when reactivity enters the picture — frameworks wrap objects in Proxies that intercept exactly these property accesses, which is precisely why hot per-frame state doesn’t belong inside them.