The Abramov's mental model represents primitive values as distant stars, floating farthest form our code. We can point to them, but they will always stay where they are, unchanged.
Values
Values
Primitive Values
Objects and Functions
Functions are objects, but with additional features.
9 types of values
undefined
null
Boolean
Numbers
Strings
BigInts (uncommon and new)
Symbols (uncommon)
Objects ({}, etc)
Functions (x => x * 2, etc)
From 1 to 7, they are the primitive values.
Note: things like arrays, dates and regular expressions are fundamentally Objects.
I like to look the image below and think "the further a type is from the center, the less manipulable it is"
Expressions always return a value.
Values and Variables
Primitive values are immutable (objects and functions are mutable).
Code where the results is not what is expected:
let reaction = 'yikes'
reaction[0] = 'l'
console.log(reaction) // "yikes"
Variables are not values. Variables point to values.
let pet = 'Narwhal';
pet = 'The Kraken';
console.log(pet); // "The Kraken"
In JavaScript variables don't have types, only values do.
Rules of Assignment
The right side of an assignment must be an expression, so it always result in a value.
// "connects" x with 10
let x = 10;
// x is evaluated to 10
// connects y with 10
let y = x;
// connects x with 0
x = 0;
// x is connected with 0
// y is connected with 10
Note that y = x did not mean point y to x. We can’t point variables to each other! Variables always point to values.
When we call a function, we're always passing the value.
function double(x) {
x = x * 2;
}
let money = 10;
// money is an expression resulting in 10
// therefore we're passing the value 10 to the function
double(money);
console.log(money); // 10
If the the value being passed is a primitive value, it can't be changed. However, if it's a mutable value (like an array, which is an Object) it can be changed!
Primitive Values
undefined
undefined is a type and a value.
It represents the concept of an unintentionally missing value.
When you declare a variable and don't assign any value to it, then it points to the undefined value, by default.
null
null is also a type and a value.
Note
null is the only value of its own type, but it's also a liar, as it pretends to be an object:
console.log(typeof(null)); // "object"
You must understand that it's a lie! null is a primitive value, and is NOT an object at all.
Unfortunately, typeof(null) is a historical accident that we'll have to live with forever. 🙁
let scale = 0;
let a = 1 / scale; // Infinity
let b = 0 / scale; // NaN
let c = -a; // -Infinity
let d = 1 / c; // -0
// fun-fact
console.log(typeof(NaN)); // "number"
BigInt
Not much used...
let alot = 9007199254740991n; // n at the end makes it a BigInt!
console.log(alot + 1n); // 9007199254740992n
console.log(alot + 2n); // 9007199254740993n
console.log(alot + 3n); // 9007199254740994n
console.log(alot + 4n); // 9007199254740995n
console.log(alot + 5n); // 9007199254740996n
Strings
Three ways to write strings: "double quotes", 'single quotes', `backticks`.
All strings have a few built-in properties, but that doesn't mean they are objects!
let cat = 'Cheshire'
console.log(cat.length)
console.log(cat[0])
console.log(cat[1])
You can't assign anything to cat[0]. Strings are primitives, and all primitives are immutable.
Objects and Functions
Non-primitive types: types that let us make our own values.
Object are not primitive values, it means they're mutable.
Every time we use the {} object literal, we create a brand new object value:
let shrek = {};
let donkey = {};
Note how this 👆 is different from what happens with primitive values.
An object literal creates a brand new distinct object value
// in this loop we're passing the very
// same value, `2`, to console.log
for (let i = 0; i < 5; i++) {
console.log(2)
}
// in this loop we're passing a completely
// distinct object value in each iteration
for (let i = 0; i < 5; i++) {
console.log({}) // <-- creates a brand new object
}
Technically, functions are objects in JavaScript. We’ll keep treating them as a separate fundamental type because they have unique capabilities compared to regular objects. But, generally speaking, if you can do something to an object, you can also do that to a function too. They are very special objects
Important
Primitive values (strings, numbers, etc...) have always existed in our universe. For example, writing 2 or "hello" always “summons” the same number or a string value. Objects and functions behave differently and allow us to generate our own values. Writing {} or function() {} always creates a brand new, different value. This idea is crucial to understanding equality in JavaScript.
Equality of Values
Kinds of equality:
strict equality: a === b (has two exceptions: NaN and 0/-0)
loose equality: a == b (🛑✋ don't use this!)
same value equality: Object.is(a, b)
Fun
Despite the Object in the method name, Object.is is not specific to objects.
Exceptions:
NaN === NaN is false, although they are the same value (history)
-0 === 0 and 0 === -0 are true, although they are different values (history).
Important
NaN is the only value that's not equal to itself.
Since NaN === NaN is always false, never check if a variable is NaN through strict equality. Use this insteaad: Number.isNaN(variable)
This example shows how effective is our mental model to detect potential unexpected bugs:
// Sherlock Holmes live in london
let sherlock = {
surname: 'Holmes',
address: { city: 'London' }
};
// John Watson go live with Mr. Holmes
let john = {
surname: 'Watson',
address: sherlock.address
};
// then Watson decided to change his name and live in Malibu
john.surname = 'Lennon'
john.address.city = 'Malibu'
// now let's check
console.log(john.surname); // "Lennon"
console.log(john.address.city); // "Malibu"
console.log(sherlock.surname); // "Holmes"
console.log(sherlock.address.city); // "Malibu"
// 👆 this is unexpected! -------------👆
Unlike variables, properties belong to a particular object.
In our JavaScript mental model, both variables and properties act like "wires".
Importantly, properties don't contain values - they point to them!
Simplified set of rules that JavaScript uses when parsing an expression like sherlock.boat:
Figure out the value of the part before the dot .
If that value is null or undefined, throw an error immediately.
Check whether a property with that name exists on our object:
If it exists, answer with the value this property points to.
If it doesn't exist, answer with the undefined value.
Mutation
No Nested Objects
let sherlock = {
surname: 'Holmes',
address: { city: 'London' }
};
In the code above, we have not one, but two completely separate objects. Two pairs of curly braces mean two objects.
Important
Objects might appear "nested" in code, but in our universe each object is completely separate. An object cannot be "inside" of another object!
let vs const
Remember: const prevents variable reassignment, not object mutation.