Chapter 5: Data types

5.1 Methods of primitives

  • Primitives except null and undefined provide many helpful methods.

  • Formally, these methods work via temporary objects, but JavaScript engines are well tuned to optimize that internally, so they are not expensive to call.

A primitive as an object

  • There are many things one would want to do with a primitive like a string or a number. It would be great to access them as methods.

  • Primitives must be as fast and lightweight as possible.

  • Primitives are still primitive. A single value, as desired.

  • The language allows access to methods and properties of strings, numbers, booleans and symbols.

  • In order for that to work, a special "object wrapper" that provides the extra functionality is created, and then is destroyed.

let str = "Hello";

alert(str.toUpperCase()); // HELLO
  1. The string str is a primitive. So in the moment of accessing its property, a special object is created that knows the value of the string, and has useful methods, like toUpperCase().

  2. That method runs and returns a new string (shown by alert).

  3. The special object is destroyed, leaving the primitive str alone.

Don't use constructors like String, Number, Boolean:

  • Objects are always truthy in if.

  • null/undefined have no methods.

  • Permitives (temporary objects) can't store additional data. (Error in strict mode, undefined in regular mode.)

5.2 Numbers

  • Double precision floating point numbers

  • BigInt numbers

More ways to write a number

Hex, binary and octal numbers

toString(base)

The method num.toString(base) returns a string representation of num in the numeral system with the given base.

If we want to call a method directly on a number, like toString in the example above, then we need to place two dots .. after it or wrap it in a parentheses.

Rounding

  • Math.floor (Rounds down)

  • Math.ceil (Rounds up)

  • Math.round (Nearst integer)

  • Math.trunc (Remove anything after the decimal point)

To round a number into n-th digits:

  1. Multiply, floor, and divide

  2. toFixed(n), which returns a string (padding with zeros if necessary)

Imprecise calculations

Number: 52 of them are used to store the digits, 11 of them store the position of the decimal point (they are zero for integer numbers), and 1 bit is for the sign.

Integer overflow

Loss of precision

Use toFixed(n) to solve this problem.

Tests: isFinite and isNaN

  • Infinity(and -Infinity) is a special numeric value that is greater (less) than anything.

  • NaN represents an error.

  • isNaN(value) converts its argument to a number and then tests it for being NaN.

  • The value of NaN is unique, and it does not equal anything, including itself.

  • isFinite(value) converts its argument to a number and returns true if it’s a regular number, not NaN/Infinity/-Infinity.

  • Sometimes isFinite is used to validate whether a string value is a regular number. (An empty or a space-only string is treated as 0.)

parseInt and parseFloat

They "read" a number from a string until they can’t. In case of an error, the gathered number is returned.

The second argument of parseInt() is used to specifies the base of the numeral system.

Other math functions

  • Math.random(): returns a random number from 0 to 1 (not including 1)

  • Math.max(), Math.min()

  • Math.pow()

5.3 Strings

  • Strings can be enclosed within either single quotes, double quotes, or backticks.

  • It is still possible to create multiline strings with single and double quotes by using a so-called “newline character”, written as \n, which denotes a line break.

  • The length property has the string length.

  • To get a character at position pos, use square brackets [pos] or call the method str.charAt(pos). Square brackets return undefind, but charAt returns an empty string.

  • We can also iterate over characters using for..of.

Strings can’t be changed in JavaScript and it is impossible to change a character. The usual workaround is to create a whole new string and assign it to str.

Methods toLowerCase() and toUpperCase() change the case.

Searching for a substring

  • str.indexOf(substr, pos) (pos is the starting position)

  • str.lastIndexOf(substr, position)

  • if (~str.indexOf(...)) reads as "if found". (Ancient Bitwise NOT trick)

  • includes, startsWith, endsWith

Getting a substring

  • str.slice(start [, end])

  • str.substring(start [, end]) (start could be greater than end)

  • str.substr(start [, length])

  • str.trim() – removes (“trims”) spaces from the beginning and end of the string.

  • str.repeat(n) – repeats the string n times.

Comparing Strings

  • str.codePointAt(pos) (z -> 122)

  • str.fromCodePoint(code) (122 -> z, \u005a -> z)

The characters are compared by their numeric code. (Ö > a > Z)

Use str.localeCompare(str2) to correctly handle all cases:

Internals, Unicode

Surrogate pairs: Rare symbols are encoded with a pair of 2-byte characters called a surrogate pair.

