← Home

Notes on Dan Abramov's Just JavaScript Series

Notes on Dan Abramov’s Just JavaScript series:

Values

Very generally speaking, values are the entities existing in JavaScript.

There are two categories of values, each with various types:

  • Category 1: Primitive values

    • String, for text → e.g. "john"
    • Number, for calculations → e.g. 7 and 3.14
    • Boolean, for logical operations → only true and false
    • Null, for intentionally missing values → only null
    • Undefined, for unintentionally missing values → only undefined
  • Category 2: Special values

    • Object, for key-value pair mappings, as in { name: "john", score: 20 }
    • Array, as in [1, 2, 3]
    • Date, as in Sat Jul 04 2020 19:15:32 GMT-0300
    • Regex, as in /\s+/
    • etc.
    • Function, for bundles of functionality, as in (x) => x * 2;

To check the type of a value, use the typeof operator together with the value. You will receive back a string with the name of the type.

typeof 75; // "number"
typeof "whoa"; // "string"
typeof undefined; // "undefined"
typeof { name: "john" }; // "object"

Primitive values

Primitive values are standalone entities that cannot be changed.

Numbers and strings, the best known primitives, exist in an infinite number. Each number and each string is an immutable standalone entity. (To be precise, there are 18 quintillion number values, so they are not infinite, but for our purposes here they can be treated as if they were.)

As for strings, there is one value for every conceivable string, i.e., all conceivable strings already exist and are always there to be referenced. An empty string counts as a string. All strings have built-in properties that make them seem like objects—but even so, strings are not objects.

let friend = "john";
friend.length; // 4
friend[0]; // "j"
friend[1]; // "o"

The remaining primitives—the boolean, null and undefined types—exist in a limited number: true, false, null and undefined are four immutable standalone entities. There can be no third member of the Boolean just as much as there can be no second null or undefined. There are only two values of the Boolean type, only one value of the Null type, and only one value of the Undefined type.

Mind the formatting! The type here is uppercase, whereas the value here is monospaced with a green background. The type Undefined is the name of the group; the value undefined is the entity belonging to that group.

The primitive undefined represents an unintentionally missing value—something that should be there is now missing. It often crops up when you declare a variable and try to use it without having assigned a value to it.

let pet; // variable with no value assigned
console.log(pet); // `undefined`

The primitive null is an intentionally missing value—think of “none” or “nothing”. You are asserting that there is nothing there.

Mind the bug! typeof null returns "object" but null is not, and does not behave like, an object. This is a historical accident and has to be treated as an exception to the rule that typeof always returns the real type of the primitive inside a string.

Expressions

When you calculate the result of two number values like 1 + 2, you are not “changing” any number values. You are just creating an expression containing two primitive values, an expression that evaluates to the single primitive value 3.

When you concatenate two string values like "john " + "smith", you are not “changing” any string values. You are just creating an expression containing two primitive values, an expression that evaluates to the single primitive value "john smith".

When you combine two boolean values like true && false, you are not “changing” any boolean values. You are just creating an expression containing two primitive values, an expression that evaluates to the single primitive value false.

Primitives in an expression always evaluate to a single primitive, i.e. primitives combine with the operator to become a single primitive. In this sense, an expression can be thought of as a question: What is 1 + 2? The answer is the evaluated result: 3. When you enter an expression in the browser console, you expect a single value as an answer.

Immutability

Primitive values are immutable: inalterable, unchangeable, untouchable. You are free refer to primitives in your code, but your code cannot affect primitives in any way. Primitives are visible to you, but they exist outside your reach. They always stay as they are—primitives are read-only.

Primitives cannot be changed, even when it might look like they can.

let name = "ray";

// attempted mutation (unsuccessful)
name[1] = "o";
console.log(name); // → "ray"

Look at the highlighted line. If not in strict mode, JavaScript will ignore your command on that line, because you cannot mutate a string. If in strict mode, JavaScript will throw an error.

Special values

Special values are objects and functions. Unlike primitives, special values are standalone entities that can be changed. You code can manipulate special values. If a special value exists and your code changes it, that special value will remember and reflect that change.

There is one object value for every object literal {} we execute. There is one function value for every function expression function() {} we run. Unlike primitives, which exist from the beginning, special values are brought into existence by your code. The number of existing objects and functions will be that which your code has caused to exist.

Objects

Objects include key-value mappings as well as arrays, dates, regexes and others.

