Главная проблема программистов-самоучек
...и ее решение. Не окончательное, не для всех. Но тем не менее.
Проблема самоучек - непонятно, что учить. Люди теряются в безбрежном просторе технологий - и либо бросают, либо поступают на дорогие, не обязательно качественные курсы.
Но объем технологий вовсе не безграничен.
Каждая технология решает строго определенный скоуп задач. Примем за данность, что каждая из них имеет аналоги...
...и это снижает реальное разнообразие в несколько раз. Не нужно учить реакт, ангуляр, вью и свелте. Достаточно взять одно из решений. Ответив на вопрос, что технология делает, ты поймёшь, чем аналог лучше - и без труда переключишься.
Далее. Не нужно начинать с низкого уровня - начинайте с архитектуры веба. Сложите ее из ответов на предыдущие вопросы.
То, что я предлагаю дальше, подходит только визуалам. Если это про вас - применяйте.
Создайте в голове визуальную модель архитектуры веба. Разделите React и Redux, REST и HTTP, клиент и сервер... Осознайте, в какой из блоков модели входят те или иные технологии. Визуализируйте каждый блок отдельно, разделяя на подблоки. Масштабируйте вглубь, пока это имеет смысл.
Это станет прорывом в вашем самообучении.
Вы начнёте учиться осознанно. Сможете переключаться в изучении разных технологий, не теряя общей картины. Будете запоминать детали этих технологий намного ярче. Сможете без волнения проходить собеседования - вместо каши в голове появится строгая и ясная модель.
Попробуйте. Это изменит все.
...и ее решение. Не окончательное, не для всех. Но тем не менее.
Проблема самоучек - непонятно, что учить. Люди теряются в безбрежном просторе технологий - и либо бросают, либо поступают на дорогие, не обязательно качественные курсы.
Но объем технологий вовсе не безграничен.
Каждая технология решает строго определенный скоуп задач. Примем за данность, что каждая из них имеет аналоги...
...и это снижает реальное разнообразие в несколько раз. Не нужно учить реакт, ангуляр, вью и свелте. Достаточно взять одно из решений. Ответив на вопрос, что технология делает, ты поймёшь, чем аналог лучше - и без труда переключишься.
Далее. Не нужно начинать с низкого уровня - начинайте с архитектуры веба. Сложите ее из ответов на предыдущие вопросы.
То, что я предлагаю дальше, подходит только визуалам. Если это про вас - применяйте.
Создайте в голове визуальную модель архитектуры веба. Разделите React и Redux, REST и HTTP, клиент и сервер... Осознайте, в какой из блоков модели входят те или иные технологии. Визуализируйте каждый блок отдельно, разделяя на подблоки. Масштабируйте вглубь, пока это имеет смысл.
Это станет прорывом в вашем самообучении.
Вы начнёте учиться осознанно. Сможете переключаться в изучении разных технологий, не теряя общей картины. Будете запоминать детали этих технологий намного ярче. Сможете без волнения проходить собеседования - вместо каши в голове появится строгая и ясная модель.
Попробуйте. Это изменит все.
Решаем задачки. Имитация Promise.all
Имеется недописанная функция parallel. Она принимает массив функций и коллбэк, вызываемый, когда все функции будут обработаны. Возвращает она массив результатов функций - в той последовательности, в которой они были переданы. Это важно!
Нельзя писать код вне функции parallel. Нежелательно использовать промисы.
Вот код:
#задачи
Имеется недописанная функция parallel. Она принимает массив функций и коллбэк, вызываемый, когда все функции будут обработаны. Возвращает она массив результатов функций - в той последовательности, в которой они были переданы. Это важно!
Нельзя писать код вне функции parallel. Нежелательно использовать промисы.
Вот код:
function parallel(funcArray, doneAll) {Решение - в следующем посте.
}
var a = function(done) {
setTimeout(function() {
done('result a');
}, 300);
};
var b = function(done) {
setTimeout(function() {
done('result b');
}, 200);
};
parallel([a,b], function(results) {
console.log(results); // ['result a', 'result b']
});
#задачи
Имитация Promise.all. Решение.
Исследуем код. Какие есть подсказки:
- массив функций (а значит, его нужно перебирать)
- коллбэк doneAll (который, получается, должен вызываться, когда функция закончит работу)
- две функции, содержащие setTimeout (значит, работаем с Event Loop)
- коллбэк done внутри этих функций (который выполняется при каждой асинхронной операции)
- результат в виде массива результатов вызова функций в том же порядке, что и были переданы
Прежде всего, нужно пробежаться по массиву funcArray. Код асинхронен, поэтому нельзя сразу вернуть результат. Мы не используем .map - вместо этого пробежимся на месте с помощью forEach. Создадим переменную result с пустым массивом для результата.
Нужно как-то указывать асинхронным коллбэкам, в каком порядке они были переданы. К счастью, forEach принимает вторым аргументом текущий индекс. Его прокинем в коллбэк.
.push для пополнения использовать нельзя, но можно присваивать значения прямо по числовому индексу. Так и поступим, используя индекс из forEach.
Но как узнать, что работа закончена и можно возвращать result? Синхронный код не умеет отслеживать выполнение setTimeout.
Раз не умеет синхронный - сумеет асинхронный!
Поэтому поместим эту логику тоже в коллбэк done. Введем в синхронной части счетчик count и инкрементируем его внутри done. Если значение счетчика сравняется с длиной переданного массива - выполним во все том же done коллбэк doneAll.
Предлагаю реализовать эту логику самостоятельно. Если не получится, погуглите реализацию Promise.all под капотом.
А вот моё решение: https://codepen.io/gretten/pen/zYzKLZL
Исследуем код. Какие есть подсказки:
- массив функций (а значит, его нужно перебирать)
- коллбэк doneAll (который, получается, должен вызываться, когда функция закончит работу)
- две функции, содержащие setTimeout (значит, работаем с Event Loop)
- коллбэк done внутри этих функций (который выполняется при каждой асинхронной операции)
- результат в виде массива результатов вызова функций в том же порядке, что и были переданы
Прежде всего, нужно пробежаться по массиву funcArray. Код асинхронен, поэтому нельзя сразу вернуть результат. Мы не используем .map - вместо этого пробежимся на месте с помощью forEach. Создадим переменную result с пустым массивом для результата.
Нужно как-то указывать асинхронным коллбэкам, в каком порядке они были переданы. К счастью, forEach принимает вторым аргументом текущий индекс. Его прокинем в коллбэк.
.push для пополнения использовать нельзя, но можно присваивать значения прямо по числовому индексу. Так и поступим, используя индекс из forEach.
Но как узнать, что работа закончена и можно возвращать result? Синхронный код не умеет отслеживать выполнение setTimeout.
Раз не умеет синхронный - сумеет асинхронный!
Поэтому поместим эту логику тоже в коллбэк done. Введем в синхронной части счетчик count и инкрементируем его внутри done. Если значение счетчика сравняется с длиной переданного массива - выполним во все том же done коллбэк doneAll.
Предлагаю реализовать эту логику самостоятельно. Если не получится, погуглите реализацию Promise.all под капотом.
А вот моё решение: https://codepen.io/gretten/pen/zYzKLZL
Решаем задачки. "Бесконечное каррирование"
Эта задача популярна на интервью в Amazon. Напишите функцию sum, которую можно вызывать следующим образом:
запутать читателя не допустить ошибок и показать код нагляднее, использую форму c if-ами и TS:
Задача функции - суммировать числа.
Пока в вызовах присутствует аргумент - она продолжает суммировать. Как только появляется вызов без аргумента - вычисления заканчиваются.
Для решения вспомним, что такое каррирование. Это прием, при котором функция возвращает функцию-обертку с фиксированным аргументом. Мы вызываем обертку с новым аргументом - таким образом сокращая написание кода и возможности для ошибок.
Обычно для этого обертки пишут явно. Однако здесь количество аргументов не ограничено, нельзя просто взять и возвращать несколько оберток под их заданное количество.
Бесконечное количество аргументов означает цикл.
В функциональном программировании циклы делают через рекурсию - её и используем.
Вызываем функцию первый раз. Передан агрумент a. На этом шаге функция возвращает обертку.
Вызываем обертку. Она проверяет, передан ли аргумент b.
Если аргумент передан - обертка возвращает изначальную функцию из себя, передав аргументом сумму a + b. Отсюда цикл повторяется, до тех, пока не выполнится условие...
...если аргумента нет. Тогда обертка возвращает a и заканчивает цикл.
Таким образом мы ограничили количество аргументов на отдельных шагах вычислений, чтобы использовать каррирование для неограниченного числа аргументов.
#задачи
Эта задача популярна на интервью в Amazon. Напишите функцию sum, которую можно вызывать следующим образом:
sum(1)() // 1
sum(1)(2)() // 3
sum(1)(2)(-1)() // 2
Решение напишу здесь же. Чтобы const sum = (a: number): number => {
return (b: number): number => {
if (b) {
return sum(a + b)
} else {
return a
}
}
}
Следите за пальцами.Задача функции - суммировать числа.
Пока в вызовах присутствует аргумент - она продолжает суммировать. Как только появляется вызов без аргумента - вычисления заканчиваются.
Для решения вспомним, что такое каррирование. Это прием, при котором функция возвращает функцию-обертку с фиксированным аргументом. Мы вызываем обертку с новым аргументом - таким образом сокращая написание кода и возможности для ошибок.
Обычно для этого обертки пишут явно. Однако здесь количество аргументов не ограничено, нельзя просто взять и возвращать несколько оберток под их заданное количество.
Бесконечное количество аргументов означает цикл.
В функциональном программировании циклы делают через рекурсию - её и используем.
Вызываем функцию первый раз. Передан агрумент a. На этом шаге функция возвращает обертку.
Вызываем обертку. Она проверяет, передан ли аргумент b.
Если аргумент передан - обертка возвращает изначальную функцию из себя, передав аргументом сумму a + b. Отсюда цикл повторяется, до тех, пока не выполнится условие...
...если аргумента нет. Тогда обертка возвращает a и заканчивает цикл.
Таким образом мы ограничили количество аргументов на отдельных шагах вычислений, чтобы использовать каррирование для неограниченного числа аргументов.
#задачи
Пятиминутка мотивации
Я разработчик-самоучка с 2.5 годами опыта. Сегодня я получил job offer от крупной продуктовой компании. Для этого понадобилось три дня по 2-3 собеседования. Бессрочный договор, ДМС, приятная даже по московским меркам зарплата и другие плюшки в комплекте.
На собеседовании в ту компанию было три блока вопросов: вёрстка, JS, React. Я ответил правильно лишь на один вопрос по верстке - но ни разу не ошибся в остальных.
Задачи по JS:
- очередность вызова console.log в синхронном коде, макротасках и микротасках (на понимание Event Loop).
- что выведут обработчики событий после навешивания в цикле (на понимание асинхронности). Гуглите задачу "армия функций".
- что такое preventDefault и stopPropagation
- что такое this
И еще парочка подобных задач.
По React:
- почему React - библиотека, а не фреймворк?
- что такое хуки?
- назовите популярные хуки Реакта
- как работают хуки, например, useCallback
- какие есть стадии и методы жизненного цикла?
И еще немного поболтали про Реакт в целом
Ничего сложного, как видите. Вспоминать на память все методы массива (а такое порой спрашивали) не пришлось - и это позитивный момент. Пришлось поработать компилятором в голове.
Параллельно мне поступило более 50 откликов от рекрутеров - в том числе, на вакансии, формально требующие высшего образования, более пяти лет опыта и весьма обширного стека.
Выводы просты: порог вхождения во фронтэнд вырос, но не задран непроходимо. Продолжайте учиться. Большинство сдается - но те, кто упорен, выигрывают.
Я разработчик-самоучка с 2.5 годами опыта. Сегодня я получил job offer от крупной продуктовой компании. Для этого понадобилось три дня по 2-3 собеседования. Бессрочный договор, ДМС, приятная даже по московским меркам зарплата и другие плюшки в комплекте.
На собеседовании в ту компанию было три блока вопросов: вёрстка, JS, React. Я ответил правильно лишь на один вопрос по верстке - но ни разу не ошибся в остальных.
Задачи по JS:
- очередность вызова console.log в синхронном коде, макротасках и микротасках (на понимание Event Loop).
- что выведут обработчики событий после навешивания в цикле (на понимание асинхронности). Гуглите задачу "армия функций".
- что такое preventDefault и stopPropagation
- что такое this
И еще парочка подобных задач.
По React:
- почему React - библиотека, а не фреймворк?
- что такое хуки?
- назовите популярные хуки Реакта
- как работают хуки, например, useCallback
- какие есть стадии и методы жизненного цикла?
И еще немного поболтали про Реакт в целом
Ничего сложного, как видите. Вспоминать на память все методы массива (а такое порой спрашивали) не пришлось - и это позитивный момент. Пришлось поработать компилятором в голове.
Параллельно мне поступило более 50 откликов от рекрутеров - в том числе, на вакансии, формально требующие высшего образования, более пяти лет опыта и весьма обширного стека.
Выводы просты: порог вхождения во фронтэнд вырос, но не задран непроходимо. Продолжайте учиться. Большинство сдается - но те, кто упорен, выигрывают.
Убедите за пять минут нанять вас?
Был такой вопрос на собеседовании в крупнейший банк. Я тогда отшутился. А сейчас, пожалуй, назвал бы три причины.
1. Я разработал методику легкого обучения в разработке. Я вижу фронтэнд как систему, решающую узкий скоуп задач, сложенную из подсистем, у каждой из которых есть масса аналогов. Поэтому мне легко задать вопрос, что делает новый фреймворк или язык, каковы его сильные и слабые стороны, и изучать с этой позиции.
2. У меня есть четкий карьерный план. Я знаю, как последовательно стать senior разработчиком, лидом и руководителем проектов - какие компетенции отделяют их от middle. И знаю, кем хочу быть через пять лет. Или через 10.
3. Я ❤️ свою работу. Я подписан на каналы рисерчеров от фронтенда, читаю книги по архитектуре, практикую новые технологии просто из интереса к ним. А еще я люблю, когда моё изделие приносит радость тысячам пользователей - и отличные деньги компании, с которой работаю.
Считаю, эти сильные стороны имеет смысл рассказывать. И, конечно, использовать. Ведь это не просто слова.
Был такой вопрос на собеседовании в крупнейший банк. Я тогда отшутился. А сейчас, пожалуй, назвал бы три причины.
1. Я разработал методику легкого обучения в разработке. Я вижу фронтэнд как систему, решающую узкий скоуп задач, сложенную из подсистем, у каждой из которых есть масса аналогов. Поэтому мне легко задать вопрос, что делает новый фреймворк или язык, каковы его сильные и слабые стороны, и изучать с этой позиции.
2. У меня есть четкий карьерный план. Я знаю, как последовательно стать senior разработчиком, лидом и руководителем проектов - какие компетенции отделяют их от middle. И знаю, кем хочу быть через пять лет. Или через 10.
3. Я ❤️ свою работу. Я подписан на каналы рисерчеров от фронтенда, читаю книги по архитектуре, практикую новые технологии просто из интереса к ним. А еще я люблю, когда моё изделие приносит радость тысячам пользователей - и отличные деньги компании, с которой работаю.
Считаю, эти сильные стороны имеет смысл рассказывать. И, конечно, использовать. Ведь это не просто слова.
Дэн Щербаков ⚛️ pinned «Убедите за пять минут нанять вас? Был такой вопрос на собеседовании в крупнейший банк. Я тогда отшутился. А сейчас, пожалуй, назвал бы три причины. 1. Я разработал методику легкого обучения в разработке. Я вижу фронтэнд как систему, решающую узкий скоуп…»
Учим строки
Знать наизусть, что JS умеет делать со строками, полезно не только на собеседованиях, но и в реальной работе - экономит время на раскурку документации. Давайте раз и навсегда запомним его возможности. Регулярные выражения исключаю - это тема отдельного юнита.
Что вообще есть в скоупе строк?
- Кавычки. Три варианта: "str", 'str', интерполяция. Третий - для шаблонных строк.
- Спецсимволы. Всегда экранируются \. Перевод каретки, кавычки внутри строк, знак экранирования и т.д.
- Длина строки. Свойство length.
- Доступ к символам.
- Иммутабельность. Строки нельзя мутировать - можно только заменять, как массивы в Реакте.
- Работа с регистром (капслок): toLowerCase, toUpperCase.
- Поиск подстроки:
- IndexOf и lastIndexOf. Принимает искомую подстроку и начальный индекс, возвращает индекс первого символа (включая 0, который неявно приводится к false) найденной подстроки или -1. Может работать в цикле. Первый ищет с начала строки, второй - с конца.
Сигнатурно аналогичны indexOf. Возвращают булево значение в зависимости, найдено совпадение или нет.
Получение подстроки. Возвращает результат поиска.
- Триммер: trim(). Удаляет пробелы по обе стороны строки.
- Повторитель: repeat(count). Повторяет строку count раз.
Знать наизусть, что JS умеет делать со строками, полезно не только на собеседованиях, но и в реальной работе - экономит время на раскурку документации. Давайте раз и навсегда запомним его возможности. Регулярные выражения исключаю - это тема отдельного юнита.
Что вообще есть в скоупе строк?
- Кавычки. Три варианта: "str", 'str', интерполяция. Третий - для шаблонных строк.
- Спецсимволы. Всегда экранируются \. Перевод каретки, кавычки внутри строк, знак экранирования и т.д.
- Длина строки. Свойство length.
- Доступ к символам.
str[Index: int] -> char | undefined
str.charAt(index: int) -> char | ""
- Перебор (без преобразования в массив): for...of- Иммутабельность. Строки нельзя мутировать - можно только заменять, как массивы в Реакте.
- Работа с регистром (капслок): toLowerCase, toUpperCase.
- Поиск подстроки:
- IndexOf и lastIndexOf. Принимает искомую подстроку и начальный индекс, возвращает индекс первого символа (включая 0, который неявно приводится к false) найденной подстроки или -1. Может работать в цикле. Первый ищет с начала строки, второй - с конца.
indexOf(target: string, startIndex: int) -> int | -1
lastIndexOf(target: string, position: int) -> int | -1
~str.indexOf(…) -> -int | 0 // побитовый трюк для легаси-кода
- includes, startsWith, endsWithСигнатурно аналогичны indexOf. Возвращают булево значение в зависимости, найдено совпадение или нет.
Получение подстроки. Возвращает результат поиска.
- slice(start: int, end: int) -> string | ""
возвращает подстроку, включая start, но не включая end. Может принимать отрицательные числа для поиска с конца.- substring(start: int, end: int) -> string | ""
возвращает подстроку между start и end. Не понимает отрицательных чисел.- substr(start: int, length: int) -> string | ""
принимает начальный индекс и длину искомого отрезка. Умеет искать с конца (отрицательный индекс).- Триммер: trim(). Удаляет пробелы по обе стороны строки.
- Повторитель: repeat(count). Повторяет строку count раз.
What's next?
Нет ничего плохого, чтобы не знать чего-либо. Что действительно плохо - не стремиться учиться и не осознавать слабых сторон в своих знаниях.
Вот что я хочу освежить, дополнить или изучить в этом году:
- Тестирование. Jest, разные виды тестирования компонентов и сопутствующих систем. TDD.
- Бандлинг. Webpack и аналоги.
- Typescript. Узнать его глубже. Дженерики и другие сложные элементы типизации. Разработка от интерфейсов.
- Верстка. Словить тренды, вспомнить старое.
- Middleware. Танки, саги, вот это всё.
- Авторизация. JWT и подобное.
- Архитектура. Чтобы делать проекты с нуля, руководить их разработкой, общаться с бизнесом.
- UI/UX. Чтобы аргументированно общаться с дизайнерами.
- Ну и пару книг об искусстве переговоров.
Список неполон, но это и не обязательно. Во фронте вообще планы должны быть гибкими :)
Нет ничего плохого, чтобы не знать чего-либо. Что действительно плохо - не стремиться учиться и не осознавать слабых сторон в своих знаниях.
Вот что я хочу освежить, дополнить или изучить в этом году:
- Тестирование. Jest, разные виды тестирования компонентов и сопутствующих систем. TDD.
- Бандлинг. Webpack и аналоги.
- Typescript. Узнать его глубже. Дженерики и другие сложные элементы типизации. Разработка от интерфейсов.
- Верстка. Словить тренды, вспомнить старое.
- Middleware. Танки, саги, вот это всё.
- Авторизация. JWT и подобное.
- Архитектура. Чтобы делать проекты с нуля, руководить их разработкой, общаться с бизнесом.
- UI/UX. Чтобы аргументированно общаться с дизайнерами.
- Ну и пару книг об искусстве переговоров.
Список неполон, но это и не обязательно. Во фронте вообще планы должны быть гибкими :)
Аgile. От хаоса к решению.
Однажды меня пригласили на тренинг по эджайлу. Оттуда я вынес убежденность, что это клёвая штука для крупных проектов, - а также идею, полезную в личном развитии. Дословно не помню, но суть была вот в чем.
Когда команда встает перед бизнес-задачей, она начинает в хаосе. Ничего не понятно, ничего не сделано - есть только цель. Это первый шаг.
Менеджмент формулирует охват предметной области, ставит вопросы, собирает информацию. Это второй шаг.
Аналитики и разработчики подбирают инструменты для реализации, оценивают стоимость разработки. Это третий шаг.
Разработчики создают конкретные реализации. Это четвертый шаг.
Цикл повторяется, масштабируясь до отдельных фич.
Таким образом команды вступают в борьбу с хаосом - с неопределенностью, непониманием, расплывчатыми перспективами - и приходят к четким решениям.
Эта методика, вместе с визуализацией сложных систем, помогает мне учиться. Я начинаю в хаосе новых технологий и областей ответственности - и заканчиваю четким о них представлением.
Помогает, кстати, во всех областях жизни, где присутствует хаос.
Однажды меня пригласили на тренинг по эджайлу. Оттуда я вынес убежденность, что это клёвая штука для крупных проектов, - а также идею, полезную в личном развитии. Дословно не помню, но суть была вот в чем.
Когда команда встает перед бизнес-задачей, она начинает в хаосе. Ничего не понятно, ничего не сделано - есть только цель. Это первый шаг.
Менеджмент формулирует охват предметной области, ставит вопросы, собирает информацию. Это второй шаг.
Аналитики и разработчики подбирают инструменты для реализации, оценивают стоимость разработки. Это третий шаг.
Разработчики создают конкретные реализации. Это четвертый шаг.
Цикл повторяется, масштабируясь до отдельных фич.
Таким образом команды вступают в борьбу с хаосом - с неопределенностью, непониманием, расплывчатыми перспективами - и приходят к четким решениям.
Эта методика, вместе с визуализацией сложных систем, помогает мне учиться. Я начинаю в хаосе новых технологий и областей ответственности - и заканчиваю четким о них представлением.
Помогает, кстати, во всех областях жизни, где присутствует хаос.
Учим относительные единицы по эджайлу
Изучим относительные единицы измерения по методике работы с хаосом.
Шаг 1. Бизнес-задача: изучить востребованную на рынке часть modern CSS - относительные единицы: em, rem и другие.
Шаг 2. Поставим вопросы:
- Что это?
- Какие задачи решает?
- В чём выгода использования?
- Как использовать?
Шаг 3. Ответы, кейсы.
Это относительные единицы размерности:
Em - emphemeral unit,
rem - root em,
vh - viewport height,
vw - viewport width.
Используется для адаптивной / мобильной верстки.
Облегчает адаптивную верстку. Параметры, указанные в этих единицах, управляются от размера шрифта. Таким образом, передавая только размер шрифта таким элементам, мы управляем, например, их отступами и размерами. Rem - облегчает управление глобальной размерностью шрифтов для лучшей доступности контента.
Шаг 4. Детали реализации. Эти единицы устанавливаются относительно некоего абсолютного указателя - размерности шрифта или размера viewport:
Установленная браузером по умолчанию (16px) (em)
Унаследованная от родительского блока (em)
Переданная напрямую (em)
Унаследованная от переменной :root (rem)
Установленная реальной шириной экрана (vw)
...высотой (vh)
Книга “Css для профи“ советует:
“Если сомневаетесь, используйте rem для размера шрифта, пиксели - для border-ов, а em - для большинства других свойств”.
Простейший кейс с em: https://codepen.io/gretten/pen/dyRdWQK
Задача выполнена. Остальное - вопрос опыта.
Изучим относительные единицы измерения по методике работы с хаосом.
Шаг 1. Бизнес-задача: изучить востребованную на рынке часть modern CSS - относительные единицы: em, rem и другие.
Шаг 2. Поставим вопросы:
- Что это?
- Какие задачи решает?
- В чём выгода использования?
- Как использовать?
Шаг 3. Ответы, кейсы.
Это относительные единицы размерности:
Em - emphemeral unit,
rem - root em,
vh - viewport height,
vw - viewport width.
Используется для адаптивной / мобильной верстки.
Облегчает адаптивную верстку. Параметры, указанные в этих единицах, управляются от размера шрифта. Таким образом, передавая только размер шрифта таким элементам, мы управляем, например, их отступами и размерами. Rem - облегчает управление глобальной размерностью шрифтов для лучшей доступности контента.
Шаг 4. Детали реализации. Эти единицы устанавливаются относительно некоего абсолютного указателя - размерности шрифта или размера viewport:
Установленная браузером по умолчанию (16px) (em)
Унаследованная от родительского блока (em)
Переданная напрямую (em)
Унаследованная от переменной :root (rem)
Установленная реальной шириной экрана (vw)
...высотой (vh)
Книга “Css для профи“ советует:
“Если сомневаетесь, используйте rem для размера шрифта, пиксели - для border-ов, а em - для большинства других свойств”.
Простейший кейс с em: https://codepen.io/gretten/pen/dyRdWQK
Задача выполнена. Остальное - вопрос опыта.
Собеседования. Личный опыт.
Что я понял за множество собеседований.
1. Я задавал вопросы о проектах и компаниях ради самих вопросов. Я гуглил их и задавал, не осмысляя и не рефлексируя, исходя из того, что компании хотят видеть заинтересованность. Это была ошибка. Я понял, что нужно задавать вопросы, исходя из реальных перспектив.
Что я начал спрашивать:
- Какая часть проекта уже сделана. Это нужно, чтобы понять объем легаси на проекте и готова ли архитектура.
- Из кого состоит команда. Хочешь быть архитектором, верстальщиком, дизайнером и фронтом в одном лице? Это круто, но может жопа порваться. Нужно понимать ожидания компании от тебя.
- Какие технологии юзают - и будут юзать - на конкретном твоем проекте. Успеешь подтянуть не наугад.
- Как в компании можно развиваться. От оплаты курсов до того, с кем потолковать, если хочешь взять больше ответственности.
- Как повышать доход, оставаясь в компании.
Это лишь то, что я вывел для себя. Есть много полезных вопросов, которые описаны в книгах по этой теме.
2. Первое собеседование в оффлайне - повод отказаться. Это неоправданное вложение времени. Компания смотрит множество кандидатов - и твои шансы пропорциональны этому количеству. Ты можешь быть лучшим технически, но какой-то парень просто будет веселее и непринужденнее :) Или наоборот. Или компания просто возьмет самого дешевого.
3. Крупные компании лучше малых. Там уже отлажены процессы. Твоя роль более узка, чем в малых (а многозадачность, как известно, миф). Ты знаешь, к чему готовиться. И никто не урежет тебе зарплату за месяц, чтобы оплатить аренду офиса, если что-то пойдет не так. Зато в малых все более человечно.
Потом еще че-нить добавлю.
Что я понял за множество собеседований.
1. Я задавал вопросы о проектах и компаниях ради самих вопросов. Я гуглил их и задавал, не осмысляя и не рефлексируя, исходя из того, что компании хотят видеть заинтересованность. Это была ошибка. Я понял, что нужно задавать вопросы, исходя из реальных перспектив.
Что я начал спрашивать:
- Какая часть проекта уже сделана. Это нужно, чтобы понять объем легаси на проекте и готова ли архитектура.
- Из кого состоит команда. Хочешь быть архитектором, верстальщиком, дизайнером и фронтом в одном лице? Это круто, но может жопа порваться. Нужно понимать ожидания компании от тебя.
- Какие технологии юзают - и будут юзать - на конкретном твоем проекте. Успеешь подтянуть не наугад.
- Как в компании можно развиваться. От оплаты курсов до того, с кем потолковать, если хочешь взять больше ответственности.
- Как повышать доход, оставаясь в компании.
Это лишь то, что я вывел для себя. Есть много полезных вопросов, которые описаны в книгах по этой теме.
2. Первое собеседование в оффлайне - повод отказаться. Это неоправданное вложение времени. Компания смотрит множество кандидатов - и твои шансы пропорциональны этому количеству. Ты можешь быть лучшим технически, но какой-то парень просто будет веселее и непринужденнее :) Или наоборот. Или компания просто возьмет самого дешевого.
3. Крупные компании лучше малых. Там уже отлажены процессы. Твоя роль более узка, чем в малых (а многозадачность, как известно, миф). Ты знаешь, к чему готовиться. И никто не урежет тебе зарплату за месяц, чтобы оплатить аренду офиса, если что-то пойдет не так. Зато в малых все более человечно.
Потом еще че-нить добавлю.
Мантра программиста
В промышленной разработке я постоянно сталкиваюсь с проблемой сложности систем. Например, проект достался в наследство. Сложность здесь - понять мысль предыдущих разработчиков. Или, когда разработка ведется с нуля, конкретные применения известных реализаций могут вгонять в ступор.
Сейчас я не буду писать, как предотвращать переусложенение систем. Расскажу, как найти силы бороться с текущей.
Это своего рода хакерская мантра:
Пределы сложности не бесконечны. Изучи задачи, которые решает проект, - и осознаешь систему.
Глобально фронтенд решает только одну задачу - рисовать интерфейс. Проект строится только для этого. Он бьётся на подзадачи, каждая из которой - решать глобальную задачу.
Например, Реакт отвечает за компонентный подход. Редакс - за хранение глобального стора. Апи - за работу с сетью. Тайпскрипт строго типизирует параметры подсистем.
Я открываю компонент - и ничего в нём не понимаю. Код абстрактен, незадокументирован, плохо проименован. Работать с ним сложно. Но я говорю себе: он выполняет узкий спектр задач. Я подхожу к нему как исследователь. Я думаю, какова его глобальная задача, на какие подсистемы он может быть разбит. Откуда могут приходить данные, как их дебажить.
И со временем удается все осознать.
В промышленной разработке я постоянно сталкиваюсь с проблемой сложности систем. Например, проект достался в наследство. Сложность здесь - понять мысль предыдущих разработчиков. Или, когда разработка ведется с нуля, конкретные применения известных реализаций могут вгонять в ступор.
Сейчас я не буду писать, как предотвращать переусложенение систем. Расскажу, как найти силы бороться с текущей.
Это своего рода хакерская мантра:
Пределы сложности не бесконечны. Изучи задачи, которые решает проект, - и осознаешь систему.
Глобально фронтенд решает только одну задачу - рисовать интерфейс. Проект строится только для этого. Он бьётся на подзадачи, каждая из которой - решать глобальную задачу.
Например, Реакт отвечает за компонентный подход. Редакс - за хранение глобального стора. Апи - за работу с сетью. Тайпскрипт строго типизирует параметры подсистем.
Я открываю компонент - и ничего в нём не понимаю. Код абстрактен, незадокументирован, плохо проименован. Работать с ним сложно. Но я говорю себе: он выполняет узкий спектр задач. Я подхожу к нему как исследователь. Я думаю, какова его глобальная задача, на какие подсистемы он может быть разбит. Откуда могут приходить данные, как их дебажить.
И со временем удается все осознать.
Функциональное программирование. Начало.
Прежде всего - зачем?
1. React в 2021 - функциональный.
2. RxJs и в целом реактивный подход - это функциональщина. В наше время реактивность наращивают популярность.
3. В целом во фронте используется смешанный стиль. И хотелось бы части, написанные на ФП, писать более профессионально.
Прежде всего - зачем?
1. React в 2021 - функциональный.
2. RxJs и в целом реактивный подход - это функциональщина. В наше время реактивность наращивают популярность.
3. В целом во фронте используется смешанный стиль. И хотелось бы части, написанные на ФП, писать более профессионально.
Quick Sort. Часть 1: зачем?
В серии постов я расскажу, для чего нужен алгоритм быстрого поиска. Также напишу код, для понятности разбитый на четыре части, и прокомментирую каждую часть. Наконец, расскажу про оптимизации.
Я считаю, что для большинства собеседований и leetcode достаточно знать этот алгоритм. Он хорош для сортировки больших объемов данных с разными значениями. Для уже отсортированных массивов либо тех, где значения часто повторяются, алгоритм можно оптимизировать.
Временная сложность этого алгоритма:
Худший случай: O(n2).
Средний: O(n log n).
Лучший: O(n log n).
Сложность алгоритма по памяти:
С сортировкой на месте: O(log n).
Без: O(n).
Обычно его делают через рекурсию. Итеративный метод со стеком любопытные могут погуглить или попросить о нем ИИ.
В следующем посте опишу декомпозицию алгоритма для лучшего осознания и запоминания.
В серии постов я расскажу, для чего нужен алгоритм быстрого поиска. Также напишу код, для понятности разбитый на четыре части, и прокомментирую каждую часть. Наконец, расскажу про оптимизации.
Я считаю, что для большинства собеседований и leetcode достаточно знать этот алгоритм. Он хорош для сортировки больших объемов данных с разными значениями. Для уже отсортированных массивов либо тех, где значения часто повторяются, алгоритм можно оптимизировать.
Временная сложность этого алгоритма:
Худший случай: O(n2).
Средний: O(n log n).
Лучший: O(n log n).
Сложность алгоритма по памяти:
С сортировкой на месте: O(log n).
Без: O(n).
Обычно его делают через рекурсию. Итеративный метод со стеком любопытные могут погуглить или попросить о нем ИИ.
В следующем посте опишу декомпозицию алгоритма для лучшего осознания и запоминания.
Quick Sort. Часть 2: декомпозиция.
Речь пойдет об алгоритме с сортировкой на месте и средним элементом массива в качестве опорного.
Для удобства понимания делю на четыре части: |
- quickSort,
Главная функция.
Принимает и возвращает массив. Вызывает вспомогательную функцию. Опционально проверяет массив на минимальную длину.
Сигнатура:
- quickSortHelper.
Вспомогательная функция, вызываемая внутри quickSort.
Принимает три аргумента: массив и начальные указатели.
Сигнатура:
- partition.
Функция, сортирующая массив и подмассивы.
Определяет опорный элемент. С помощью двух указателей бежит по массиву и переставляет его элементы.
Сигнатура:
- swap.
Переставляет элементы массива.
Сигнатура:
В следующем посте напишу реализацию части алгоритма с комментариями.
Речь пойдет об алгоритме с сортировкой на месте и средним элементом массива в качестве опорного.
Для удобства понимания делю на четыре части: |
- quickSort,
Главная функция.
Принимает и возвращает массив. Вызывает вспомогательную функцию. Опционально проверяет массив на минимальную длину.
Сигнатура:
(array) => array (sorted);
- quickSortHelper.
Вспомогательная функция, вызываемая внутри quickSort.
Принимает три аргумента: массив и начальные указатели.
Сигнатура:
(array, leftIndex, rightIndex) => void;
- partition.
Функция, сортирующая массив и подмассивы.
Определяет опорный элемент. С помощью двух указателей бежит по массиву и переставляет его элементы.
Сигнатура:
(array, leftIndex, rightIndex) => leftIndex;
- swap.
Переставляет элементы массива.
Сигнатура:
(array, leftIndex, rightIndex) => void;
В следующем посте напишу реализацию части алгоритма с комментариями.
Quick Sort. Часть 3: реализация.
1. Напишем главную функцию:
4. Наконец, напишем swap:
1. Напишем главную функцию:
const quickSort = (arr) => {2. Теперь напишем функцию quickSortHelper:
if(arr.length < 2) { // если длина массива меньше 2, запуск алогоритма не имеет смысла - вернем сам массив.
return arr;
}
quickSortHelper(arr, 0, arr.length - 1); // изначальные аргументы всегда 0 и arr.length-1. Они означают первый и последний индексы в массиве.
return arr; // массив сортируется на месте и возвращается после всех рекурсивных вызовов.
}
const quickSortHelper = (arr, left, right) => {3. Теперь - функция partition:
if(left >= right) { // условие для выхода из рекурсии.
return arr;
}
const partitionIndex = partition(arr, left, right); // вызывается для каждого массива, начиная с изначального и заканчивая единичными.
quickSortHelper(arr, left, partitionIndex-1); // вызов для левой части с числами меньше опорного элемента.
quickSortHelper(arr, partitionIndex, right); // вызов для правой части с числами больше опорного элемента.
}
const partition = (arr, left, right) => {
const pivot = arr[Math.floor((left + right) / 2)]; // опорный элемент определяем как средний. Опорный элемент - значит, тот, с которым сравниваются все остальные элементы при сортировке.
while (left <= right) { // двигаем указатели, пока они не сойдутся.
// cравниваем с опорным элементом и передвигаем указатель до тех пор, пока не выполнится условие.
while(arr[left] < pivot) {
left++;
}
// то же, что в предыдущем случае, но после левого указателя.
while(arr[right] > pivot) { // то же, что в предыдущем случае.
right--;
}
// если элементы равны, просто передвигаем указатели, не вызывая swap. Это немного оптимизирует алгоритм.
if(arr[left] === arr[right]) {
left++;
right--;
continue; // важно, чтобы не выполнился swap.
}
swap(arr, left, right)
}
return left;
}
4. Наконец, напишем swap:
const swap = (arr, left, right) => {
[arr[left], arr[right]] = [arr[right], arr[left]];
}
Quick Sort. Часть 4: оптимизации и тест-кейсы.
Версия выше использует несколько оптимизаций:
- сортировка на месте с перестановками: позволяет не создавать новые массивы на каждом рекурсивном вызове, экономя память.
- выбор среднего элемента как опорного: помогает избегать худших случаев, ускоряя алгоритм для уже отсортированных массивов.
- пропуск перестановок для одинаковых элементов: ускоряет сортировку для данных с большим количеством одинаковых данных.
Что еще можно использовать:
- выбор случайного опорного элемента.
- выбор опорной медианы из первого, последнего и среднего элементов.
Теперь соберите алгоритм сами и попробуйте на тест-кейсах :)
В качестве бонуса - алгоритм без оптимизаций:
Версия выше использует несколько оптимизаций:
- сортировка на месте с перестановками: позволяет не создавать новые массивы на каждом рекурсивном вызове, экономя память.
- выбор среднего элемента как опорного: помогает избегать худших случаев, ускоряя алгоритм для уже отсортированных массивов.
- пропуск перестановок для одинаковых элементов: ускоряет сортировку для данных с большим количеством одинаковых данных.
Что еще можно использовать:
- выбор случайного опорного элемента.
- выбор опорной медианы из первого, последнего и среднего элементов.
Теперь соберите алгоритм сами и попробуйте на тест-кейсах :)
export const cases = [
[],
[1],
[2, 1],
[1,2,3],
[3,1,2,4],
[5,4,3,2,1],
[116, 90, 89, 70, 65, 30, 25, 16, 9, 1, 4, 0, -1, -9],
[1, 27, 5, 16, 3, 11, 8],
[1, 27, 116, 9, 5, 16, 3, 2, 11, 8],
[9,8,5,2,1],
['string', { object: true }],
[undefined, undefined]
]
В качестве бонуса - алгоритм без оптимизаций:
const quickSort = (array) => {
if(array.length <= 1) {
return array;
}
const left = [];
const right = [];
const middleIndex = Math.floor(array.length / 2);
const pivot = array[middleIndex];
for(let i = 0; i < array.length; i++) {
if(i === middleIndex) {
continue;
}
if(typeof array[i] !== "number") return null;
if(array[i] > pivot) {
right.push(array[i]);
} else {
left.push(array[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)]
}
В чём разница между arr.length и arr.length-1?
Всё просто:
- arr.length возвращает общее количество элементов в массиве. Например, length для [1,2,3] будет 3.
Но можем ли мы использовать это значение как индекс последнего элемента массива? Нет! Ведь массивы индексируются с нуля.
Поэтому любой код, где нужен последний элемент массива, пользуется записью arr.length - 1.
Запомни это, чтобы избежать распространенной ошибки :)
Всё просто:
- arr.length возвращает общее количество элементов в массиве. Например, length для [1,2,3] будет 3.
Но можем ли мы использовать это значение как индекс последнего элемента массива? Нет! Ведь массивы индексируются с нуля.
Поэтому любой код, где нужен последний элемент массива, пользуется записью arr.length - 1.
Запомни это, чтобы избежать распространенной ошибки :)
Бинарный поиск. В чем суть?
Представьте линейку школьников, где дети выстроены по росту - от самых низких до самых высоких. Физрук ищет ученика определенного роста.
Первая мысль - измерить всех. Это метод brute force. Физруку придется поработать над каждым из учеников - даже над теми, кто точно выше или ниже нужного.
Вместо этого физрук прибегает к методу бинарного поиска:
1. Переходит в центр колонны.
2. Измеряет ученика. Если ученик подходит - заканчивает подбор. Если нет - смотрит, выше ученик или ниже.
3. Отбрасывает половину с теми, кто ниже или выше.
4. Переходит в середину оставшихся.
5. Повторяет цикл, пока не найдет нужного.
6. Если не находит - идет пить пиво :)
Это и есть алгоритм бинарного поиска. Всё просто!
Вот реализация на JS. Проследите, как в коде описаны шаги электронного физрука :)
Представьте линейку школьников, где дети выстроены по росту - от самых низких до самых высоких. Физрук ищет ученика определенного роста.
Первая мысль - измерить всех. Это метод brute force. Физруку придется поработать над каждым из учеников - даже над теми, кто точно выше или ниже нужного.
Вместо этого физрук прибегает к методу бинарного поиска:
1. Переходит в центр колонны.
2. Измеряет ученика. Если ученик подходит - заканчивает подбор. Если нет - смотрит, выше ученик или ниже.
3. Отбрасывает половину с теми, кто ниже или выше.
4. Переходит в середину оставшихся.
5. Повторяет цикл, пока не найдет нужного.
6. Если не находит - идет пить пиво :)
Это и есть алгоритм бинарного поиска. Всё просто!
Вот реализация на JS. Проследите, как в коде описаны шаги электронного физрука :)
const binarySearch = (array, target) => {
let left = 0;
let right = array.length-1;
while(left <= right) {
const pivotIndex = Math.floor((left + right) / 2);
let pivot = array[pivotIndex];
if(pivot === target) {
return pivotIndex;
}
if(pivot < target) {
left = pivotIndex+1;
}
if(pivot > target) {
right = pivotIndex-1;
}
}
return -1;
}