Expressive JavaScript: Data Structures: Objects and Arrays

Original author: Marijn Haverbeke
  • Transfer

Content




They asked me twice: “Tell me, Mr. Babbage, and if you enter the wrong data into the machine, will you get the right answer?” The incomprehensibility of the confusion in the head that leads to such questions.

Charles Babbage, “Excerpts from the Life of a Philosopher” (1864)


Numbers, Boolean values, and strings are the bricks from which data structures are built. But you can not make a house of one brick. Objects allow us to group values ​​(including other objects) together - and build more complex structures.

Writing programs that we have been working on so far has been greatly hindered by the fact that they only worked with simple data. This chapter will add to your toolkit understanding of data structures. By the end, you will know enough to start writing useful programs.

The chapter will go over a more or less realistic programming example, introducing concepts as needed. The code for the examples will be built from the functions and variables that we defined earlier.

Werewolf squirrel


Sometimes, usually between eight and ten in the evening, Jacques against his will turns into a small rodent with a furry tail.

On the one hand, Jacques is glad that he does not turn into a classic wolf. Turning into a protein entails fewer problems. Instead of worrying about whether you would eat a neighbor (that would be embarrassing), he worries about being eaten by a neighbor's cat. After he woke up twice on a very thin branch in the crown of an oak tree, naked and disoriented, he learned to lock the windows and doors in his room for the night, and put a few nuts on the floor to keep himself busy.


This solves the problems with the cat and oak. But Jacques is still suffering from his illness. Irregular appeals make him think that they should be caused by something. At first he thought this only happened on the days when he touched the trees. He stopped doing this, and even began to avoid coming up to them. But the problem has not disappeared.

Moving to a more scientific approach, Jacques decided to keep a daily diary of everything he did, writing down whether he contacted the squirrel. So he hopes to narrow the circle of things leading to transformation.

First, he decided to develop a data structure to store this information.

Datasets

To work with a piece of data, we first need to find a way to represent them in the memory of the machine. For example, we need to remember a collection of numbers:

2, 3, 5, 7, 11


You can play with strings - strings can be of any length, you can put a lot of data in them, and use “2 3 5 7 11” to represent this set. But it is inconvenient. We will need to somehow extract numbers from there or insert new ones into a string.

Fortunately, JavaScript offers a data type specifically for storing sequences of numbers. It is called an array, and is written as a list of values ​​in square brackets, separated by commas:

var listOfNumbers = [2, 3, 5, 7, 11];
console.log(listOfNumbers[1]);
// → 3
console.log(listOfNumbers[1 - 1]);
// → 2


A record to get an element from an array also uses square brackets. A pair of brackets after the expression, containing another expression inside, will find in the array that is specified by the first expression, the element whose serial number is given by the second expression.

The number of the first element is zero, not one. Therefore, the first element can be obtained like this: listOfNumbers [0]. If you have not programmed before, you will have to get used to such numbering. But it has a long tradition, and all the while it is consistently observed, it works great.

The properties

We saw a lot of suspicious expressions like myString.length (getting the length of the string) and Math.max (getting the maximum) in the earlier examples. These expressions use the properties of quantities. In the first case, we get access to the length property of the myString variable. The second is access to the max property of the Math object (which is a set of functions and variables related to mathematics).

Almost all variables in JavaScript have properties. Exceptions are null and undefined. If you try to access non-existent properties of these non-quantities, you will get an error:

null.length;
// → TypeError: Cannot read property 'length' of null


The two main ways to access properties are dot and square brackets. value.x and value [x] access the value property - but not necessarily the same thing. The difference is how x is interpreted. When using a point, the record after the point must be the name of an existing variable, and it thus directly calls a property by name. When using square brackets, the expression in parentheses is evaluated to get the name of the property. value.x calls a property called “x”, and value [x] evaluates the expression x and uses the result as the name of the property.

If you know that the property you are interested in is called “length”, you write value.length. If you want to extract the property name from the variable i, you write value [i]. And since the property can have any name, to access the property by the name “2” or “Jon Doe” you have to use square brackets: value [2] or value [“John Doe”]. This is necessary even when you know the exact name of the property, because “2” or “John Doe” are not valid variable names, therefore they cannot be accessed by writing through a dot.

Array elements are stored in properties. Since the names of these properties are numbers, and we often have to get their names from the values ​​of variables, we need to use square brackets to access them. The length property of the array indicates how many elements are in it. The name of this property is a valid variable name, and we know it in advance, so we usually write array.length because it is easier than writing array [“length”].