typeof { name: "john" }; // "object" (key-value mapping)
typeof [1, 2, 3]; // "object" (Array)
typeof new Date(); // "object" (Date)
typeof /\w+/; // "object" (Regex)
typeof Math; // "object" (module)

Remember: primitives are immutable, so you can only refer to (i.e., “summon”) primitives, but not change them. String values always exist as they are.

let brother = "john";
let friend = "john";
Two variables pointing to a single target value
Two variables pointing to a single target value

Objects are special values. Unlike primitives, special values allow for change. Objects can therefore be created, reshaped and destroyed.

let hero = { name: "john" };
let villain = { name: "john" };
Two variables pointing to two separate objects, each with a property pointing to the same value
Two variables pointing to two separate objects, each with a property pointing to the same value

Every time we use the object literal, we are bringing into existence a brand new object value. Here, two objects have come into existence, and each of the two labels has been connected to one of those objects.

To destroy an object, you have make sure that no label is connected to the object. If the object has no wire connected to it, that is, if the object cannot be referenced and therefore cannot be used, the garbarge collector will eventually destroy the object. You cannot destroy an object directly.

Object properties

let john = {
  surname: "smith",
  score: 30,
};

Here john is a variable pointing to an object, and this object contains key-value pairs, namely the properties surname and score pointing to the values "smith" and 30 respectively.

Properties are just like variables: both are labels with wires pointing to values. Properties do not contain values—properties point to values.

One variable pointing to an object with two properties pointing to a value each
One variable pointing to an object with two properties pointing to a value each

Accessing a property in an object is an expression:

john.surname; // → "smith"

We first evaluate the label john to the object { surname: "smith", score: 20 }. Now that we have the object, we find in it the property surname and follow the property’s wire to the value "smith". Remember that an object cannot have two properties with the same name.

If you happen to access a non-existing property in an object, as in john.spouse, JavaScript will return undefined. This does not mean that the object john has that property spouse pointing to undefined. It simply means that the object john does not have a property spouse. Remember that accessing a property is an expression, a question, to be answered by JavaScript.

When you assign a value to a property, you are detaching the wire of the property from whichever target value the property was pointing to, and you reattach that wire to a new value.

john.score = 300;
Property reattached to another value
Property reattached to another value

No nested objects

An object that seems to “contain” another object, in fact, has a property pointing to that other object. Objects do not “contain” objects!

let john = {
  surname: "smith",
  score: 20,
  friend: { name: "paul" },
};

The smaller object { name: "paul" } is not “inside” the object { surname: "smith", score: 20, friend: { name: "paul" }}. The two objects are two distinct values—the larger object simply has a property pointing to that smaller object. If you change the smaller object, the larger object will keep pointing to that smaller object in its changed version. If another big object has a property pointing to that small object, the change will be reflected there as well.

Remember: Properties always point at values! There are no nested objects.

Prototypes

If you try to access a property that does not exist on an object, JavaScript will not find it and therefore return undefined. But you can instruct JavaScript to keep looking elsewhere by setting the hidden property __proto__ in the object.

const person = {
  legs: 2,
};

const john = {
  __proto__: person,};

john.legs; // → 2

The expression john.legs returns 2. The lookup chain searches for the property legs in the object john, does not find the property legs in john, follows the hidden property __proto__ pointing to the object person, and finds it there.

The hidden property __proto__ on the highlighted line is a property connecting an object to another object: its prototype. Any object may be connected to another (its prototype!) and the prototype chain can be indefinitely long.

If the prototype does not hold the sought-after property, then JavaScript returns undefined. Only when it runs out of prototypes without finding the property does JavaScript return undefined.

Object connected to another through the hidden property __proto__
Object connected to another through the hidden property __proto__

With __proto__ we instruct JavaScript to keep looking for any missing properties in another object. They have to be missing, so JavaScript will not keep looking if the property we are looking for is already in the first object.

The built-in method hasOwnProperty() checks if a property exists in an object, excluding properties in an object’s prototype.

Prototypes only matter when accessing (i.e., reading) properties in objects. Prototypes play no role when writing (i.e., adding) properties to objects.

The Object Prototype

The Object Prototype is the primordial object that every object points to, by default, through the hidden property __proto__.

let john = {};

Even if we do not set the hidden property __proto__ in john, it will still be there, pointing to the Object Prototype. This Object Prototype is where methods like hasOwnProperty() and toString() are stored!

