Track my progress through https://javascript.info/
The JavaScript language
- An Introduction to JavaScript
- Code editors
- Developer console
- Hello, world!
- Code structure
- The modern mode, "use strict"
- Variables
- Data types
- Type Conversions
- Operators
- Comparisons
- Interaction: alert, prompt, confirm
- Conditional operators: if, '?'
- Logical operators
- Loops: while and for
- The "switch" statement
- Functions
- Function expressions and arrows
- JavaScript specials
- Debugging in Chrome
- Coding style
- Comments
- Ninja code
- Automated testing with mocha
- Polyfills
- Objects
- Garbage collection
- Going deeper:
- Book “The Garbage Collection Handbook: The Art of Automatic Memory Management” (R. Jones et al)
- V8
- Going deeper:
- Symbol type
- Global Symbols
- In order to create or read a symbol in the global registry use
Symbol.for(key)
- Reverse call to return a name by a global symbol
Symbol.keyFor(sym)
- In order to create or read a symbol in the global registry use
- "Hidden" object properties, does not appear in
for..in
- Technically, symbols are still accessible by Object.getOwnPropertySymbols(obj) that allows us to get all symbols, and Reflect.ownKeys(obj) that returns all keys of an object including symbolic ones
- There are many system symbols used by JavaScript, accessible as
Symbol.*
- Ex.
Symbol.iterator
for iterables - Ex.
Symbol.toPrimitive
to setup object-to-primitive conversion
- Ex.
- Global Symbols
- Object methods, "this"
- Object to primitive conversion
- The object-to-primitive conversion is called automatically by many built-in functions and operators that expect a primitive as a value.
- There is no Object to Boolean primitive conversion, as every object is true.
- For any primitive conversion JavaScript takes a hint, there are 3 types of it:
string
(for string conversions, likealert
)number
(for maths)default
(few operators)
- The specification describes explicitly which operator uses which hint. There are some cases in where operators "don't know what to expect" and use the
default
hint. Usually for built-in objects thedefault
hint is handled the same waynumber
is handled, so in practide the last two are merged together. - The conversion algorithm is:
- Call
obj[Symbol.toPrimitive](hint)
if the method exists, - Otherwise if hint is
string
- try
obj.toString()
andobj.valueOf()
, whatever exists.
- Otherwise if hint is
number
ordefault
- try
obj.valueOf()
andobj.toString()
, whatever exists. - In practice, it's often enough to implement only
obj.toString()
as "catch-all" method for all conversions that return a "human-readable", representation of an object, for logging or debugging purposes.
- Call
- Constructor, operator "new"
- Constructors are regular functions, but there's a common agreement to name them with capital letters first.
- Constructor functions should only be called using
new
. Such a call implies a creation of emptythis
at the start and returning the populated one at the end.
- Methods of primitives
- Numbers
- Imprecise calculations:
- In JavaScript, a number is internally represented in 64-bit format IEEE 754. So, there are exactly 64 bits to store a number,
52
of them are used to store the digits,11
of them store the decimal point (zero for integer numbers), and1
bit is for the sign. - If a number is too big, it would overflow thr 64-bit storare, and potentially giving
Infinity
. - Also, because a number is stored in memory in its binary form, fractions like
0.1
and0.2
are unending fractions. So,0.1 + 0.2 === 0.3
is falsy.
- In JavaScript, a number is internally represented in 64-bit format IEEE 754. So, there are exactly 64 bits to store a number,
- isFinite and isNaN
- In JavaScript there are two special values that belong to
number
, but are not "normal" numbers, so there are special functions to check them. Infinity
(and-Infinity
) is a special numeric value that is grester (less) than anything.isFinite(number)
converts its argument to a number and returns true if it’s a regular number, not NaN/Infinity/-Infinity.NaN
represents an error, the value is unique that it does not equal anything, includingNaN
.isNaN(number)
converts its argument and test it for beingNaN
.
- In JavaScript there are two special values that belong to
- To write big numbers:
- Append "e" with the zeroes count to the number. Like:
123e6
is123
with 6 zeroes. - A negative number after
"e"
causes the number to be divided by 1 with given zeroes. That’s for one-millionth or such.
- Append "e" with the zeroes count to the number. Like:
- For different numeral systems:
- Can write numbers directly in hex (
0x
), octal (0o
) and binary (0b
) systems parseInt(str, base)
parses an integer from any numeral system with base:2 <= base <= 36
.num.toString(base)
converts a number to a string in the numeral system with the givenbase
.
- Can write numbers directly in hex (
- For converting values like
12pt
and100px
to a number:- Use
parseInt/parseFloat
for the “soft” conversion, which reads a number from a string and then returns the value they could read before the error.
- Use
- For fractions:
- Round using
Math.floor
,Math.ceil
,Math.trunc
,Math.round
ornum.toFixed(precision)
. - Make sure to remember there’s a loss of precision when working with fractions.
- Round using
- See the Math object when you need them. The library is very small, but can cover basic needs.
- Imprecise calculations:
- Strings
- There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions.
- Strings in JavaScript are encoded using
UTF 16
. - We can use special characters like
\n
and insert letters by their unicode using\u....
- To get a character, use:
[]
. - To get a substring, use:
slice
orsubstring
. - To lowercase/uppercase a string, use:
toLowerCase/toUpperCase
. - To look for a substring, use:
indexOf
, orincludes/startsWith/endsWith
for simple checks. - To compare strings according to the language, use:
localeCompare
, otherwise they are compared by character codes. - Other helpful methods:
str.trim()
– removes (“trims”) spaces from the beginning and end of the string.str.repeat(n)
– repeats the stringn
times.- …and more. See the manual for details.
- Arrays
- Array methods
- A cheatsheet of array methods:
- To add/remove elements:
push(...items)
-- adds items to the end,pop()
-- extracts an item from the end,shift()
-- extracts an item from the beginning,unshift(...items)
-- adds items to the beginning.splice(pos, deleteCount, ...items)
-- at indexpos
deletedeleteCount
elements and insertitems
.slice(start, end)
-- creates a new array, copies elements from positionstart
tillend
(not inclusive) into it.concat(...items)
-- returns a new array: copies all members of the current one and addsitems
to it. If any ofitems
is an array, then its elements are taken.
- To search among elements:
indexOf/lastIndexOf(item, pos)
-- look foritem
starting from positionpos
, return the index or-1
if not found.includes(value)
-- returnstrue
if the array hasvalue
, otherwisefalse
.find/filter(func)
-- filter elements through the function, return first/all values that make it returntrue
.findIndex
is likefind
, but returns the index instead of a value.
- To transform the array:
map(func)
-- creates a new array from results of callingfunc
for every element.sort(func)
-- sorts the array in-place, then returns it.reverse()
-- reverses the array in-place, then returns it.split/join
-- convert a string to array and back.reduce(func, initial)
-- calculate a single value over the array by callingfunc
for each element and passing an intermediate result between the calls.
- To iterate over elements:
forEach(func)
-- callsfunc
for every element, does not return anything.
- Additionally:
Array.isArray(arr)
checksarr
for being an array.
- Please note that methods
sort
,reverse
andsplice
modify the array itself. - These methods are the most used ones, they cover 99% of use cases. But there are few others:
- arr.some(fn)/arr.every(fn) checks the array.
- The function
fn
is called on each element of the array similar tomap
. If any/all results aretrue
, returnstrue
, otherwisefalse
. - arr.fill(value, start, end) -- fills the array with repeating
value
from indexstart
toend
. - arr.copyWithin(target, start, end) -- copies its elements from position
start
till positionend
into itself, at positiontarget
(overwrites existing). - For the full list, see the manual.
- To add/remove elements:
- A cheatsheet of array methods:
- Iterables
- Objects that can be used in
for..of
are called iterables. - Technically, iterables must implement the method named
Symbol.iterator
.- The result of
obj[Symbol.iterator]
is called an iterator. It handles the further iteration process. - An iterator must have the method named
next()
that returns an object{done: Boolean, value: any}
, heredone:true
denotes the iteration end, otherwise thevalue
is the next value.
- The result of
- The
Symbol.iterator
method is called automatically byfor..of
, but we also can do it directly. - Built-in iterables like strings or arrays, also implement
Symbol.iterator
. - String iterator knows about surrogate pairs.
- Objects that have indexed properties and
length
are called array-like. Such objects may also have other properties and methods, but lack the built-in methods of arrays. - If we look inside the specification – we’ll see that most built-in methods assume that they work with iterables or array-likes instead of “real” arrays, because that’s more abstract.
Array.from(obj[, mapFn, thisArg])
makes a realArray
of an iterable or array-likeobj
, and we can then use array methods on it. The optional argumentsmapFn
andthisArg
allow us to apply a function to each item.
- Objects that can be used in
- Map, Set, WeakMap and WeakSet
Map
is a collection of keyed values.- The differences form a regular
Object
:- Any keys, objects can be objects.
- Iterates in the insertion order.
- Additiona convenient methods, the
size
property.
- Main methods:
new Map()
creates the map.map.set(key, value)
stores the value by the key.map.get(key)
returns the value by the key,undefined
ifkey
doesn't exist in map.map.has(key)
returnstrue
if thekey
exists,false
otherwise.map.delete(key)
removes the value by the key.map.clear()
clears the map.map.size
returns the current element count.- For looping over a
map
, there are 3 methods:map.keys()
returns an interable for keys.map.values()
returns an iterable for values.map.entries()
returns an iterable for entries[key, value]
, it's used by default infor..of
.
- Besides that,
Map
has a built-inforEach
method, similar toArray
.map.forEach( (value, key, map) => {} );
- The differences form a regular
Set
is a collection of unique values.- Unlike an array, does not allow to reorder elements.
- Keeps insertion order.
- Main methods:
new Set(iterable)
creates the set, optionally from an array of values (any iterable will do).set.add(value)
adds a value, returns the set itself.set.delete(value)
removes the value, returns true if value existed at the moment of the call, otherwise false.set.has(value)
returns true if the value exists in the set, otherwise false.set.clear()
removes everything from the set.set.size
is the elements count.- For looping over a
Set
the same iterators are also supported:set.keys()
returns an iterable object for values.set.values()
same asset.keys()
, kept for compatibility withMap
,set.entries()
returns an iterable object for entries[values, value]
, exists for compatibility withMap
- Using
forEach
is also possible, but for compatibility withMap
the functions has 3 arguments: a value, then again a value, an then the target object. The same value appears in the argument twice.set.forEach( (value, valueAgain, set) => {} );
WeakMap
a variant ofMap
that allows only objects as keys and removes them once they become inaccessible by other means.- It does not support operations on the structure as a whole: no
size
, noclear()
, no iterations.
- It does not support operations on the structure as a whole: no
WeakSet
is a variant ofSet
that only stores objects and removes them once they become inaccessible by other means.- Also does not support
size/clear()
and iterations.
- Also does not support
WeakMap
andWeakSet
are used as “secondary” data structures in addition to the “main” object storage. Once the object is removed from the main storage, so it only stays inWeakMap/WeakSet
, they clean up automatically.
- Object.keys, values, entries
- Previously, we saw methods
map.keys()
,map.values()
andmap.entries()
. These methods are generic, there is a common agreement to use them for data structures. If we ever create a data structure of our own, we should implement them too.- They are supported in
Map
,Set
andArray
(except forarr.values()
).
- They are supported in
- For plain objects, the following methods are available.
Object.keys(obj)
- returns an array of keys.
Object.values(obj)
- returns an array of values.
Object.entries(obj)
- returns an array of [key, value] pairs.
- There are two differences with the past methods.
- First, we have to call
Object.keys(obj)
, and notobj.keys()
. - Second,
Object.*
methods return “real” array objects, not just an iterable. That’s mainly for historical reasons.
- First, we have to call
- Like
for..in
loop, these methods ignore properties that useSymbol(...)
as keys. To get the symbolic keys we have two methods:Object.getOwnPropertySymbols(obj)
- returns an array only of symbolic properties.
Reflect.ownKeys(obj)
- returns all object properties, 'regular' and symbolic ones.
- Previously, we saw methods
- Destructuring assignment
- Destructuring assignment allows for instantly mapping an object or array onto many variables.
- Object syntax:
let {prop : varName = default, ...} = object
- This means that property
prop
should go into the variablevarName
and, if no such property exists, thendefault
value should be used.
- Array syntax:
let [item1 = default, item2, ...rest] = array
- The first item goes to
item1
, the second goes intoitem2
, all the rest makes the arrayrest
. - For more complex cases, the left side must have the same structure as the right one.
- Date and time
- Methods:
- Creation:
new Date()
without arguments, create aDate
object for the current date and time.new Date(milliseconds)
create aDate
object with the time equal to number of milliseconds (1/1000 of a second) passed afeterJan 1st of 1970 UTC+0
.new Date('year-month-day')
, if the argument is a string then it is parsed with theDate.parse
algorithm.new Date(year, month, date, hours, minutes, seconds, ms)
create the date with the given components in the local time zone. Only two first arguments are obligatory.year
must have 4 digits: 2013 is okay, 98 is not.month
count starts with 0 (Jan), up to 11 (Dec).date
parameter is actually the day of month, if absent then 1 is assumed.hours/minutes/seconds/ms
is absent, they are assumed to be equal 0.
- Access:
getFullYear()
get the year (4 digits).getMonth()
get the month, from 0 to 11.getDate()
get the day of the month, from 1 to 31.getDay()
get the day of the week, from 0 (Sunday) to 6 (Saturday).getHours()
,getMinutes()
,getSeconds()
,getMilliseconds()
.
- All the methods above return the components relative to the local time zone.
- There are also their UTC-counterparts, that return day, month, year and so on for the time zone UTC+0:
getUTCFullYear()
,getUTCMonth()
,getUTCDay()
. Just insert the "UTC" right after "get". - Besides the given methods, there are two special ones, that do not have a UTC-variant:
getTime()
returns the timestamp for the date – a number of milliseconds passed from the January 1st of 1970 UTC+0.getTimezoneOffset()
returns the difference between the local time zone and UTC, in minutes.
- Creation:
- Date and time in JavaScript are represented with the
Date
object. We can't create 'only date' or 'only time'. - Months are counted from zero (yes, Janaury is zero month).
- Days of week in
getDay()
are also counted from zero (that's Sunday). Date
auto-corrects itself when out-of-range components are set. Good for adding/subtracting days/months/hours.- Dates can be sutracted, giving their differences in milliseconds. That's because a
Date
becomes the timestamp when converted to a number. - Use
Date.now()
to get the current timestamp fast. - Unlike many othe systems, timestamps in JavaScript are in milliseconds, not in seconds.
- When we need more precise measurements, we need to leverage for the different envioroments of JavaScript, JavaScript itself does not have a way to measure time in microseconds (1 millionth of a second).
- Browser has
performance.now()
. - Node has
microtime
module.
- Browser has
- Methods:
- JSON methods, toJSON
- JSON is a data format that has its own independent standard and libraries for most programming languages.
- JSON supports plain objects, arrays, strings, numbers, booleans and
null
.- Function properties (methods), Symbolic properties, and Properties that store undefined, are skipped.
- JavaScript provides methods JSON.stringify to serialize into JSON and JSON.parse to read from JSON.
- Both methods support transformer functions for smart reading/writing.
- For
JSON.stringify
we have replacer. - For
JSON.parse
we have reviver.
- For
- If an object has
toJSON
, then it is called byJSON.stringify
.
- Recursion and stack
- Rest parameters and spread operator
- When we se
"..."
in the code, it is either rest parameters or the spread operator. - There's an easy way to distinguish between them:
- When
...
is at the end of function parameters, it's "rest parameters" and gathers the rest of the list into arguments into an array. - When
...
occurs in a function call or alike, it's called a "spread operator" and expands an array into a list.
- When
- Use patterns:
- Rest parameters are used to create functions that accept any number of arguments.
- The spread operator is sued to pass an array to functions that normally require a list of many arguments.
- Together they help to travel between a list and an array of parameters with ease.
- All arguments of a function call are also available in "old-style"
arguments
, an array-like iterable object.
- When we se
- Closure
- There is a general programming term
closure
, that developers generally should know. - A
closure
is a function that remembers its outer variables and can access them. In some languages, that's not possible, or a function should be written in a special way to make it happen. But in JavaScript all functions are naturally closures (there's only one exclusion, thenew Function()
syntax).
- There is a general programming term
- The old "var"
- There are two main differences of
var
:- Variables have no block scope, they are visible minimum at the function level.
- Variable declarations are processed at function start.
- There’s one more minor difference related to the global object, we’ll cover that in the next chapter.
- These differences are actually a bad thing most of the time. First, we can’t create block-local variables. And hoisting just creates more space for errors. So, for new scripts
var
is used exceptionally rarely.
- There are two main differences of
- Global object
- When JavaScript was created, there was an idea of a “global object” that provides all global variables and functions. It was planned that multiple in-browser scripts would use that single global object and share variables through it.
- Since then, JavaScript greatly evolved, and that idea of linking code through global variables became much less appealing. In modern JavaScript, the concept of modules took its place.
- But the global object still remains in the specification. -In a browser it is named “window”, for Node.JS it is “global”, for other environments it may have another name.
- It does two things:
- Provides access to built-in functions and values, defined by the specification and the environment. For instance, we can call
alert
directly or as a method ofwindow
.window.alert("Hello");
- Provides access to global Function Declarations and var variables. We can read and write them using its properties.
- ...But the global object does not have variables declared with
let/const
!
- ...But the global object does not have variables declared with
- Provides access to built-in functions and values, defined by the specification and the environment. For instance, we can call
- The global object is not a global Environment Record
- In versions of ECMAScript prior to ES-2015, there were no
let/const
variables, onlyvar
. And global object was used as a global Environment Record (wordings were a bit different, but that’s the gist). - But starting from ES-2015, these entities are split apart. There’s a global Lexical Environment with its Environment Record. And there’s a global object that provides some of the global variables.
- As a practical difference, global
let/const
variables are definitively properties of the global Environment Record, but they do not exist in the global object. - Naturally, that’s because the idea of a global object as a way to access “all global things” comes from ancient times. Nowadays is not considered to be a good thing. Modern language features like
let/const
do not make friends with it, but old ones are still compatible.
- In versions of ECMAScript prior to ES-2015, there were no
- Function object, NFE
- Named Function Expression
- Functions are
objects
. - Properties:
name
- the function name. Exists not only when given in the function definition, but also for assignments and object properties.length
- the number of arguments in the function definition. Rest parameters are not counted.
- If the function is declared as a Function Expression (not in the main code flow), and it carries the name, then it is called a Named Function Expression. The name can be used inside to reference itself, for recursive calls or such.
- Also, functions may carry additional properties. Many well-known JavaScript libraries make great use of this feature.
- They create a “main” function and attach many other “helper” functions to it. For instance, the jquery library creates a function named
$
. The lodash library creates a function_
. And then adds_.clone
,_.keyBy
and other properties to (see the docs when you want learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts. - So, a function can do a useful job by itself and also carry a bunch of other functionality in properties.
- The "new Function" syntax
- The syntax:
let func = new Function(arg1, arg2, ..., body);
- For historical reasons, arguments can also be given as a comma-separated list.
- These three mean the same:
new Function('a', 'b', 'return a + b'); // basic syntax
new Function('a,b', 'return a + b'); // comma-separated
new Function('a , b', 'return a + b'); // comma-separated with spaces
- Functions created with
new Function
, have[[Environment]]
referencing the global Lexical Environment, not the outer one. Hence, they cannot use outer variables. But that’s actually good, because it saves us from errors. Passing parameters explicitly is a much better method architecturally and causes no problems with minifiers.
- The syntax:
- Scheduling: setTimeout and setInterval
- methods
setInterval(func, delay, ..args)
andsetTimeout(func, delay, ..args)
allow to run thefunc
regulary/once afterdelay
in milliseconds. - To cancel the execution, we should call
clearInterval/clearTimeout
with the value returned bysetInterval/setTimeout
. - Nested
setTimeout
calls are a more flexible alternative thansetInterval
. Also they can guarantee the minimal time between the executions. - Zero-timeout scheduling
setTimeout(...,0)
is used to schedule a call as soon as possible, but after the current code is complete`.- Some use cases for zero-timeout scheduling:
- To split CPU-hungry tasks into pieces, so that the script doesn't "hang".
- To let the browser do something else while the process is going on.
- Note that, scheduling methods does not guarantee the exact delay. We should not rely on that in the scheduling code.
- For example, the in-browser timer may slow down for a lot of reasons:
- The CPU is overloaded.
- The browser tab is in background mode.
- The laptop is on battery.
- All that may increase the minimal timer resolution (the minimal delay) to 300ms or even 1000ms, depending on the browser and settings.
- For example, the in-browser timer may slow down for a lot of reasons:
- Some use cases for zero-timeout scheduling:
- methods
- Decorators and forwarding, call/apply
- Function binding
- Currying and partials
- Arrow functions revisited
- Property flags and descriptors
- Property getters and setters
- Prototypal inheritance
- F.prototype
- Native prototypes
- Methods for prototypes
- Class patterns
- Classes
- Class inheritance, super
- Class checking: "instanceof"
- Mixins
- Error handling, "try..catch"
- Custom errors, extending Error
Browser: Document, Events, Interfaces
- Browser environment, specs
- DOM tree
- Walking the DOM
- Searching: getElement* and querySelector*
- Node properties: type, tag and contents
- Attributes and properties
- Modifying the document
- Styles and classes
- Element size and scrolling
- Window sizes and scrolling
- Coordinates
- Introduction to browser events
- Bubbling and capturing
- Event delegation
- Browser default actions
- Dispatching custom events
- Mouse events basics
- Moving: mouseover/out, mouseenter/leave
- Drag'n'Drop with mouse events
- Keyboard: keydown and keyup
- Scrolling
- Page lifecycle: DOMContentLoaded, load, beforeunload, unload
- Resource loading: onload and onerror
- Form properties and methods
- Focusing: focus/blur
- Events: change, input, cut, copy, paste
- Form submission: event and method submit
Additional articles
- Bezier curve
- CSS-animations
- JavaScript animations
- Popups and window methods
- Cross-window communication
- The clickjacking attack
- Patterns and flags
- Methods of RegExp and String
- Character classes
- Escaping, special characters
- Sets and ranges [...]
- The unicode flag
- Quantifiers +, *, ? and {n}
- Greedy and lazy quantifiers
- Capturing groups
- Backreferences: \n and $n
- Alternation (OR) |
- String start ^ and finish $
- Multiline mode, flag "m"
- Lookahead (in progress)
- Infinite backtracking problem
- Introduction: callbacks
- Promise
- Promises chaining
- Promise API
- Async/await