Обложка статьи «Задачи по JavaScript для начинающих от Tproger и GeekBrains»

Задачи по JavaScript для начинающих от Tproger и GeekBrains

Партнёрский материал. Что это?

Вместе с факультетом Веб-разработки GeekUniversity собрали для вас несколько простых задач по JavaScript для обучения и тренировки, а также пару теоретических вопросов. Задачи расположены в порядке возрастания сложности.

Обратите внимание, что у любой задачи по программированию может быть несколько способов решения. Чтобы посмотреть добавленный нами вариант решения, кликните по соответствующей кнопке.

***

Задача 1

Напишите однострочное решение, которое вычисляет сумму квадратных корней для всех чётных чисел целочисленного массива.

console.log(
 // Входной массив
 [1, 4, 3, 0, 4, 5, 4]
   // Оставляем только чётные числа
   .filter(element => !(element % 2))
   // Считаем квадратный корень и записываем в аккумулятор
   .reduceRight((accumulator, element) => accumulator + Math.sqrt(element), 0)
); // 6

Метод reduceRight() применяет функцию к аккумулятору и каждому значению массива (справа налево), сводя его к одному значению. А метод reduce() выполняет функцию callback один раз для каждого элемента, присутствующего в массиве, за исключением пустот, принимая четыре аргумента:

  • начальное значение (или значение от предыдущего вызова callback);
  • значение текущего элемента;
  • текущий индекс;
  • массив, по которому происходит итерация.

Задача 2

Напишите функцию, которая пишет в консоль число в заданном диапазоне, в случае, если оно успешно делится или не делится с остатком или без остатка в зависимости от параметров.

function getNumbersModulatordBy(modulus, loggerCallback) {
 // Функция, которая возвращает функцию - это подход из функционального программирования
 // Называется замыканием (Closure)
 return function(start, end) {
   loggerCallback({ message: "Конфигурация", config: configuration });
   loggerCallback({ message: "Полученный модулятор", modulus: modulus });
   loggerCallback({
     message: "Полученный start и end",
     start: start,
     end: end
   });
 
   while (start <= end) {
     // Стоит обратить внимание:
     // 1. При нестрогом равенстве true == 1 и false == 0
     // 2. Здесь мы обращаемся к глобальному контексту configuration
     // p.s. поле isEntry может меняться
     if (start % modulus == configuration.isEntry) {
       // В данном случае мы используем loggerCallback как middleware
       // Это определит дальнейшую судьбу результата
       // Позволяет убрать side-effect
       loggerCallback(start);
     }
     start++;
   }
 };
}
 
// Глобальный контекст не имеет блочной видимости
// В данном случае переменная поднимается выше в самое начало кода
// Исполнитель JavaScript видит её в независимости от места инициализации
var configuration = {
 modulus: 10,
 isEntry: false,
 start: 45,
 end: 68
};
 
var loggerCallback = data => console.log(data);
 
let tenNumbersModulator = getNumbersModulatordBy(
 configuration.modulus,
 loggerCallback
);
 
// Переменные с глобальным контекстом доступны из:
// 1. globalThis (в Node.js)
// 2. window (в браузере)
window.configuration.modulus = 5;
 
let fiveNumbersModulator = getNumbersModulatordBy(
 configuration.modulus,
 loggerCallback
);
 
tenNumbersModulator(configuration.start, configuration.end); // 50, 60
tenNumbersModulator(10, 100); // 10, 20, 30, 40, 50, 60, 70, 80, 90, 100
 
// Так как мы изменили поле isEntry, то теперь:
// Функция вернёт те значения, которые не входят в модуляцию числа
window.configuration.isEntry = true;
 
fiveNumbersModulator(configuration.start, configuration.end); // 46, 51, 56, 61, 66
fiveNumbersModulator(10, 21); // 11, 16, 21

Задача 3

Есть ферма животных, у всех животных есть имена и возраст. Животные бывают разных типов: Кошки, Собаки, Коровы. У каждого животного могут быть дети. Если животное является родителем этих детей, в свою очередь глубина семейного древа может достигать N либо 0.

Опишите структуры данных для фермы животных и напишите функцию, которая делает подсчёт всех возрастов животных и выводит общий возраст для всей фермы.

