| # JavaScript Overview for V8 Developers |
| |
| This document provides a high-level overview of how JavaScript works, focusing on concepts that are relevant for understanding V8's implementation. |
| |
| ## Primitives vs. Objects |
| |
| JavaScript has two main categories of values: |
| |
| ### 1. Primitives |
| Immutable values that are passed by value. |
| * `undefined`, `null`, `boolean` (`true`/`false`), `number`, `string`, `bigint`, `symbol`. (Spec names are capitalized, but JS `typeof` returns lowercase strings). |
| * In V8, small integers (Smis) are stored directly in the pointer word, while other primitives are allocated on the heap (e.g., `HeapNumber`, `String`). |
| |
| ### 2. Objects |
| Mutable collections of properties, passed by reference. |
| * Any value that is not a primitive is an object (including arrays and functions). |
| * In V8, objects are represented by `JSObject` and its subclasses. |
| * **Note**: JS also has primitive wrapper objects (e.g., `new String('foo')`), which are actual objects and not primitives. Be careful not to confuse them with the primitive values! |
| |
| ## How V8 implements prototypes |
| |
| JavaScript uses a **prototypal inheritance** model. Every object has a hidden link to another object called its **prototype** (internal `[[Prototype]]`). |
| |
| ### Prototype Chain |
| * When you access a property on an object, JavaScript first looks for it on the object itself. |
| * If not found, it looks at the object's prototype, and then the prototype's prototype, forming a **prototype chain**. |
| * The chain ends when it reaches `null` (typically `Object.prototype.__proto__` is `null`). |
| |
| ### `Foo.prototype` vs `foo.__proto__` |
| * `Foo.prototype` is a property of the **constructor function** `Foo`. It is the object that will be used as the prototype for instances created with `new Foo()`. |
| * `foo.__proto__` is a property of the **instance** `foo`. It points to the prototype object that `foo` inherited from. |
| * If `foo = new Foo()`, then `foo.__proto__ === Foo.prototype`. |
| |
| ```dot |
| digraph G { |
| rankdir=TB; |
| node [shape=record]; |
| edge []; |
| |
| Foo [label="Constructor: Foo"]; |
| Foo_Proto [label="Foo.prototype"]; |
| foo [label="Instance: foo"]; |
| Obj_Proto [label="Object.prototype"]; |
| null_node [label="null"]; |
| |
| Foo -> Foo_Proto [label=".prototype"]; |
| Foo -> foo [label="new Foo", style=dashed]; |
| foo -> Foo_Proto [label="[[Prototype]]"]; |
| Foo_Proto -> Obj_Proto [label="[[Prototype]]"]; |
| Obj_Proto -> null_node [label="[[Prototype]]"]; |
| } |
| ``` |
| |
| ### `__proto__` and V8 Implementation |
| * `__proto__` is an accessor property (getter/setter) on `Object.prototype` that exposes the internal prototype link. |
| * Modern JavaScript recommends using `Object.getPrototypeOf()` and `Object.setPrototypeOf()` instead of `__proto__`. |
| * In V8, the prototype is stored in the `Map` of the object. Changing the prototype of an object changes its map! |
| |
| ## ES6 Classes |
| |
| ES6 classes are primarily **syntactic sugar** over JavaScript's existing prototype-based inheritance. |
| |
| ```javascript |
| class Animal { |
| constructor(name) { |
| this.name = name; |
| } |
| speak() { |
| console.log(`${this.name} makes a noise.`); |
| } |
| } |
| |
| class Dog extends Animal { |
| speak() { |
| console.log(`${this.name} barks.`); |
| } |
| } |
| ``` |
| |
| Under the hood, this is equivalent to: |
| |
| ```javascript |
| function Animal(name) { |
| this.name = name; |
| } |
| Animal.prototype.speak = function() { |
| console.log(this.name + ' makes a noise.'); |
| }; |
| |
| function Dog(name) { |
| Animal.call(this, name); |
| } |
| Dog.prototype = Object.create(Animal.prototype); |
| Dog.prototype.constructor = Dog; |
| Dog.prototype.speak = function() { |
| console.log(this.name + ' barks.'); |
| }; |
| ``` |
| |
| |
| |
| ## File Structure |
| * `src/objects/js-objects.h`: Definitions for JavaScript objects. |
| * `src/objects/map.h`: Definitions for Maps (hidden classes) which store prototype links. |