let john = {};
john.__proto__; // → Object Prototype

If you cut off the __proto__ link, the ordinary object will not have access to all the methods stored in Object Prototype.

let john = {
  __proto__: null,
};

john.hasOwnProperty; // → undefined

If you add a property in Object Prototype, every ordinary object will have access to the newly added property!

let john = {};
john.__proto__.soul = true;

let paul = {};
paul.soul; // → true

Warning! Do not manipulate __proto__ directly!

Functions

As with objects, whenever you create a function, you are causing a new function to come into existence—functions do not exist from the beginning, as primitives do. Functions can be created, altered and destroyed.

For example, see below how ten distinct functions hello() are created and passed into console.log(). We create a function every time we use a function declaration like function hello() {}.

for (let i = 0; i < 10; i++) {
  console.log(function hello() {});
}

If you assign a function to a variable, the label will point to the function itself. Assigning a function to a variable is not the same as assigning the value returned by the function to a variable.

function getName() {
  return 7;
}

let name = getName; // variable points to function
console.log(retrieveName); // function itself is logged out
function getName() {
  return "john";
}

let name = getName(); // variable points to return value of function
console.log(name); // → "john"

Variables and values

A variable is a wire connecting a label to a target value, whether primitive or special. The connection always starts at the label and ends at the target value. This wire allows you to refer to that particular value by a custom name inside your code.

let bestFriend = "john";

When you reassign a variable, you are reconnecting (i.e. “reattaching”) the wire of the label to another target value, i.e. you are removing the end of the wire from the target value, and attaching that end to another target value. The label where the wire originates, the start of the wire, remains where it is.

bestFriend = "mary";
New string assigned to a variable
New string assigned to a variable

Declaration, evaluation and assignment

Below we declare a variable and assign a value to it:

Variable declaration and assignment
Variable declaration and assignment

But what if the right side of reassignment contains, not a value, but an expression? Remember, an expression is a combination of primitive values that can be evaluated down into a single primitive. If a reassignment contains an expression, JavaScript first evaluates the expression and then uses the evaluated result in the assignment.

let surname = "smith";
let fullName = "john " + surname;

JavaScript first evaluates the expression "john " + surname into "john " + "smith" and that other expression into the single value "john smith". Having evaluated the expression to a single value, JavaScript uses that single value in the assignment let fullName = "john smith".

In fact, while we have been referring to expressions as two or more primitives that can be evaluated down into a single result, we can be more precise. There also exist single-primitive expressions: literal expressions. A literal is both a question and its own answer. If you input a literal in the browser console, it will answer back with that same literal. let surname = "smith"; is therefore an assignment containing an expression, because "smith" is an expression itself. If you then enter that label in the browser console, it will answer back with the attached value.

Evaluation precedes assignment:

let a = 5;
let b = a;
a = 0;
First evaluate the expression, then assign the value
First evaluate the expression, then assign the value

In other words…

  1. On line 1, we connect the label a to the value 5.
  2. On line 2, we first evaluate a to 5, then connect the label b to the value 5.
  3. On line 3, we detach the label a from wherever it was pointing to, and reattach the label a to the value 0.

As a result, in the end a points to 0 and b points to 5.

The same holds true for objects.

let first = {};
let second = first;
let third = second;
third = {};

In other words…

  1. On line 1, we create an object and connect the label first to it.
  2. On line 2, we evaluate the label first to that original object and connect the label second to that original object.
  3. On line 3, we evaluate the label second to that original object and connect the label third to that original object.
  4. On line 4, we create another new object, detach the label third from the original object, and reattach the label third to this other new object.

Evaluation also precedes the passing of arguments:

function addTwo(num) {
  num += 2;
}

let score = 10;
addTwo(score);
console.log(score);

In other words…

  1. On lines 1 to 3, we declare the function addTwo.
  2. On line 5, we connect the label score to the value 10.
  3. On line 6, we first evaluate the label score to the value 10 and then pass in the value 10 to the function. (We do not, and cannot, pass in the variable!)
  4. Inside the function, we connect the label num to the value 10, then immediately evaluate num to 10, we evaluate 10 + 2 to 12, we detach the label num from the 10 it was pointing to, and we reattach the label num to 12. The function ends and returns nothing.
  5. On line 7, we evaluate the label score to the value 10 (see step 2) and then we log the value 10 to the console.

At no point is the variable score reassigned.