Всем привет. В сентябре OTUS вновь запускает целую линейку курсов по JavaScript. Прямо сейчас вы можете посмотреть запись открытого урока по курсу "JavaScript Developer. Professional", а также зарегистрироваться на дни открытых дверей по курсам "React.js Developer" и "JavaScript Developer. Basic". Ну а мы традиционно делимся с вами переводом полезного материала.

Фронтенд-собеседование (в 2 частях)
1. Вопросы, которые мне задавали на фронтенд-собеседованиях.
2. Ресурсы для подготовки к собеседованию (на должность фронтенд-разработчика).
Вопросы, которые задавали на фронтенд-собеседованиях
В этой статье я собрал все вопросы, которые мне задавали на
собеседованиях во время поиска работы в условиях пандемии COVID-19.
Я также составил список ресурсов, которыми пользовался при
подготовке к собеседованию.
Вопросы я разделил на четыре части.
-
JS
-
Код
-
Задачи
-
Другие вопросы
Представленный код не является готовым решением, но лишь дает общее представление о моем подходе. Попробуйте реализовать свой собственный.
Отредактировано 20.08.2020. Посмотреть изменения
JS
1. Есть многомерный массив глубиной N, который нужно сгладить.
После сглаживания используйте его в качестве метода и привяжите к
экземпляру объекта array
.
Решение:
/** * [1,2,[3,4]] -> [1,2,3,4] */let arr = [1,2,[3,4, [5,6, [7, [8, 9, 10]]]]]function flatten(arr) { return arr.reduce(function(acc, next){ let isArray = Array.isArray(next) return acc.concat(isArray ? flatten(next) : next) }, [])}if (!Array.prototype.flatten) { Array.prototype.flatten = function() { return flatten(this) }}console.log(arr.flatten());
2. Создайте промис с нуля
Решение:
class CustomPromise { state = "PENDING" value = undefined thenCallbacks = [] errorCallbacks = [] constructor(action) { action(this.resolver.bind(this), this.reject.bind(this)) } resolver(value) { this.state = "RESOLVED" this.value = value this.thenCallbacks.forEach((callback) => { callback(this.value) }) } reject(value) { this.state = "REJECTED" this.value = value this.errorCallbacks.forEach((callback) => { callback(this.value) }) } then(callback) { this.thenCallbacks.push(callback) return this } catch (callback) { this.errorCallbacks.push(callback) return this }}let promise = new CustomPromise((resolver, reject) => { setTimeout(() => { const rand = Math.ceil(Math.random(1 * 1 + 6) * 6) if (rand > 2) { resolver("Success") } else { reject("Error") } }, 1000)})promise .then(function(response){ console.log(response) }) .catch(function(error){ console.log(error) })
3. Отфильтруйте список фильмов по среднему рейтингу, названию. Отсортируйте отфильтрованный список по любому полю.
Решение:
// O(M)function getMovies() { return []; // [{id, name, year}]}// O(R)function getRatings() { return []; // [{id, movie_id, rating}] 0 <= rating <= 10 // e.g 9.3}/** * minAvgRating -> * avgRating >= minAvgRating * * sort -> * name -> ascending order movies by name * -name -> descending * * avgRating * * * search -> * 'ave' -> 'Avengers' * 'avengers' -> 'Avengers' * 'AvengersInfinitywar' -> 'Avengers' */const toLower = str => str.toLocaleLowerCase()const getAvrgRating = (movie, movingWithRatings) => { let count = 0; return movingWithRatings.reduce((acc, value, index) => { const movieMatch = movie.id === value.movie_id if (movieMatch) { acc+=value.rating count++ } if (index === movingWithRatings.length - 1) { acc = acc/count } return acc }, 0)}const isSubString = (str1, str2) => { str1 = toLower(str1.split(" ").join("")) str2 = toLower(str2.split(" ").join("")) if (str1.length > str2.length) { return str1.startWith(str2) } else { return str2.startWith(str1) }}const moviesList = getMovies()const movingWithRatings = getRatings();function queryMovies({ search, sort, minAvgRating }) { let filteredMovies = movingWithRatings.filter(movie => getAvrgRating(movie, movingWithRatings) >= minAvgRating); filteredMovies = filteredMovies.map(movie => moviesList.filter(listItem => listItem.id === movie.movie_id).pop()) filteredMovies = filteredMovies.filter(movie => isSubString(toLower(movie.name), toLower(search))) filteredMovies = filteredMovies.sort((a, b) => { const isDescending = sort[0] === '-' ? true : false let sortCopy = isDescending ? sort.slice(1) : sort const value1 = a[sortCopy] const value2 = b[sortCopy] if (isDescending) { return value1 > value2 ? -1 : 1 }else { return value1 < value2 ? -1 : 1 } }) filteredMovies = filteredMovies.map(movie => ({ ...movie, avgRating: movingWithRatings.filter(ratedMovie => ratedMovie.movie_id === movie.id)[0].rating })) return filteredMovies}
4. Загрузите все публикации
и
комментарии
по URL-адресу конечной точки. Выполните
следующее.
-
Соотнесите все комментарии с публикациями, к которым они относятся. В результате данные должны иметь следующую структуру.
//service.jsconst POSTS_URL = `https://jsonplaceholder.typicode.com/posts`;const COMMENTS_URL = `https://jsonplaceholder.typicode.com/comments`;export const fetchAllPosts = () => { return fetch(POSTS_URL).then(res => res.json());};export const fetchAllComments = () => { return fetch(COMMENTS_URL).then(res => res.json());};import { fetchAllPosts, fetchAllComments } from "./service";const fetchData = async () => { const [posts, comments] = await Promise.all([ fetchAllPosts(), fetchAllComments() ]); const grabAllCommentsForPost = postId => comments.filter(comment => comment.postId === postId); const mappedPostWithComment = posts.reduce((acc, post) => { const allComments = grabAllCommentsForPost(post.id); acc[post.id] = allComments; return acc; }, {}); console.log("mappedPostWithComment ", mappedPostWithComment);};fetchData();
5. Реализуйте метод getHashCode
в экземпляре
объекта string. Метод должен быть доступен для всех строк.
Решение:
let s1 = "sample"if (!String.prototype.getHashCode) { String.prototype.getHashCode = function(){ console.log('String instance ', this) return this }}
6. Какой результат будет у приведенных ниже выражений?
1+true true+true 1+true 2 > 3 two>three
Решение:
221truefalsetrue
7. Реализуйте методы bind
и
reduce
.
Решение:
//bindif (!Function.prototype.bind) { Function.prototype.bind = function(...arg){ const func = this const context = arg[0] const params = arg.slice(1) return function(...innerParam) { func.apply(context, [...params, ...innerParam]) } }}//reduceArray.prototype.reduce = function(func, initState) { const arr = this const callback = func let init = initState arr.forEach(function(value, index){ init=callback(init, value) }) return init}
8. Реализуйте функцию debounce
Решение:
const debounce = function(func, interval) { let timerId; return function(e){ clearTimeout(timerId) timerId = setTimeout(function(){ func.apply() }, interval) }}debounce(apiCall, 3000)
9. Реализуйте функцию throttle
Решение:
const throttle = (callback, interval) => { let timerId; let allowEvents = true; return function() { let context = this; let args = arguments; if (allowEvents) { callback.apply(context, args) allowEvents = false; timerId = setTimeOut(function(){ allowEvents = true }, interval) } }}
10. Создайте механизм опроса для API. Вызов API выполняется через заданные интервалы. Это API фондового рынка, который должен получать обновленные данные о котировках. После получения результатов отразите их в пользовательском интерфейсе.
Нужно описать свой подход к решению, код писать не нужно. Возможны разные варианты решения.
Решение:
//С использованием метода setInterval, декоратора throttle и флаговsetInterval=>Endpoint=>Render//с инверсией управления//Endpoint=>Render=>setTimeout=>Endpoint=>Render=>SetTimeout...
11. Конвертируйте этот код, основанный на наследовании классов, в ES5.
class Parent(name){ constructor(name) { this.name=name } getName(){return this.name}}class Children extends Parent { constructor(props){ super(props) }}
Решение:
function Parent(name) { this.name = name}Parent.prototype.getName = function() { return this.name}function Children(name){ Parent.call(this, name)}Children.prototype = new Parent()
12. Что выведет следующий код?
//Q.1var x = 1;var y = x;x = 0;console.log(x, y);//Q.2var x = [1];var y = x;x = [];console.log(x,y);//Q.3function Abc() { console.log(this); };Abc()new Abc();//Q.4var x = 1;var obj = { x: 2, getX: function () { return console.log(this.x); }};obj.getX()let a = obj.getXconsole.log(a)//Q.5//Как вывести 2 с использованием переменной a в коде из предыдущего вопроса?//Q.6console.log("A");setTimeout(() => console.log("B"), 0);setTimeout(() => console.log("C"), 0);console.log("D");//Q.7setTimeout(function() { console.log("A");}, 0);Promise.resolve().then(function() { console.log("B");}).then(function() { console.log("C");});console.log("D");//Q.8let obj1 = { a:1, b:2}function mutate(obj) { obj = {a:4, c:6}}console.log(obj1)mutate(obj1)console.log(obj1)
Решение:
//A.10 1//A.2[] [1]//A.3Будет выведен объект window//A.4Будут выведены значения 2 и 1//A.5a.call(obj);//A.6A, D, B , C//A.7D, B, C, A//A.8{ a: 1, b: 2 }{ a: 1, b: 2 }
13. Есть массив чисел. Выведите следующие элементы.
const list = [1,2,3,4,5,6,7,8]const filteredArray = list.filter(between(3, 6)) // [4,5]
Решение:
function between(start, end) { return function (value,index) { return value>start && value<end }}
Алгоритмы
1. Рассмотрите последовательность.
A := 1B := A*2 + 2C := B*2 + 3 и так далее...
Напишите программу, которая :
-
выводит число, соответствующее определенной букве;
-
для строки, например 'GREP', вычисляет сумму чисел, соответствующих буквам строки (т.е. G + R + E + P) из этой последовательности;
-
и находит самую короткую строку, соответствующую большому числу (не больше максимального 32-разрядного целого числа).
Для решения последней задачи можно использовать жадный алгоритм. Числовые значения, соответствующие буквам, должны рассчитываться по мере необходимости. НЕ НУЖНО вычислять их заранее и сохранять в структуре данных.
Решение:
//A = 1//B = A*2 +2 //C = B*2+ 3//D = C*2+ 3var genCharArray = function(charA, charZ) { var a = [], i = charA.charCodeAt(0), j = charZ.charCodeAt(0); for (; i <= j; ++i) { a.push(String.fromCharCode(i)); } return a;}var charMap = {};var charArray = genCharArray('a', 'z');charArray.forEach(function(char, index){ charMap[char] = Number(index + 1);});var charSequence = function(char){ if(typeof char==="string"){ char = charMap[char]; } if(char==1){ return 1; }else{ return char + 2 * charSequence(char-1); }}var input = process.argv[2];if(input.length===1){ console.log(charSequence(charMap[input]));}else if(input.length>1){ var charTotalSequence = input.split("").reduce(function(acc, curr){ return acc + charSequence(charMap[curr]); },0); console.log(charTotalSequence);}
2. Найдите в массиве пару чисел, сумма которых равна заданному числу.
Решение:
let nums = [2, 7, 10, 1, 11, 15, 9]let target = 11let numsMap = new Map()let pairs = nums.reduce((acc, num) => { let numToFind = target - num if (numsMap.get(numToFind)) { return [...acc, [num, numToFind]] } else { numsMap.set(num, true) return [...acc] }}, [])console.log("Pairs ", pairs)
3. Найдите локальный максимум в заданном массиве. Локальный максимум это элемент, значение которого превышает значения элементов, расположенных справа и слева от него. Я использовал нотацию O(n). Это очевидное решение, которое можно оптимизировать.
Решение:
let x = [1, 2, 3, 5, 4] //Результаты: 5if x.length == 1 return x[0]else let i = 1 for(;i<x.length-1;i++){ if x[i-1]<x[i] and x[i] > x[i+1] return x[i] } if x.length - 1 == i return x[i]
4. Поверните матрицу на 90 градусов по часовой стрелке. Решение должно выполняться в памяти, содержащей входные данные (алгоритм in-place, на месте).
Решение:
[ [1, 2, 3], [4, 5, 6], [7, 8, 9]]//Сначала транспонируем матрицу.//После транспонирования матрица будет выглядеть так.[ [1, 4, 7], [2, 5, 8], [3, 6, 9]]//Теперь остается всего лишь изменить порядок элементов массива на обратный.//В результате матрица будет выглядеть так.[ [7, 4, 1], [8, 5, 2], [9, 6, 3]]//Первоначальная матрица развернута на 90 градусов.
5. Максимальная сумма подмассивов по модулю M.
6. Найдите в массиве три элемента, сумма которых равна заданному числу.
Решение:
let x = [1, 2, 3, 4, 5]let target = 7let found = []const twoPointer = (l ,r, current) => { while(l<r){ const totalSum = current + x[l] + x[r] if (totalSum === target) { found.push([current, x[l], x[r]]) return } else if (totalSum > target) { r-- } else { l++ } }}const threeSum = (x, target) => { for (let i=0;i<x.length;i++) { const current = x[i]; let leftPointer = i+1 let rightPointer = x.length - 1 if (current+x[leftPointer]+x[rightPointer] === target) { found.push([current, x[leftPointer], x[rightPointer]]) } else { twoPointer(leftPointer, rightPointer, current) } } return found}
7. Есть строка и целое число k. Вычислите количество подстрок, в которых каждый уникальный символ встречается ровно k раз.
Решение:
const subStrHasSameCharCount = (str, startIndex, endIndex, totalHop) => { let charMap = {} for (let k=startIndex;k<endIndex;k++) { let currentChar = str[k] if (charMap[currentChar]) { charMap[currentChar]++ } else { charMap[currentChar] = 1 } } let totalCount = Object.values(charMap).length > 0 return totalCount ? Object.values(charMap).every(item => item == totalHop) : false}const characterWithCountK = (str, k) => { if (k == 0) return '' let count = 0 let initialHop = k while (initialHop < str.length) { for (let j=0;j<str.length;j++) { let startIndex = j let endIndex = j + initialHop if(endIndex > str.length) continue count = subStrHasSameCharCount(str, startIndex, endIndex, k) ? count + 1: count } initialHop+=k } count = subStrHasSameCharCount(str, 0, initialHop, k) ? count + 1: count return count}let str = 'aabbcc'let k = 2console.log(characterWithCountK(str, k))
8. Есть две строки s1 и s2, каждая из которых содержит символы от a до z в разном порядке. Проверьте, можно ли переставить символы в строке s1 таким образом, чтобы строки стали равными.
Решение:
let s1 = 'dadbcbc'let s2 = 'ccbbdad'let charMap = {}const canBeRearranged = (s1, s2) => { if(s1.length!==s2.length){ return false } for(let i=0;i<s1.length;i++){ const charFromString1 = s1[i] const charFromString2 = s2[i] if(charFromString1 in charMap){ charMap[charFromString1]++ } else { charMap[charFromString1] = 1 } if(charFromString2 in charMap){ charMap[charFromString2]-- } else { charMap[charFromString2] = -1 } } for(let x in charMap){ if (charMap[x]!==0){ return false } } return true}canBeRearranged(s1, s2)
9. Есть массив с переменной начальной длиной. Расположите элементы массива в случайном порядке.
Решение:
const swap = (index1, index2, arr) => { let temp = arr[index1] arr[index1] = arr[index2] arr[index2] = temp}const shuffle = (arr) => { let totalLength = arr.length while(totalLength > 0) { let random = Math.floor(Math.random() * totalLength) totalLength-- swap(totalLength, random, arr) } return arr}let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]arr = shuffle(arr)
10. Вычислите сумму всех элементов многомерного массива с бесконечной глубиной.
Решение:
let arr = [4, 5, 7, 8, [5, 7, 9, [3, 5, 7]]]let sum = 0const calculateSum = (arr) => { arr.reduce(function(acc, currentVal) { const isEntryArray = Array.isArray(currentVal) if (isEntryArray) { return acc.concat(calculateSum(currentVal)) } else { sum+=currentVal return acc.concat(currentVal) } }, [])}calculateSum(arr)console.log(sum)
11. Сгладьте многоуровневый вложенный объект.
Решение:
const obj = { level1: { level2: { level3: { more: 'stuff', other: 'otherz', level4: { the: 'end', }, }, }, level2still: { last: 'one', }, am: 'bored', }, more: 'what', ipsum: { lorem: 'latin', },};var removeNesting = function(obj, parent){ for (let key in obj){ if (typeof obj[key] === "object") { removeNesting(obj[key], parent+"."+key) } else { flattenedObj[parent+'.'+key] = obj[key] } }}let flattenedObj = {}const sample = removeNesting(obj, "");console.log(flattenedObj);
12. Есть данные в формате json, где каждый объект обозначает каталог и может, в свою очередь, содержать другой вложенный объект. Выведите на экран структуру каталога.
13. Есть массив объектов, содержащих информацию о сотрудниках (у каждого сотрудника есть несколько подчиненных). На основании этих данных постройте иерархию сотрудников.
Решение:
const employeesData = [{ id: 2, name: 'Андрей (главный технический директор)', reportees: [6] }, { id: 3, name: 'Алексей (главный операционный директор)', reportees: []}, { id: 6, name: 'Александр (руководитель инженерной группы)', reportees: [9] }, { id: 9, name: 'Анатолий (старший инженер)', reportees: []}, { id: 10, name: 'Антон (генеральный директор)', reportees: [2, 3],}];/*A (генеральный директор)----B (главный технический директор)--------D (руководитель инженерной группы)------------E (старший инженер-разработчик)----C (главный операционный директор)*/const findCeo = (currentEmp) => { let parentEmployee = employeesData.filter(emp => emp.reportees.indexOf(currentEmp.id) > -1) if (parentEmployee && parentEmployee.length > 0) { return findCeo(parentEmployee[0]) } else { return currentEmp }}const logHierarchy = (currentEmp, indent) => { console.log("-".repeat(indent) + currentEmp.name) indent+=4; for(let i=0;i <currentEmp.reportees.length;i++) { let employee = employeesData.filter(emp => emp.id === currentEmp.reportees[i]) logHierarchy(employee[0], indent) }}const traverse = (employee) => { let ceo = findCeo(employee) logHierarchy(ceo, 0)}traverse(employeesData[0])
14. Преобразуйте заданную матрицу в спиральную и выведите на экран
const inputMatrix = [ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11,12,13,14,15], [16,17,18,19,20],]const exprectOutput = [1,2,3,4,5,10,15,20,19,18,17,16,11,6,7,8,9,14,13,12]
Решение:
function spiralParser(inputMatrix){ const output = []; let rows = inputMatrix.length; let cols = rows > 0 ? inputMatrix[0].length : 0; //singleEmptyRow => Edge case 1 //[] if (rows === 0) { return [] } if (rows === 1) { //singleElementRowNoCol => Edge case 2 //[[]] if (cols === 0) { return [] } else if (cols === 1){ //singleElementRow => Edge case 3 //[[1]] output.push(inputMatrix[0][0]) return output } } let top = 0; let bottom = rows - 1; let left = 0; let right = cols - 1; let direction = 0; //0 => left->right //1 => top->bottom //2 => right->left //3 => bottom->top while(left <= right && top <= bottom) { if(direction === 0) { //left->right for (let i=left; i<=right;i++) { output.push(inputMatrix[top][i]) } top++; } else if (direction === 1) { //top->bottom for (let i=top; i<=bottom;i++) { output.push(inputMatrix[i][right]) } right-- } else if (direction === 2) { //right->left for (let i=right; i>=left;i--) { output.push(inputMatrix[bottom][i]) } bottom-- } else if (direction === 3) { //bottom->top for (let i=bottom; i>=top;i--) { output.push(inputMatrix[i][left]) } left++ } direction = (direction + 1) % 4 } return output;}console.log(spiralParser(inputMatrix2))
15. Найдите символ с максимальным количеством последовательных вхождений в заданной строке.
let str = 'bbbaaaaccadd'; //Больше последовательных вхождений (4) у символа a
Решение:
//псевдокодmaxNow = если длина входной строки равна1 или больше 1 ? 1 : 0maxOverall = если длина входной строки равна1 или больше 1 ? 1 : 0для символов входной строки с индексом 1 и больше если символ равен предыдущему символу maxNow++ //увеличить на 1 maxOverall = max(maxOverall, maxNow) иначе если символ не равен предыдущему символу maxNow = 1
16. Есть массив переменной длины. Переместите все двойки (2) в конец массива.
let inputArr = [2,9,1,5,2,3,1,2,7,4,3,8,29,2,4,6,54,32,2,100]//ouput => [9,1,5,3,1,7,4,3,8,29,4,6,54,32,100,2,2,2,2,2]
Решение:
let slowRunner = 0for (let fastRunner=0;fastRunner<arr.length;fastRunner++) { if (arr[fastRunner]!==2 && arr[slow] == 2) { [arr[fastRunner], arr[slow]] = [arr[slow], arr[fastRunner]] slowRunner++ }}
17. Выведите список в обратном порядке
//На входе = 1 -> 2 -> 3 -> 4 -> 5 -> 6//В результате = 1 <- 2 <- 3 <- 4 <- 5 <- 6
Решение:
//псевдокодlet current = headlet prev = nulllet next = nullwhile(current) { next = current.next current.next = prev prev = current current = next}
18. Реализуйте прямой обход дерева с помощью итераций (без рекурсии)
Решение:
//псевдокодconst preorder = (root) => { let stack = [] stack.push(root) пока(переменная stack содержит данные) { let current = stack.pop() console.log(current.value) if (current.right) { stack.push(current.right) } if (current.left) { stack.push(current.left) } }}
Задачи
1. Разработайте систему для автоматизации парковки, отвечающую следующим требованиям.
-
Она хранит данные об N автомобилях. Она обрабатывает данные о наличии парковочных мест.
-
Она регистрирует въезжающие и выезжающие автомобили.
-
Автоматизированная система учета автомобилей регистрирует все въезжающие и выезжающие автомобили по следующим данным: регистрационный номер, цвет, парковочное место.
Система должна предоставлять следующую информацию:
-
регистрационные номера всех автомобилей определенного цвета;
-
номер парковочного места автомобиля по регистрационному номеру;
-
парковочные места автомобилей определенного цвета.
-
список свободных парковочных мест.
Требования:
-
для структурирования кода можно пользоваться классами/структурами;
-
решение должно быть расширяемым для последующего применения.
Некоторые принципы разработки кода:
-
модульность кода;
-
соглашения об именовании;
-
принципы SOLID.
2. Создайте React-компонент Ping
, при использовании
которого API будет отправлять запрос по заданному URL. Если
возвращается код состояния 200, это означает, что пользователь в
Сети. Если возвращается любой другой код состояния, это означает,
что пользователь не в Сети.
Попробуйте изменить статус
из сетевой панели dev
tools.
3. Создайте конструктор динамических форм на базе
json
. Формы должны группироваться по id
.
В каждой группе может быть другая вложенная группа.
4. Создайте на чистом JavaScript простейший лист Excel, в
котором можно добавлять
и удалять
строки
и столбцы. На решение этой задачи отводилось 40 минут.
5. Создайте строку поиска по списку пользователей.
Объект user (пользователь) включает следующие поля.
id: уникальный идентификаторимя: имя пользователятовары: список товаров, заказанных пользователемадрес: адрес пользоиндекс: почтовый индекс пользователя
Поиск должен осуществляться по всем полям.
Результаты поиска должны выводиться в виде карточки пользователя.
Требования:
Когда пользователь начинает вводить символы в строку поиска,
появляется выпадающий список. Поиск может осуществляться по
строке.
Навигация по списку карточек осуществляется с клавиатуры или
мышью.
Если для навигации используется и клавиатура, и мышь, в каждый
отдельно взятый момент времени должна подсвечиваться только одна
карточка.
(Навигация будет осуществляться с помощью клавиатуры, если мышь наведена на элемент списка. Если клавиатура не используется, навигация будет осуществляться с помощью мыши.)
Это похоже на принцип поиска в YouTube
Если результаты не найдены, выводится пустая карточка.
Список карточек должен прокручиваться.
Подсвеченная карточка (с помощью клавиатуры или мыши) появляется в области просмотра полностью.
Другие вопросы
1. Как бы вы структурировали интерфейсное приложение? (посмотреть)
2. Реализуйте стратегию ленивой загрузки (посмотреть)
3. Что такое серверный рендеринг?
4. Как развернуть React-приложение в промышленной среде?
5. Что такое сервис-воркер (веб-воркер)?
6. Как оптимизировать веб-приложение и повысить его
производительность?
7. Расскажите о стратегиях кэширования на стороне клиента.
8. Что такое CORS?
9. Назовите компоненты высшего порядка в React.
10. Как работает функция connect в Redux?
11. Что такое PureComponent в React?