Methods

The string and array objects contain, in addition to the length property, several properties that reference functions.

var doh = "Дык";
console.log(typeof doh.toUpperCase);
// → function
console.log(doh.toUpperCase());
// → ДЫК


Each row has a toUpperCase property. When called, it returns a copy of the string in which all letters are replaced with uppercase. There is also toLowerCase - you can guess what it does.

Interestingly, although the call toUpperCase does not pass any arguments, the function somehow gets access to the “Duc” line, the property of which we called. How this works is described in Chapter 6.

Properties containing functions are usually called methods of the variable to which they belong. That is, toUpperCase is a string method.

The following example demonstrates some of the methods that arrays have:

var mack = [];
mack.push("Трест,");
mack.push("который", "лопнул");
console.log(mack);
// → ["Трест,", "который", "лопнул"]
console.log(mack.join(" "));
// → Трест, который лопнул
console.log(mack.pop());
// → лопнул
console.log(mack);
// → ["Трест,", "который"]


The push method is used to add values ​​to the end of an array. pop does the opposite: removes the value from the end of the array and returns it. An array of strings can be flattened into a single string using the join method. As a join argument, pass the string that will be inserted between the elements of the array.

The objects

Back to our squirrel. A set of journal entries can be represented as an array. But the records do not consist only of numbers or lines - each should keep a list of what our hero did, and a Boolean value indicating whether Jacques turned into a squirrel. Ideally, we would like to group each of the records into a single variable, and then add them to the array.

Variables of type object (object) - a collection of arbitrary properties, and we can add and remove properties of the object as desired. One way to create an object is to use braces:

var day1 = {
  squirrel: false,
  events: ["работа", "тронул дерево", "пицца", "пробежка", "телевизор"]
};
console.log(day1.squirrel);
// → false
console.log(day1.wolf);
// → undefined
day1.wolf = false;
console.log(day1.wolf);
// → false


In parentheses we can specify a list of properties separated by commas. Each property is written as a name, followed by a colon, then comes the expression, which is the value of the property. Spaces and line breaks are not taken into account. By breaking the record of object properties into several lines, you improve the readability of the code. If the property name is not a valid variable name, you must enclose it in quotation marks:

var descriptions = {
  work: "Пошёл на работу",
  "тронул дерево": "Дотронулся до дерева"
};


It turns out that curly braces in JavaScript have two meanings. Used at the beginning of the instruction, they begin a new block of instructions. Elsewhere, they describe an object. Usually it makes no sense to start the instruction with a description of the object, and therefore in programs there is usually no ambiguity about these two uses of braces.

If you try to read the value of a nonexistent property, you get undefined - as in the example when we first tried to read the wolf property.

A property can be assigned a value through the = operator. If it previously had a value, it will be replaced. If the property was missing, it will be created.

Returning to our model with tentacles and variables, we see that the properties are also similar to them. They grab values, but other variables and properties may refer to the same values. Objects are octopuses with an arbitrary number of tentacles, on each of which is written the name of the property.


The delete operator cuts off the tentacle. This is a unary operator that applies to a property access expression. This is rarely done, but quite possible.

var anObject = {left: 1, right: 2};
console.log(anObject.left);
// → 1
delete anObject.left;
console.log(anObject.left);
// → undefined
console.log("left" in anObject);
// → false
console.log("right" in anObject);
// → true


The binary in operator takes a string and the name of the object, and returns a Boolean value indicating whether the object has a property with that name. There is a difference between setting the property value to undefined and deleting the property. In the first case, the property is stored in the object, it is just empty. In the second, there are no more properties, and then in returns false.

It turns out that arrays are a kind of objects that specialize in storing sequences. The expression typeof [1, 2] will return “object”. They can be considered as long flat octopuses, in which all the tentacles are located evenly and labeled with numbers.



Therefore, Jacques magazine can be represented as an array of objects:

var journal = [
  {events: ["работа", "тронул дерево", "пицца", "пробежка", "телевизор"],
   squirrel: false},
  {events: ["работа ", "мороженое", "цветная капуста", "лазанья", " тронул дерево ", "почистил зубы"],
   squirrel: false},
  {events: ["выходной", "велик", "перерыв", "арахис", "пивасик"],
   squirrel: true},
  /* и так далее... */
];