// Родительский класс для всех животных
class Animal {
 constructor(name, age, childs = null) {
   this.name = name;
   this.age = age;
   this.childs = childs;
 }
}
 
// Класс Cat - потомок класса Animal
// Имеет те же поля, что и Animal
class Cat extends Animal {
 constructor(name, age, childs = null) {
   super(name, age, childs);
 }
}
 
// Класс Dog - потомок класса Animal
// Имеет те же поля, что и Animal
class Dog extends Animal {
 constructor(name, age, childs = null) {
   super(name, age, childs);
 }
}
 
// Класс Cow - потомок класса Animal
// Имеет те же поля, что и Animal
class Cow extends Animal {
 constructor(name, age, childs = null) {
   super(name, age, childs);
 }
}
 
// Рекурсивная функция для подсчета age
// Обходит все дочерние элементы
function getAnimalsAge(animals) {
 let output = 0;
 
 if (animals.length > 0) {
   // Использование функции reduce для получения общего age
   // https://learn.javascript.ru/array-iteration
   output += animals.reduce((acc, current) => {
     // Сумма age всех childs
     let count = 0;
     // Если childs пустой или его нет, тогда нет смысла пробегать по ним
     if (current.childs && current.childs.length > 0) {
       // Применение рекурсии
       count += getAnimalsAge(current.childs);
     }
 
     // Возвращаем сумму аккумулятора, текущего животного, сумму age всех childs
     return acc + current.age + count;
   }, 0);
 }
 
 return output;
}
 
// Функция для получения определённого количества животных
function generateAnimals(type, count) {
 let output = [];
 
 for (let i = 0; i <= count; i++) {
   let parameter = {
     name: `${type} ${i}`,
     age: i,
     childs: []
   };
 
   let item = null;
 
   switch (type) {
     case "Cat":
       item = new Cat(parameter.name, parameter.age);
       break;
     case "Dog":
       item = new Dog(parameter.name, parameter.age);
       break;
     case "Cow":
       item = new Cow(parameter.name, parameter.age);
       break;
   }
 
   if (item) {
     output.push(item);
   }
 }
 
 return output;
}
 
// Добавление childs ко всем переданным животным
function addChildsTo(animals, count, type) {
 animals.forEach(animal => {
   if (!animal.childs) {
     animal.childs = [];
   }
   // Обратите внимание, что массив - ссылка, поэтому изменяя здесь его поля
   // мы меняем их глобально
   animal.childs = generateAnimals(type, count);
 });
}
 
let cats = generateAnimals("Cat", 5);
addChildsTo(cats, 10, "Cat");
 
let dogs = generateAnimals("Dog", 3);
addChildsTo(dogs, 3, "Dog");
 
let cows = generateAnimals("Cow", 7);
addChildsTo(cows, 1, "Dog");
 
// Использование оператора Spread (ES6)
// https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/Spread_syntax
let animals = [...cats, ...dogs, ...cows];
 
console.log(getAnimalsAge(animals)) // 411

Задача 4

Перепишите функцию clone таким образом, чтобы она была способна клонировать переданный как параметр объект.

Пример проблемы:

let animal = {
 name: "animal",
 age: 10,
 childs: ["child 1", "child 2"]
};
 
let cat = clone(animal);
cat.name = "cat";
cat.age = 5;
cat.childs.push("child 3");
 
console.log(cat.name, cat.age); // cat 5
console.log(cat.childs); // [ 'child 1', 'child 2', 'child 3' ]
 
console.log(animal.name, animal.age); // cat 5
console.log(animal.childs); // [ 'child 1', 'child 2', 'child 3' ]
 
function clone(obj) {
 return obj;
}

Deep copy (глубокое копирование)

console.log(cat.name, cat.age); // cat 5
console.log(cat.childs); // [ 'child 1', 'child 2', 'child 3' ]
 
console.log(animal.name, animal.age); // animal 10
console.log(animal.childs); // [ 'child 1', 'child 2' ]
 
// Отличный вариант для JSON-safe объектов
let clone = (obj) => JSON.parse(JSON.stringify(obj));
 
