Just JavaScript
The JavaScript Universe
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.
Inspect type of something with typeof
Note: typeof(null) == 'object'
. It's a known historical bug in the language. Here's an explanation: https://2ality.com/2013/10/typeof-null.html
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.
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. 🙁
null
is used for intentionally missing values.
booleans
Only two boolean values: true
and false
.
numbers
console.log(0.1 + 0.2 === 0.3); // false
console.log(0.1 + 0.2 === 0.30000000000000004); // true
As we move from 0
in either direction, we start losing precision. At some point, even two closes JavaScript numbers stay further apart than by 1
:
onsole.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 (again!)
console.log(Number.MAX_SAFE_INTEGER + 3); // 9007199254740994
console.log(Number.MAX_SAFE_INTEGER + 4); // 9007199254740996
console.log(Number.MAX_SAFE_INTEGER + 5); // 9007199254740996 (again!)
learn more about floating-point math
special numbers
NaN
Infinity
-Infinity
-0
examples
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`.
console.log(typeof("こんにちは")); // "string"
console.log(typeof('こんにちは')); // "string"
console.log(typeof(`こんにちは`)); // "string"
Strings are NOT objects.
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
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
and0
/-0
) - loose equality:
a == b
(🛑✋ don't use this!) - same value equality:
Object.is(a, b)
Despite the Object
in the method name, Object.is
is not specific to objects.
Exceptions:
NaN === NaN
isfalse
, although they are the same value (history)-0 === 0
and0 === -0
are true, although they are different values (history).
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)
From https://dorey.github.io/JavaScript-Equality-Table/unified/
Properties
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
orundefined
, 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.
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.
const shrek = { species: 'ogre' };
shrek = fiona; // TypeError
shrek.species = 'human';
console.log(shrek.species); // 'human'