Mutability


Soon we will get to programming. In the meantime, we need to understand the last part of the theory.

We saw that the values ​​of the object can be changed. The types of values ​​that we examined earlier — numbers, strings, Boolean values ​​— are immutable. You cannot change the existing value of a given type. You can combine them and derive new values ​​from them, but when you work with some string value, this value remains constant. The text inside the line cannot be changed. If you have a link to the “cat” line, you cannot change the symbol in the code to make a “midge”.

But for objects, the content can be changed by changing the values ​​of their properties.

If we have two numbers, 120 and 120, we can consider them as one and the same, regardless of whether they are stored in memory in the same place. But when we deal with objects, it makes a difference whether we have two references to the same object or whether we have two different objects containing the same properties. Consider an example:

var object1 = {value: 10};
var object2 = object1;
var object3 = {value: 10};
console.log(object1 == object2);
// → true
console.log(object1 == object3);
// → false
object1.value = 15;
console.log(object2.value);
// → 15
console.log(object3.value);
// → 10


The variables object1 and object2 hold on to the same object, so changes to object1 lead to changes in object2. The variable object3 points to another object that initially contains the same properties as object1, but lives its own life.

When comparing objects, the == operator returns true only if the objects being compared are the same variable. Comparing different objects will return false, even if they have identical content. The “deep” comparison operator, which would compare the contents of objects, is not provided in JavaScript, but it can be done independently (this will be one of the exercises at the end of the chapter).

Werewolf Magazine


So, Jacques launches his favorite JavaScript interpreter and creates the environment needed to store the log.

var journal = [];
function addEntry(events, didITurnIntoASquirrel) {
  journal.push({
    events: events,
    squirrel: didITurnIntoASquirrel
  });
}


Every evening, at ten o’clock, and sometimes tomorrow morning, going down from the top shelf of the cabinet, he writes down his day.

addEntry(["работа", "тронул дерево", "пицца", "пробежка", "телевизор"], false);
addEntry(["работа ", "мороженое", "цветная капуста", "лазанья", " тронул дерево ", "почистил зубы"], false);
addEntry(["выходной", "велик", "перерыв", "арахис", "пивасик"], true);


Once he has enough data, he is going to calculate the correlation between his wraps and the events of each day, and ideally, to learn something useful from their correlations.

Correlation is a measure of the relationship between variables (variables in a statistical sense, not in the sense of JavaScript). It is usually expressed as a coefficient taking values ​​from -1 to 1. Zero correlation means that the variables are not connected at all, and correlation 1 means that they are completely connected - if you know one, you automatically know the other. Minus one also means a strong connection of variables, but also their opposite - when one is true, the second is always false.

The coefficient phi (ϕ) is well suited to measure the correlation of Boolean variables; moreover, it is relatively easy to calculate. To do this, we need a table containing the number of times when various combinations of two variables were observed. For example, we can take the events “ate pizza” and “appeal” and present them in the following table:


ϕ can be calculated using the following formula, where n refers to the cells of the table:


n01 indicates the number of measurements when the first event (pizza) is false (0 ), and the second event (call) is true (1). In our example, n01 = 4.

The record n1 • denotes the sum of all measurements where the first event was true, which for our example is 10. Accordingly, n • 0 is the sum of all measurements where the “call” event was false.

So, for a pizza table, the numerator of the formula will be 1 × 76 - 9 × 4 = 40, and the denominator is the root of 10 × 80 × 5 × 85, or √340000. It turns out that ϕ ≈ 0.069, which is rather small. Pizza does not seem to have any effect on protein.

Calculate the correlation

A 2x2 table can be represented as an array of four elements ([76, 9, 4, 1]), an array of two elements, each of which is also a two-element array ([76, 9], [4, 1]]), or an object with properties under the names “11” or “01”. But for us, a one-dimensional array is simpler and the expression for accessing it will be shorter. We will process the indices of the array as two-digit binary numbers, where the left sign denotes the turnover variable and the right sign denotes events. For example, 10 indicates the case when Jacques turned to a squirrel, but an event (for example, “pizza”) did not take place. It happened 4 times. And since binary 10 is decimal 2, we will store this in an array at index 2.

A function that calculates the coefficient ϕ from such an array:

function phi(table) {
  return (table[3] * table[0] - table[2] * table[1]) /
    Math.sqrt((table[2] + table[3]) *
              (table[0] + table[1]) *
              (table[1] + table[3]) *
              (table[0] + table[2]));
}
console.log(phi([76, 9, 4, 1]));
// → 0.068599434


This is just a direct implementation of the ϕ formula in JavaScript. Math.sqrt is a function to extract the square root of a Math object from a standard JavaScript environment. We need to add the two fields of the table to obtain fields of type n1 •, because we do not explicitly store the sum of columns or rows.

Jacques logged for three months. The result is available on the book's website
eloquentjavascript.net/code/jacques_journal.js.

To extract a 2x2 variable for a particular event, we need to go through all the records in a cycle and calculate how many times it happens in relation to the appeal to the protein.

function hasEvent(event, entry) {
  return entry.events.indexOf(event) != -1;
}
function tableFor(event, journal) {
  var table = [0, 0, 0, 0];
  for (var i = 0; i < journal.length; i++) {
    var entry = journal[i], index = 0;
    if (hasEvent(event, entry)) index += 1;
    if (entry.squirrel) index += 2;
    table[index] += 1;
  }
  return table;
}
console.log(tableFor("pizza", JOURNAL));
// → [76, 9, 4, 1]


The hasEvent function checks to see if the record contains the desired item. Arrays have an indexOf method that looks for a given value (in our case, the name of the event) in the array. and returns the index of its position in the array (-1 if it is not in the array). So, if the call to indexOf did not return -1, then there is an event in the record.

The loop body in tableFor calculates which cell in the table each of the log entries falls into. She looks to see if the record contains the desired event, and whether it is related to the appeal to the protein. Then the loop increments by one the element of the array corresponding to the desired cell.

Now we have all the tools for calculating correlations. It remains only to calculate the correlations for each of the events, and see if any of the list is displayed. But how to store these correlations?

Objects as maps

One way is to store correlations in an array using objects with the name and value properties. However, the search for correlations in the array will be rather cumbersome: you will need to go through the entire array to find the object with the desired name. It would be possible to wrap this process in a function, but the code would have to be written anyway, and the computer would do more work than necessary.

The better way is to use the properties of objects with event names. We can use square brackets to create and read properties, and the in operator to check for the existence of a property.

var map = {};
function storePhi(event, phi) {
  map[event] = phi;
}
storePhi("пицца", 0.069);
storePhi("тронул дерево", -0.081);
console.log("пицца" in map);
// → true
console.log(map["тронул дерево"]);
// → -0.081


A map is a way to associate values ​​from one area (in this case, the names of events) with values ​​in another (in our case, the coefficients ϕ).

There are a couple of problems with this use of objects - we will discuss them in Chapter 6, but for now we will not worry.

What if we need to collect all the events for which the coefficients are stored? They do not create a predictable sequence, as it would be in an array, so the for loop will not work. JavaScript offers a loop construct specifically for traversing all the properties of an object. It looks like a for loop, but uses the in command.

for (var event in map)
  console.log("Кореляция для '" + event +
              "' получается " + map[event]);
// → Кореляция для 'пицца' получается 0.069
// → Кореляция для 'тронул дерево' получается -0.081


Summary analysis


To find all types of events represented in the dataset, we process each occurrence in turn, and then create a loop for all occurrence events. We store a phis object that contains correlation coefficients for all types of events that we have already found. If we meet a new type that was not in phis yet, we calculate its correlation and add it to the object.

function gatherCorrelations(journal) {
  var phis = {};
  for (var entry = 0; entry < journal.length; entry++) {
    var events = journal[entry].events;
    for (var i = 0; i < events.length; i++) {
      var event = events[i];
      if (!(event in phis))
        phis[event] = phi(tableFor(event, journal));
    }
  }
  return phis;
}
var correlations = gatherCorrelations(JOURNAL);
console.log(correlations.pizza);
// → 0.068599434


We look. what happened:

for (var event in correlations)
  console.log(event + ": " + correlations[event]);
// → морковка:   0.0140970969
// → упражнения: 0.0685994341
// → выходной:  0.1371988681
// → хлеб:   -0.0757554019
// → пудинг: -0.0648203724
// и так далее...


Most correlations are close to zero. Carrots, bread and pudding are obviously not related to protein conversion. But it seems to happen more often on the weekend. Let's filter the results to only display correlations greater than 0.1 or less than -0.1