If a character has the code in the interval of 0xd800..0xdbff, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval 0xdc00..0xdfff.

UTF-16 allows us to use several unicode characters: the base character followed by one or many "mark" characters that "decorate" it.

"S\u0307\u0323".normalize() will bring that into one unicode: (\u1e68)

5.4 Arrays

Declaration

The total count of the elements in the array is its length property.

Methods pop/push, shift/unshift

  • push adds an element to the end.

  • pop takes an element from the end.

  • unshift adds an element to the beginning.

  • shift takes an element from the beginning.

Internals

Array is an object and thus behaves like an object. It is copied by reference. If we add propertie to the array, the engine will disable all of its array-specific optimizations.

Methods push/pop run fast O(1), while shift/unshift are slow O(N).

Loops

  • The loop for..in iterates over all properties, not only the numeric ones.

  • The for..in loop is optimized for generic objects, not arrays, and thus is 10-100 times slower.

A word about "length"

It's not the count of values in the array, but the greatest numeric index plus one, and it's writable. If we decrease it, the array is truncated.

new Array()

If new Array is called with a single argument which is a number, then it creates an array without items, but with the given length. new Array(number) has all elements undefined:

toString

toString method of arrays returns a comma-separated list of elements.

5.5 Array methods

splice

It starts from the position index: removes deleteCount elements and then inserts elem1, ..., elemN at their place. Returns the array of removed elements.

concat

If an array-like object has a special Symbol.isConcatSpreadable property, then it’s treated as an array by concat.

Iterate: forEach

The arr.forEach method allows to run a function for every element of the array.

indexOf/lastIndexOf and includes

  • arr.indexOf(item, from) – looks for item starting from index from, and returns the index where it was found, otherwise -1.

  • arr.lastIndexOf(item, from) – same, but looks for from right to left.

  • arr.includes(item, from) – looks for item starting from index from, returns true if found. (correctly handles NaN)

find and findIndex

findIndex returns the index or -1 instead of the element itself.

filter

The find method looks for a single (first) element that makes the function return true.

map

It calls the function for each element of the array and returns the array of results.

sort(fn)

The items are sorted as strings by default.

reverse

The method arr.reverse reverses the order of elements in arr.

split and join

Split into letters:

reduce/reduceRight

If there’s no initial, then reduce takes the first element of the array as the initial value and starts the iteration from the 2nd element.

If the array is empty, then reduce call without initial value gives an error.

The method arr.reduceRight does the same, but goes from right to left.

Array.isArray

It returns true if the value is an array, and false otherwise.

thisArg

The value of thisArg parameter becomes this for func.

5.6 Iterables

Iterable objects is a generalization of arrays.

Symbol.iterator

To make for...of work, we need to add a method to the Symbol.iterator.

  • The method must return an iterator – an object with the method next.

  • When for..of wants the next value, it calls next() on that object.

  • The result of next() must have the form {done: Boolean, value: any}

Calling an iterator explicitly

Iterables and array-likes

  • Iterables are objects that implement the Symbol.iterator method, as described above.

  • Array-likes are objects that have indexes and length, so they look like arrays.

Strings are both iterable (for..of works on them) and array-like (they have numeric indexes and length).

Array.from

Array.from takes an iterable or array-like value and makes a “real” Array from it.

5.7 Map and Set

Map