// Опасен для рекурсивных объектов или когда имеется конструктор с параметрами
let clone = obj => {
 if (obj === null || typeof obj !== "object" || "isActiveClone" in obj)
   return obj;
 
 if (obj instanceof Date) var temp = new obj.constructor();
 //or new Date(obj);
 else var temp = obj.constructor();
 
 for (var key in obj) {
   if (Object.prototype.hasOwnProperty.call(obj, key)) {
     obj["isActiveClone"] = null;
     temp[key] = clone(obj[key]);
     delete obj["isActiveClone"];
   }
 }
 return temp;
};

Experimental deep copy (экспериментальное глубокое копирование)

Как пишут на Stack Overflow, HTML-стандарт включает в себя алгоритм структурированного клонирования, который может создавать глубокие копии объектов. Он всё ещё ограничен встроенными типами, но в дополнение к тем типам, что поддерживаются в JSON, поддерживает Dates, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, Sparse Arrays, Typed Arrays и, вероятно, больше в будущем. Решает также проблемы цикличных и рекурсивных структур, которые ломают JSON.

console.log(cat.name, cat.age); // cat 5
console.log(cat.childs); // [ 'child 1', 'child 2', 'child 3' ]
 
console.log(animal.name, animal.age); // animal 10
console.log(animal.childs); // [ 'child 1', 'child 2' ]
 
let clone = (obj) => require("v8").deserialize(require("v8").serialize(obj));

Shallow copy (поверхностное копирование)

Клонирование вложенных свойств по ссылке пропускается, нужно быть осторожным в использовании.

console.log(cat.name, cat.age); // cat 5
console.log(cat.childs); // [ 'child 1', 'child 2', 'child 3' ]
 
console.log(animal.name, animal.age); // animal 10
console.log(animal.childs); // [ 'child 1', 'child 2', 'child 3' ]
 
// Копирует значение всех собственных итерируемых элементов объекта
let clone = (obj) => Object.assign(new Object(), obj);
// Более известен как оператор Spread
let clone = (obj) => { ...obj };

Задача 5

Выйдите из цикла, изменив только две отмеченные строки. Результат в консоли сейчас останавливается на 9 9. Должен на 5 4.

for (let i = 0; i < 10; i++) { //! Эту строку можно изменить
 for (let j = 0; j < 10; j++) {
   if (i === 5 && j === 5) {
     //! Эту строку можно изменить
   }
 
   console.log(i, j);
 }
}

Как пишут на MDN web docs, инструкция метки (label) используется вместе с break или continue для альтернативного выхода из цикла. Метка добавляется перед блочным выражением в качестве ссылки, которая может быть использована в дальнейшем.

cycle: for (let i = 0; i < 10; i++) { //! Эту строку можно изменить
 for (let j = 0; j < 10; j++) {
   if (i === 5 && j === 5) {
     break cycle; //! Эту строку можно изменить
   }
   console.log(i, j);
 }
}

Задача 6

Яблоко стоит 1.15, апельсин стоит 2.30. Сколько они стоят вместе – чему равна сумма 1.15 + 2.30 с точки зрения JavaScript?

3.4499999999999997

Число хранится в памяти в бинарной форме, как последовательность бит – единиц и нулей. Но дроби, такие как 1.15, 2.30, которые выглядят довольно просто в десятичной системе счисления, на самом деле являются бесконечной дробью в двоичной форме. Это объяснение взято с сайта Современный учебник JavaScript, там же можно подробно почитать про числа в языке.

Задача 7

Чему равен typeof null в режиме use strict?

object

Как пишут на Хабре:

Все JavaScript-программисты давно привыкли к тому, что typeof null === 'object'; // true, хотя фактически null — примитивное значение. Многие знают, что это баг, и лично Брэндан Айк это признаёт. Этот баг, вероятно, никогда не будет исправлен из-за необходимости сохранения обратной совместимости существующего кода с новыми версиями языка.

Хотите вырасти от новичка до профессионала? Факультет Веб-разработки GeekUniversity даёт полтора года опыта для вашего резюме. Обучайтесь на практических заданиях, по-настоящему освойте фулстек-разработку и станьте ближе к профессии мечты.

Узнать больше

Рекламные публикации для бизнеса:
sales@tproger.ru, +7 916 559-71-10

Tproger