for (var event in correlations) {
  var correlation = correlations[event];
  if (correlation > 0.1 || correlation < -0.1)
    console.log(event + ": " + correlation);
}
// → выходной:        0.1371988681
// → чистил зубы: -0.3805211953
// → конфета:          0.1296407447
// → работа:          -0.1371988681
// → спагетти:      0.2425356250
// → читал:        0.1106828054
// → арахис:        0.5902679812


Yeah! Two correlation factors are noticeably larger than the rest. Peanuts greatly affect the likelihood of becoming a protein, while brushing your teeth prevents it.

Interesting. Let's try this:

for (var i = 0; i < JOURNAL.length; i++) {
  var entry = JOURNAL[i];
  if (hasEvent("арахис", entry) &&
     !hasEvent("чистка зубов", entry))
    entry.events.push("арахис зубы");
}
console.log(phi(tableFor("арахис зубы ", JOURNAL)));
// → 1


There can be no mistake! The phenomenon happens when Jacques has peanuts and does not brush his teeth. If he had not been so slut about oral hygiene, he would not have noticed his misfortune at all.

Knowing this, Jacques simply stops eating peanuts and discovers that the transformations have stopped.

Jacques is fine for a while. But after a few years, he loses his job, and in the end he has to go to the circus, where he acts as the Amazing Squirrel Man, picking up a mouthful of peanut butter before the show. Once, tired of such a miserable existence, Jacques does not turn back into a man, sneaks through a hole in a circus tent and disappears into the forest. No one else saw him.

Further massology


At the end of the chapter I want to introduce you to a few more concepts related to objects. Let's start with the useful methods that arrays have.

We saw push and pop methods that add and subtract elements at the end of an array. The appropriate methods to start the array are called unshift and shift

var todoList = [];
function rememberTo(task) {
  todoList.push(task);
}
function whatIsNext() {
  return todoList.shift();
}
function urgentlyRememberTo(task) {
  todoList.unshift(task);
}


This program manages the to-do list. You add things to the end of the list, calling rememberTo (“eat”), and when you are ready to do something, call whatIsNext () to get (and remove) the first element of the list. The urgentlyRememberTo function also adds a task, but only to the top of the list.

The indexOf method has a relative named lastIndexof, which starts searching for an element in the array from the end:

console.log([1, 2, 3, 2, 1].indexOf(2));
// → 1
console.log([1, 2, 3, 2, 1].lastIndexOf(2));
// → 3


Both methods, indexOf and lastIndexOf, take an optional second argument, which sets the start position of the search.

Another important method is slice, which takes the numbers of the start (start) and end (end) elements, and returns an array consisting only of the elements that fall into this gap. Including the one at the start index, but excluding the one at the end index.

console.log([0, 1, 2, 3, 4].slice(2, 4));
// → [2, 3]
console.log([0, 1, 2, 3, 4].slice(2));
// → [2, 3, 4]


When the end index is not specified, slice selects all elements after the start index. Strings have a similar method that works the same way.

The concat method is used to glue arrays, much like the + operator glues strings. The example shows the concat and slice methods in action. The function takes an array array and an index index, and returns a new array, which is a copy of the previous one, with the exception of the deleted element located at index index.

function remove(array, index) {
  return array.slice(0, index).concat(array.slice(index + 1));
}
console.log(remove(["a", "b", "c", "d", "e"], 2));
// → ["a", "b", "d", "e"]


Strings and their properties


We can get string property values, such as length and toUpperCase. But trying to add a new property will not lead to anything:

var myString = "Шарик";
myString.myProperty = "значение";
console.log(myString.myProperty);
// → undefined


Values ​​such as string, number, and Boolean are not objects, and although the language does not complain about attempts to assign new properties to them, it does not actually save them. Values ​​are immutable.

But they have their own built-in properties. Each line has a set of methods. The most useful, perhaps, are slice and indexOf, reminiscent of the same methods with arrays.

console.log("кокосы".slice(3, 6));
// → осы
console.log("кокос".indexOf("с"));
// → 4


The difference is that for a string, the indexOf method can take a string containing more than one character, while for arrays, this method works with only one element.

console.log("раз два три".indexOf("ва"));
// → 5


The trim method removes spaces (as well as line breaks, tabs, and other similar characters) from both ends of the line.

console.log("  ладно  \n ".trim());
// → ладно


We have already encountered the property of the string length. Access to individual characters of the line can be obtained through the charAt method, as well as simply through the numbering of positions, as in an array:

var string = "abc";
console.log(string.length);
// → 3
console.log(string.charAt(0));
// → a
console.log(string[1]);
// → b


Arguments object


When a function is called, a special variable called arguments is added to the environment of the function's executable body. It points to an object containing all the arguments passed to the function. Remember that in JavaScript, you can pass functions more or fewer arguments than declared with parameters.

function noArguments() {}
noArguments(1, 2, 3); // Пойдёт
function threeArguments(a, b, c) {}
threeArguments(); // И так можно


The arguments object has a length property that contains the actual number of arguments passed to the function. It also has properties for each argument under the names 0, 1, 2, etc.

If it seems to you that it is very similar to an array - you are right. This is very similar to an array. Unfortunately, this object does not have methods like slice or indexOf, which makes access to it more difficult.

function argumentCounter() {
  console.log("Ты дал мне", arguments.length, "аргумента.");
}
argumentCounter("Дядя", "Стёпа", "Милиционер");
// → Ты дал мне 3 аргумента.


Some functions are designed for any number of arguments, like console.log. They usually loop through the properties of the arguments object. This can be used to create user-friendly interfaces. For example, remember how we created entries for Jacques magazine:

addEntry(["работа", "тронул дерево", "пицца", "пробежка", "телевизор"], false);


Since we often call this function, we can make an alternative that is easier to call:

function addEntry(squirrel) {
  var entry = {events: [], squirrel: squirrel};
  for (var i = 1; i < arguments.length; i++)
    entry.events.push(arguments[i]);
  journal.push(entry);
}
addEntry(true, "работа", "тронул дерево", "пицца", "пробежка", "телевизор");


This version reads the first argument as usual, and passes through the rest in a loop (starting at index 1, skipping the first argument) and collects them into an array.

Math object


We have already seen that Math is a set of tools for working with numbers, such as Math.max (maximum), Math.min (minimum), and Math.sqrt (square root).

The Math object is used simply as a container for grouping related functions. There is only one Math object, and it is almost never used as values. It simply provides a namespace for all these functions and values ​​so that you do not need to make them global.

Too many global variables “pollute” the namespace. The more names are occupied, the more likely it is to accidentally use one of them as a variable. For example, it is very likely that you will want to use the name max for something in your program. Because the JavaScript built-in max function is safely packaged into a Math object, we don’t need to worry about overwriting it.

Many languages ​​will stop you, or at least warn you when you define a variable with a name that is already taken. JavaScript will not do this, so be careful.

Returning to the Math object. If you need trigonometry, it will help you. It has cos (cosine), sin (sine), and tan (tangent), their inverse functions are acos, asin, and atan. The number π (pi) - or at least its close approximation that fits into a JavaScript number - is also available as Math.PI. (There is such an old tradition in programming - writing constant names in uppercase).

function randomPointOnCircle(radius) {
  var angle = Math.random() * 2 * Math.PI;
  return {x: radius * Math.cos(angle),
          y: radius * Math.sin(angle)};
}
console.log(randomPointOnCircle(2));
// → {x: 0.3667, y: 1.966}


If you are unfamiliar with sines and cosines - do not despair. We will use them in chapter 13, and then I will explain them.

The previous example uses Math.random. This is a function that returns a new pseudo-random number between zero and one (including zero) with each call.

console.log(Math.random());
// → 0.36993729369714856
console.log(Math.random());
// → 0.727367032552138
console.log(Math.random());
// → 0.40180766698904335


Although computers are deterministic machines (they always react the same to the same input data), it is possible to make them give out random numbers. For this, the machine stores several numbers in its internal state. Each time a random number is requested, it performs various complex deterministic calculations and returns a part of the calculation result. She uses this result in order to change her internal state, so the next "random" number is different.

If you need a random integer, not a fraction, you can use Math.floor (rounds the number down to the nearest integer) on the result of Math.random.

console.log(Math.floor(Math.random() * 10));
// → 2


Multiplying the random number by 10, we get a number from zero to 10 (including zero). Since Math.floor rounds down, we get a number from 0 to 9 inclusive.

There is also a function Math.ceil ("ceiling" - the ceiling), which rounds up to the nearest integer) and Math.round (rounds to the nearest integer).

Global object


The global scope, where global variables live, can be accessed in the same way as an object. Each global variable is a property of this object. In browsers, the global scope is stored in the window variable.