Map is a collection of keyed data items, just like an Object. Map allows keys of any type, such as objects or NaN.

  • 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 if key doesn’t exist in map.

  • map.has(key) – returns true if the key exists, - `false otherwise.

  • map.delete(key) – removes the value by the key.

  • map.clear() – removes everything from the map.

  • map.size – returns the current element count.

Although map[key] also works, this is treating map as a plain JavaScript object, so it implies all corresponding limitations.

Iteration over Map

  • map.keys() – returns an iterable for keys,

  • map.values() – returns an iterable for values,

  • map.entries() – returns an iterable for entries [key, value], it’s used by default in for..of.

The iteration goes in the same order as the values were inserted. Map also has a built-in forEach method.

Object.entries: Map from Object

Object.fromEntries: Object from Map

Set

A Set is a special type collection – “set of values” (without keys), where each value may occur only once.

  • new Set(iterable) – creates the set, and if an iterable object is provided (usually an array), copies values from it into the set.

  • 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.

Iteration over Set

  • set.keys() – returns an iterable object for values,

  • set.values() – same as set.keys(), for compatibility with Map,

  • set.entries() – returns an iterable object for entries [value, value], exists for compatibility with Map.

5.8 WeakMap and WeakSet

WeakMap doesn’t prevent garbage-collection of key objects.

WeakMap

WeakMap keys must be objects, not primitive values.

If there are no other references to that key, it will be removed from memory and from the map automatically.

WeakMap does not support iteration and methods keys(), values(), entries(), since it’s not exactly specified when the cleanup happens.

  • weakMap.get(key)

  • weakMap.set(key, value)

  • weakMap.delete(key)

  • weakMap.has(key)

Use case

additional data

If we’re working with an object that belongs to another code and would like to store some data associated with it, that should only exist while the object is alive, then WeakMap is exactly what’s needed.

For instance, we have code that keeps a visit count for users. When a user leaves (its object gets garbage collected), we don’t want to store their visit count anymore.

caching

When a function result should be remembered (“cached”), so that future calls on the same object reuse it.

WeakSet

It is analogous to Set, but we may only add objects to WeakSet (not primitives).

An object exists in the set while it is reachable from somewhere else.

Like Set, it supports add, has and delete, but not size, keys() and no iterations.

5.9 Object.keys, values, entries

  • 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.

These methods ignore properties that use Symbol(...) as keys.

Transforming objects

To use map, filter or other Array-specific methods on an object, transform it to an array and turn it back.

5.10 Destructuring assignment

Destructuring assignment is a special syntax that allows us to “unpack” arrays, iterables, or objects into a bunch of variables.

Array destructuring

Works with any iterable on the right-side

Looping with .entries()

Swap varaibles trick

The rest ‘…’

Default values

Object destructuring

The rest pattern “…”

Nested destructuring

Smart function parameters

5.11 Date and time

Creation

  • new Date(): create a Date object for the current date and time.

  • new Date(milliseconds): create a Date object with the time equal to number of millisecondsa Date object with the time equal to number of milliseconds. (Dates before 01.01.1970 have negative timestamps)

  • new Date(datestring): date strings are parsed automatically.

  • new Date(year, month, date, hours, minutes, seconds, ms): Only the first two arguments are obligatory.

Access date components

  • getFullYear(): 4 digits

  • getMonth(): From 0 to 11

  • getDate(): From 1 to 31

  • getHours(), getMinutes(), getSeconds(), getMilliseconds()

  • getDay(): From 0 (Sunday) to 6 (Saturday).

All the methods above return the components relative to the local time zone.

  • getTime(): Returns the timestamp for the date.

  • getTimezoneOffset(): Returns the difference between UTC and the local time zone, in minutes.

Autocorrection

We can set out-of-range values, and the Date object will auto-adjust itself. Out-of-range date components are distributed automatically.

Date to number, date diff

Dates can be subtracted, giving their difference in milliseconds. Dates becomes the timestamp when converted to a number.

Date.now()

There’s a special method Date.now() that returns the current timestamp. It doesn’t create an intermediate Date object.

Benchmarking

For more reliable benchmarking, the whole pack of benchmarks should be rerun multiple times.

Modern JavaScript engines start applying advanced optimizations only to “hot code” that executes many times.

Date.parse from a string

Date.parse(str) parses the string in the given format and returns the timestamp or NaN.

The string format should be: YYYY-MM-DDTHH:mm:ss.sssZ, where:

  • YYYY-MM-DD – is the date: year-month-day.

  • The character "T" is used as the delimiter.

  • HH:mm:ss.sss – is the time: hours, minutes, seconds and milliseconds.

  • The optional 'Z' part denotes the time zone in the format +-hh:mm. A single letter Z that would mean UTC+0.

5.12 JSON methods, toJSON

JSON.stringify

JSON supports following data types:

  • Objects { ... }

  • Arrays [ ... ]

  • Primitives:

    • strings,

    • numbers,

    • boolean values true/false,

    • null.

Function, Symbol, and Properties that are undefined are skipped by JSON.stringify. There must be no circular references.

Excluding and transforming: replacer

  • value: A value to encode.

  • replacer: Array of properties to encode or a mapping function function(key, value).

  • space: Amount of space to use for formatting.

Formatting: space

For example, space = 4 tells JavaScript to show nested objects on multiple lines, with indentation of 4 spaces inside an object.

Custom “toJSON”

JSON.parse

  • str: JSON-string to parse.

  • reviver: Optional function(key,value) that will be called for each (key, value) pair and can transform the value.

Using reviver

Last updated