Data types Flashcards
Which is heavier- objecs or primitives ?
Objects are “heavier” than primitives. They require additional resources to support the internal machinery.
Are functions in JS objects ?
Yes
Are primitives like string, number objects ? because we can use methods like str.toUpperCase() ?
Primitives are not objects
let str = “Hello”;
alert( str.toUpperCase() ); // HELLO
As we know primitives are not objects still we are able to call toUpperCase() on str. How this code works ?
Simple, right? Here’s what actually happens in str.toUpperCase():
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().
That method runs and returns a new string (shown by alert).
The special object is destroyed, leaving the primitive str alone.
So primitives can provide methods, but they still remain lightweight.
The JavaScript engine highly optimizes this process. It may even skip the creation of the extra object at all. But it must still adhere to the specification and behave as if it creates one.
Consider the following code:
let str = “Hello”;
str.test = 5;
alert(str.test);
How do you think, will it work? What will be shown?
let str = “Hello”;
str.test = 5; // (*)
alert(str.test);
Depending on whether you have use strict or not, the result may be:
undefined (no strict mode) An error (strict mode). Why? Let’s replay what’s happening at line (*):
When a property of str is accessed, a “wrapper object” is created.
In strict mode, writing into it is an error.
Otherwise, the operation with the property is carried on, the object gets the test property, but after that the “wrapper object” disappears, so in the last line str has no trace of the property.
This example clearly shows that primitives are not objects.
They can’t store additional data.
Do null/undefined also have some methods that we can call ?
The special primitives null and undefined are exceptions. They have no corresponding “wrapper objects” and provide no methods. In a sense, they are “the most primitive”.
An attempt to access a property of such value would give the error:
alert(null.test); // error
What are the two types of numbers in JS ?
In modern JavaScript, there are two types of numbers:
Regular numbers in JavaScript are stored in 64-bit format IEEE-754, also known as “double precision floating point numbers”. These are numbers that we’re using most of the time, and we’ll talk about them in this chapter.
BigInt numbers, to represent integers of arbitrary length. They are sometimes needed, because a regular number can’t safely exceed 2^53 or be less than -2^53. As bigints are used in few special areas, we devote them a special chapter BigInt.
What are the two ways of writing 1 billion ?
let billion = 1000000000; We also can use underscore _ as the separator:
let billion = 1_000_000_000;
Here the underscore _ plays the role of the “syntactic sugar”, it makes the number more readable. The JavaScript engine simply ignores _ between digits, so it’s exactly the same one billion as above.
In real life though, we try to avoid writing long sequences of zeroes. We’re too lazy for that. We’ll try to write something like “1bn” for a billion or “7.3bn” for 7 billion 300 million. The same is true for most large numbers.
How to use ‘e’ for writing large numbers ?
let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes
alert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000)
In other words, e multiplies the number by 1 with the given zeroes count.
1e3 === 1 * 1000; // e3 means *1000
1.23e6 === 1.23 * 1000000; // e6 means *1000000
How to write very small numbers in JS ?
Now let’s write something very small. Say, 1 microsecond (one millionth of a second):
let mсs = 0.000001;
Just like before, using “e” can help. If we’d like to avoid writing the zeroes explicitly, we could write the same as:
let mcs = 1e-6; // six zeroes to the left from 1 If we count the zeroes in 0.000001, there are 6 of them. So naturally it’s 1e-6.
In other words, a negative number after “e” means a division by 1 with the given number of zeroes:
// -3 divides by 1 with 3 zeroes 1e-3 === 1 / 1000; // 0.001
// -6 divides by 1 with 6 zeroes 1.23e-6 === 1.23 / 1000000; // 0.00000123
How to write Hex, binary and octal numbers in JS ?
Hexadecimal numbers are widely used in JavaScript to represent colors, encode characters, and for many other things. So naturally, there exists a shorter way to write them: 0x and then the number.
For instance:
alert( 0xff ); // 255
alert( 0xFF ); // 255 (the same, case doesn’t matter)
Binary and octal numeral systems are rarely used, but also supported using the 0b and 0o prefixes:
let a = 0b11111111; // binary form of 255 let b = 0o377; // octal form of 255
alert( a == b ); // true, the same number 255 at both sides
There are only 3 numeral systems with such support. For other numeral systems, we should use the function parseInt (which we will see later in this chapter).
How does toString(base) works ?
The method num.toString(base) returns a string representation of num in the numeral system with the given base.
For example:
let num = 255;
alert( num.toString(16) ); // ff
alert( num.toString(2) ); // 11111111
The base can vary from 2 to 36. By default it’s 10.
Common use cases for this are:
base=16 is used for hex colors, character encodings etc, digits can be 0..9 or A..F.
base=2 is mostly for debugging bitwise operations, digits can be 0 or 1.
base=36 is the maximum, digits can be 0..9 or A..Z. The whole latin alphabet is used to represent a number. A funny, but useful case for 36 is when we need to turn a long numeric identifier into something shorter, for example to make a short url. Can simply represent it in the numeral system with base 36:
alert( 123456..toString(36) ); // 2n9c
Please note that two dots in 123456..toString(36) is not a typo. 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.
If we placed a single dot: 123456.toString(36), then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now goes the method.
Also could write (123456).toString(36).
Round
How we do rounding in JS. List four methods of doing so as - Math.floor, Math.ciel, Math.round, Math.trunc ?
How to round the digits to nth place after decimal ?
The method toFixed(n) rounds the number to n digits after the point and returns a string representation of the result.
let num = 12.34; alert( num.toFixed(1) ); // "12.3" This rounds up or down to the nearest value, similar to Math.round:
let num = 12.36; alert( num.toFixed(1) ); // "12.4"
Advantages of backticks ?
allow us to embed any expression into the string, by wrapping it in ${…}:
function sum(a, b) { return a + b; }
alert(1 + 2 = ${sum(1, 2)}.
); // 1 + 2 = 3.
Another advantage of using backticks is that they allow a string to span multiple lines:
let guestList = `Guests: * John * Pete * Mary `;
alert(guestList); // a list of guests, multiple lines
Are strings immutable in JS ?
Yes
How to change the case of strings ?
Methods toLowerCase() and toUpperCase() change the case:
alert( ‘Interface’.toUpperCase() ); // INTERFACE
alert( ‘Interface’.toLowerCase() ); // interface
Or, if we want a single character lowercased:
alert( ‘Interface’[0].toLowerCase() ); // ‘i’
How the str.indexOf() works ?
The first method is str.indexOf(substr, pos).
It looks for the substr in str, starting from the given position pos, and returns the position where the match was found or -1 if nothing can be found.
For instance:
let str = ‘Widget with id’;
alert( str.indexOf(‘Widget’) ); // 0, because ‘Widget’ is found at the beginning
alert( str.indexOf(‘widget’) ); // -1, not found, the search is case-sensitive
alert( str.indexOf(“id”) ); // 1, “id” is found at the position 1 (..idget with id)
The optional second parameter allows us to start searching from a given position.
For instance, the first occurrence of “id” is at position 1. To look for the next occurrence, let’s start the search from position 2:
let str = ‘Widget with id’;
alert( str.indexOf(‘id’, 2) ) // 12
How the includes() works ?
The more modern method str.includes(substr, pos) returns true/false depending on whether str contains substr within.
It’s the right choice if we need to test for the match, but don’t need its position:
alert( “Widget with id”.includes(“Widget”) ); // true
alert( “Hello”.includes(“Bye”) ); // false
The optional second argument of str.includes is the position to start searching from:
alert( “Widget”.includes(“id”) ); // true
alert( “Widget”.includes(“id”, 3) ); // false, from position 3 there is no “id”
How startsWith() and endWith() works ?
The methods str.startsWith and str.endsWith do exactly what they say:
alert( “Widget”.startsWith(“Wid”) ); // true, “Widget” starts with “Wid”
alert( “Widget”.endsWith(“get”) ); // true, “Widget” ends with “get”
let str = “stringify”;
alert( str.slice(0, 5) );
alert( str.slice(0, 1) );
How it works ?
let str = “stringify”;
alert( str.slice(0, 5) ); // ‘strin’, the substring from 0 to 5 (not including 5)
alert( str.slice(0, 1) ); // ‘s’, from 0 to 1, but not including 1, so only character at 0
If there is n
str.slice(start [, end])
Returns the part of the string from start to (but not including) end.
F
let str = "stringify"; alert( str.slice(2) ); // Output ?
let str = “stringify”;
alert( str.slice(2) ); // ‘ringify’, from the 2nd position till the end
If there is no second argument, then slice goes till the end of the string:
let str = “stringify”;
alert( str.slice(-4, -1) ); // Output ?
Negative values for start/end are also possible. They mean the position is counted from the string end:
let str = “stringify”;
// start at the 4th position from the right, end at the 1st from the right alert( str.slice(-4, -1) ); // 'gif'
let str = “stringify”;
alert( str.substring(2, 6) );
alert( str.substring(6, 2) );
alert( str.slice(2, 6) );
alert( str.slice(6, 2) );
str.substring(start [, end])
Returns the part of the string between start and end.
This is almost the same as slice, but it allows start to be greater than end.
For instance:
let str = “stringify”;
// these are same for substring
alert( str.substring(2, 6) ); // “ring”
alert( str.substring(6, 2) ); // “ring”
// …but not for slice:
alert( str.slice(2, 6) ); // “ring” (the same)
alert( str.slice(6, 2) ); // “” (an empty string)
Negative arguments are (unlike slice) not supported, they are treated as 0.
let str = "stringify"; alert( str.substr(2, 4) ); // 'ring', from the 2nd position get 4 characters
let str = "stringify"; alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 characters
str.substr(start [, length])
Returns the part of the string from start, with the given length.
In contrast with the previous methods, this one allows us to specify the length instead of the ending position:
let str = "stringify"; alert( str.substr(2, 4) ); // 'ring', from the 2nd position get 4 characters The first argument may be negative, to count from the end:
let str = "stringify"; alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 characters
Can we store objects and functions in a array ?
An array can store elements of any type.
For instance:
// mix of values let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ];
// get the object at index 1 and then show its name alert( arr[1].name ); // John
// get the function at index 3 and run it arr[3](); // hello
What are two methods that work at the end of the array for insertion and removal of elements ?
Methods that work with the end of the array:
pop
Extracts the last element of the array and returns it:
let fruits = [“Apple”, “Orange”, “Pear”];
alert( fruits.pop() ); // remove “Pear” and alert it
alert( fruits ); // Apple, Orange
push
Append the element to the end of the array:
let fruits = [“Apple”, “Orange”];
fruits.push(“Pear”);
alert( fruits ); // Apple, Orange, Pear
The call fruits.push(…) is equal to fruits[fruits.length] = ….
How shift method works ?
shift
Extracts the first element of the array and returns it:
let fruits = [“Apple”, “Orange”, “Pear”];
alert( fruits.shift() ); // remove Apple and alert it
alert( fruits ); // Orange, Pear
How unshift method works ?
unshift
Add the element to the beginning of the array:
let fruits = [“Orange”, “Pear”];
fruits.unshift(‘Apple’);
alert( fruits ); // Apple, Orange, Pear
Can we add multiple elements at once using push and unshift methods ?
Methods push and unshift can add multiple elements at once:
let fruits = [“Apple”];
fruits. push(“Orange”, “Peach”);
fruits. unshift(“Pineapple”, “Lemon”);
// ["Pineapple", "Lemon", "Apple", "Orange", "Peach"] alert( fruits );
Is array an object ?
An array is a special kind of object. The square brackets used to access a property arr[0] actually come from the object syntax. That’s essentially the same as obj[key], where arr is the object, while numbers are used as keys.
Since, arrays are objects, Can we treat it like a object and do all the things that we do with objects ?
NO. We should use arrays as intended. JS engine will know if we treat an array as an object and will stop the operations.
what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast.
But they all break if we quit working with an array as with an “ordered collection” and start working with it as if it were a regular object.
For instance, technically we can do this:
let fruits = []; // make an array
fruits[99999] = 5; // assign a property with the index far greater than its length
fruits.age = 25; // create a property with an arbitrary name
That’s possible, because arrays are objects at their base. We can add any properties to them.
But the engine will see that we’re working with the array as with a regular object. Array-specific optimizations are not suited for such cases and will be turned off, their benefits disappear.
The ways to misuse an array:
Add a non-numeric property like arr.test = 5.
Make holes, like: add arr[0] and then arr[1000] (and nothing between them).
Fill the array in the reverse order, like arr[1000], arr[999] and so on.
Please think of arrays as special structures to work with the ordered data. They provide special methods for that. Arrays are carefully tuned inside JavaScript engines to work with contiguous ordered data, please use them this way. And if you need arbitrary keys, chances are high that you actually require a regular object {}.
How for..of loop works on arrays ?
But for arrays there is another form of loop, for..of:
let fruits = [“Apple”, “Orange”, “Plum”];
// iterates over array elements for (let fruit of fruits) { alert( fruit ); } The for..of doesn’t give access to the number of the current element, just its value, but in most cases that’s enough. And it’s shorter.
Can we use for..in loop on arrays ? since, it is like an object.
Technically, because arrays are objects, it is also possible to use for..in:
let arr = [“Apple”, “Orange”, “Pear”];
for (let key in arr) {
alert( arr[key] ); // Apple, Orange, Pear
}
But that’s actually a bad idea. There are potential problems with it:
The loop for..in iterates over all properties, not only the numeric ones.
There are so-called “array-like” objects in the browser and in other environments, that look like arrays. That is, they have length and indexes properties, but they may also have other non-numeric properties and methods, which we usually don’t need. The for..in loop will list them though. So if we need to work with array-like objects, then these “extra” properties can become a problem.
The for..in loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it’s still very fast. The speedup may only matter in bottlenecks. But still we should be aware of the difference.
Generally, we shouldn’t use for..in for arrays
let fruits = []; fruits[123] = "Apple";
alert( fruits.length ); // Output ?
The length property automatically updates when we modify the array. To be precise, it is actually not the count of values in the array, but the greatest numeric index plus one.
For instance, a single element with a large index gives a big length:
let fruits = []; fruits[123] = "Apple";
alert( fruits.length ); // 124
length property is writable. what does that mean ?
Another interesting thing about the length property is that it’s writable.
If we increase it manually, nothing interesting happens. But if we decrease it, the array is truncated. The process is irreversible, here’s the example:
let arr = [1, 2, 3, 4, 5];
arr.length = 2; // truncate to 2 elements
alert( arr ); // [1, 2]
arr.length = 5; // return length back
alert( arr[3] ); // undefined: the values do not return
So, the simplest way to clear the array is: arr.length = 0;
let arr = new Array(2); // will it create an array of [2] ?
alert( arr[0] ); // undefined! no elements.
alert( arr.length ); // length 2
Why undefined is coming in the above output ?
There is one more syntax to create an array:
let arr = new Array("Apple", "Pear", "etc"); It’s rarely used, because square brackets [] are shorter. Also there’s a tricky feature with it.
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.
Let’s see how one can shoot themself in the foot:
let arr = new Array(2); // will it create an array of [2] ?
alert( arr[0] ); // undefined! no elements.
alert( arr.length ); // length 2
To avoid such surprises, we usually use square brackets, unless we really know what we’re doing.
let arr = [1, 2, 3];
alert( arr ); // 1,2,3
alert( String(arr) === ‘1,2,3’ ); // Output ?
Arrays have their own implementation of toString method that returns a comma-separated list of elements.
For instance:
let arr = [1, 2, 3];
alert( arr ); // 1,2,3
alert( String(arr) === ‘1,2,3’ ); // true
Also, let’s try this:
alert( [] + 1 ); // “1”
alert( [1] + 1 ); // “11”
alert( [1,2] + 1 ); // “1,21”
Arrays do not have Symbol.toPrimitive, neither a viable valueOf, they implement only toString conversion, so here [] becomes an empty string, [1] becomes “1” and [1,2] becomes “1,2”.
When the binary plus “+” operator adds something to a string, it converts it to a string as well, so the next step looks like this:
alert( “” + 1 ); // “1”
alert( “1” + 1 ); // “11”
alert( “1,2” + 1 ); // “1,21”
Should we compare arrays with == or < or > ?
To compare arrays, don’t use the == operator (as well as >, < and others), as they have no special treatment for arrays. They handle them as any objects, and it’s not what we usually want.
Instead you can use for..of loop to compare arrays item-by-item.
let arr = [“I”, “go”, “home”];
delete arr[1]; // remove “go”
alert( arr[1] ); // Output ?
// now arr = ["I", , "home"]; alert( arr.length ); // Output ?
How to delete an element from the array?
The arrays are objects, so we can try to use delete:
let arr = [“I”, “go”, “home”];
delete arr[1]; // remove “go”
alert( arr[1] ); // undefined
// now arr = [“I”, , “home”];
alert( arr.length ); // 3
The element was removed, but the array still has 3 elements, we can see that arr.length == 3.
That’s natural, because delete obj.key removes a value by the key. It’s all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now.
let arr = [“I”, “study”, “JavaScript”];
arr.splice(1, 1);
alert( arr ); // Output ?
let arr = [“I”, “study”, “JavaScript”];
arr.splice(1, 1); // from index 1 remove 1 element
alert( arr ); // [“I”, “JavaScript”]
let arr = [“I”, “study”, “JavaScript”, “right”, “now”];
arr.splice(0, 3, “Let’s”, “dance”);
alert( arr ) // Output ?
let arr = [“I”, “study”, “JavaScript”, “right”, “now”];
// remove 3 first elements and replace them with another arr.splice(0, 3, "Let's", "dance");
alert( arr ) // now [“Let’s”, “dance”, “right”, “now”]
let arr = [“I”, “study”, “JavaScript”, “right”, “now”];
let removed = arr.splice(0, 2);
alert( removed ); // Output ?
let arr = [“I”, “study”, “JavaScript”, “right”, “now”];
// remove 2 first elements let removed = arr.splice(0, 2);
alert( removed ); // “I”, “study”
let arr = [“I”, “study”, “JavaScript”];
arr.splice(2, 0, “complex”, “language”);
alert( arr ); // Output ?
let arr = [“I”, “study”, “JavaScript”];
// from index 2 // delete 0 // then insert "complex" and "language" arr.splice(2, 0, "complex", "language");
alert( arr ); // “I”, “study”, “complex”, “language”, “JavaScript”
The splice method is also able to insert the elements without any removals. For that we need to set deleteCount to 0:
let arr = [1, 2, 5];
arr.splice(-1, 0, 3, 4);
alert( arr ); // Output ?
let arr = [1, 2, 5];
// from index -1 (one step from the end) // delete 0 elements, // then insert 3 and 4 arr.splice(-1, 0, 3, 4);
alert( arr ); // 1,2,3,4,5
Here and in other array methods, negative indexes are allowed. They specify the position from the end of the array, like here:
Write the syntax for slice method
The method arr.slice is much simpler than similar-looking arr.splice.
The syntax is:
arr.slice([start], [end])
It returns a new array copying to it all items from index start to end (not including end). Both start and end can be negative, in that case position from array end is assumed.
It’s similar to a string method str.slice, but instead of substrings it makes subarrays.
let arr = [“t”, “e”, “s”, “t”];
alert( arr.slice(1, 3) );
alert( arr.slice(-2) ); //Output ?
let arr = [“t”, “e”, “s”, “t”];
alert( arr.slice(1, 3) ); // e,s (copy from 1 to 3)
alert( arr.slice(-2) ); // s,t (copy from -2 till the end)
We can also call it without arguments: arr.slice() creates a copy of arr. That’s often used to obtain a copy for further transformations that should not affect the original array.
Write the syntax for arr.concat method
The method arr.concat creates a new array that includes values from other arrays and additional items.
The syntax is:
arr.concat(arg1, arg2…)
It accepts any number of arguments – either arrays or values.
The result is a new array containing items from arr, then arg1, arg2 etc.
If an argument argN is an array, then all its elements are copied. Otherwise, the argument itself is copied.
let arr = [1, 2];
alert( arr.concat([3, 4]) ); // 1,2,3,4
alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6
alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6
let arr = [1, 2];
// create an array from: arr and [3,4] alert( arr.concat([3, 4]) ); // 1,2,3,4
// create an array from: arr and [3,4] and [5,6] alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6
// create an array from: arr and [3,4], then add values 5 and 6
alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6
Normally, it
let arr = [1, 2];
let arrayLike = { 0: "something", length: 1 };
alert( arr.concat(arrayLike) ); //Output ?
let arr = [1, 2];
let arrayLike = { 0: "something", length: 1 };
alert( arr.concat(arrayLike) ); // 1,2,[object Object]
Normally, it only copies elements from arrays. Other objects, even if they look like arrays, are added as a whole:
let arr = [1, 2];
let arrayLike = { 0: "something", 1: "else", [Symbol.isConcatSpreadable]: true, length: 2 };
alert( arr.concat(arrayLike) ); // Output ?
let arr = [1, 2];
let arrayLike = { 0: "something", 1: "else", [Symbol.isConcatSpreadable]: true, length: 2 };
alert( arr.concat(arrayLike) ); // 1,2,something,else
if an array-like object has a special Symbol.isConcatSpreadable property, then it’s treated as an array by concat: its elements are added instead:
[“Bilbo”, “Gandalf”, “Nazgul”].forEach(alert);
How this works ?
alert popup will be shown for each element in the array
[“Bilbo”, “Gandalf”, “Nazgul”].forEach((item, index, array) => {
alert(${item} is at index ${index} in ${array}
);
});
Output ?
It will be like Bilbo is at index 0 in Bilbo, Gandalf, Nazgul
How arr.indexOf, arr.lastIndexOf and arr.includes works ?
The methods arr.indexOf, arr.lastIndexOf and arr.includes have the same syntax and do essentially the same as their string counterparts, but operate on items instead of characters:
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.
let arr = [1, 0, false];
alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1
alert( arr.includes(1) ); // Output ?
let arr = [1, 0, false];
alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1
alert( arr.includes(1) ); // true
arr.indexOf, arr.lastIndexOf and arr.includes uses ===. What does that mean ?
Note that the methods use === comparison. So, if we look for false, it finds exactly false and not the zero.
If we want to check for inclusion, and don’t want to know the exact index, then arr.includes is preferred.
Also, a very minor difference of includes is that it correctly handles NaN, unlike indexOf/lastIndexOf:
const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1 (should be 0, but === equality doesn't work for NaN) alert( arr.includes(NaN) );// true (correct)
Imagine we have an array of objects. How do we find an object with the specific condition?
Here the arr.find(fn) method comes in handy.
The syntax is:
let result = arr.find(function(item, index, array) { // if true is returned, item is returned and iteration is stopped // for falsy scenario returns undefined }); The function is called for elements of the array, one after another:
item is the element.
index is its index.
array is the array itself.
If it returns true, the search is stopped, the item is returned. If nothing found, undefined is returned.