var myVar = 10;
console.log("myVar" in window);
// → true
console.log(window.myVar);
// → 10


Total


Objects and arrays (which are a subspecies of objects) allow you to group several values ​​into one. Basically, this allows us to put a few related things in a bag and run around with it, instead of trying to rake all these things with your hands and try to hold them individually.

Most values ​​in JavaScript have properties, with the exception of null and undefined. We access them through value.propName or value ["propName"]. Objects use names to store properties and store more or less a fixed number of them. Arrays usually contain a variable number of similar in type variables, and use numbers (starting from zero) as the names of these quantities.

Arrays also have named properties, such as length, and several methods. Methods are functions that live among properties and (usually) work on the value whose property they are.

Objects can also act as maps, associating values ​​with names. The in operator is used to find out if an object contains a property with the given name. The same keyword is used in the for (for (var name in object)) loop to iterate over all properties of the object.

Exercises


Range sum

In the introduction, a convenient way of calculating the sums of ranges of numbers was mentioned:

console.log(sum(range(1, 10)));


Write a range function that takes two arguments, the beginning and end of the range, and returns an array that contains all the numbers from it, including the start and end.

Then write a sum function that takes an array of numbers and returns their sum. Run the above statement and make sure it returns 55.

As a bonus, add the range function so that it can take an optional third argument - the step to build the array. If it is not specified, the step is equal to one. A call to range (1, 10, 2) should return [1, 3, 5, 7, 9]. Make sure that it works with a negative step so that calling range (5, 2, -1) returns [5, 4, 3, 2].

console.log(sum(range(1, 10)));
// → 55
console.log(range(5, 2, -1));
// → [5, 4, 3, 2]


Reverse the array

Arrays have a reverse method that reverses the order of elements in an array. As an exercise, write two functions, reverseArray and reverseArrayInPlace. The first receives an array as an argument and produces a new array, with the reverse order of the elements. The second works like the original reverse method - it reverses the order of the elements in the array that was passed to it as an argument. Do not use the standard reverse method.

If you keep in mind the side effects and pure features from the previous chapter, which option do you find more useful? Which one is more effective?

console.log(reverseArray(["A", "B", "C"]));
// → ["C", "B", "A"];
var arrayValue = [1, 2, 3, 4, 5];
reverseArrayInPlace(arrayValue);
console.log(arrayValue);
// → [5, 4, 3, 2, 1]


List

Objects can be used to build various data structures. A common structure is a list (do not confuse with an array). A list is a linked set of objects, where the first object contains a link to the second, the second to the third, etc.

var list = {
  value: 1,
  rest: {
    value: 2,
    rest: {
      value: 3,
      rest: null
    }
  }
};


As a result, objects form a chain:


Lists are convenient in that they can share part of their structure. For example, you can make two lists, {value: 0, rest: list} and {value: -1, rest: list}, where list is a reference to a previously declared variable. These are two independent lists, and they have a common list structure, which includes the last three elements of each of them. In addition, the original list also saves its properties as a separate list of three elements.

Write an arrayToList function that builds such a structure, receiving [1, 2, 3] as an argument, and a listToArray function that creates an array from the list. Also write the prepend helper function, which receives an element and creates a new list, where this element is added in front of the original list, and the nth function, which takes a list and a number as arguments, and returns the element at a given position in the list, or undefined in case lack of such an element.

If your version of nth is not recursive, then write its recursive version.

console.log(arrayToList([10, 20]));
// → {value: 10, rest: {value: 20, rest: null}}
console.log(listToArray(arrayToList([10, 20, 30])));
// → [10, 20, 30]
console.log(prepend(10, prepend(20, null)));
// → {value: 10, rest: {value: 20, rest: null}}
console.log(nth(arrayToList([10, 20, 30]), 1));
// → 20


Deep comparison

The == operator compares object variables to see if they refer to the same object. But sometimes it would be useful to compare objects by content.

Write a deepEqual function that takes two values ​​and returns true only if they are two identical values ​​or if they are objects whose properties have the same value when compared with a recursive call to deepEqual.

To find out when to compare values ​​using ===, and when to compare objects by content, use the typeof operator. If it produces an “object” for both quantities, then you need to do a deep comparison. Don't forget about the one stupid exception that happened due to historical reasons: “typeof null” also returns “object”.

var obj = {here: {is: "an"}, object: 2};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
// → true

Also popular now: