Как перестать его бояться и всё-таки освоить
Сегодня мы говорим про Джаваскрипт: боль и страх новичков, а на самом деле зря они к нему так относятся. Поехали!
Зачем нужны языки программирования? Программирование это эм, программирование компьютера на выполнение нужной вам программы. Программы пишутся на языке программирования и подчиняются алгоритму.
Пример из реальной жизни: вы каждый день просыпаетесь, чистите зубы, завтракаете, идёте в метро, едете на работу, работаете работу, возвращаетесь обратно. Всё это — алгоритм, вы выполняете программу. Ваш каждый день (если он такой) — детерминирован, то есть предсказуем.
То же самое и с программами: вы заставляете компьютер отработать программу, которую вы написали. Заставляете через код, который пишете на каком-то языке программирования.
Бывают простые скрипты, которые ни от чего не зависят: например, парсинг сайта. Вы написали скрипт на каком-нибудь Nightmare.js, он зашёл на нужный сайт, спарсил вам нужные данные, отдал. Никакого взаимодействия нет, тупой скрипт с тупым алгоритмом.
Или те же базы данных: написал скрипт (на SQL), который либо отдаёт данные, либо обновляет, ну и готово. Примитивно, конечно, но идею вы понимаете.
Фронтэнд (да и бэкэнд, да и мобильные приложения, да и многое другое) отличаются интерактивностью: пользователь взаимодействует с программой и она выполняет выражения.
В любом случае всё сводится к данным: либо с ними производится работа (обновление), либо их отображают.
Но что такое данные? Это информация. Но, к сожалению, компьютеры не очень умны и неспособны распознать данные разных типов, поэтому приходится этим заниматься разработчикам. Эх, побыстрее бы пришёл AI и научился парсить что угодно!
Типы бывают разные: от простейших типа чисел до сложных типа монад. Джаваскрипт в этом плане унылый язык, да и типизация там нестрогая — как прочтёте урок, посмотрите Wat. Например, если вы попробуете выполнить '1' + 2
, то Джс выдаст '12'
, а Хаскелль (язык со строгой типизацией) — ошибку No instance for (Num Char) arising from a use of ‘+’
и будет прав.
Давайте разберём типы (кстати, это первая ссылка на MDN (Mozilla Developer Network) — наряду с Google Developers For Web это один из основных источников информации).
Число, например, 1
или 123782
.
Дробное число, 1.24
или 3.142324221
,
Строка, в основном про текст, например, 'hello world'
(могут использоваться и двойные кавычки "", и ``).
Массив — это список элементов, упорядоченных по индексу, начиная с нуля. Представьте себе стопку книг Большой Советской Энциклопедии из 30 томов. За каждый элемент массива возьмём название тома:
['1-ый том', '2-ой том', '3-ий том', ..., '30-ый том']
Первый том будет под нулевым индексом, 30-ый — под двадцать девятым. Обратиться к элементу массива можно по индексу.
В массиве могут быть данные разных типов, можно даже смешивать, но не рекомендую — сложно работать с массивом, где всё намешано как в помойке.
Мощный тип данных, который можно представить как пару "ключ" — "значение". Если в массиве у нас элементы идут в чётком порядке, то в объекте мы можем сами задавать ключ, по которому будем обращаться.
{
hello: "world",
items: [0, 1, 2, 3, 4],
mySuperNumber: 1,
myFloatNumber: 0.24,
myNestedObject: {
hello: "again"
}
}
Пустой тип данных. Как черная дыра. no value, если цитировать.
Уточню, что null по типам относится к объектам. Джаваскрипт 🤷
Незаданный тип данных. Отличие от null
в том, что мы объявили константу или переменную, но значение ей ещё не задали, а null
может как раз показывать, что значения нет.
Например, в вашем приложении два состояния: только что открытая страница (ещё незагруженные данные) и запрошенные данные с сервера. Нам нужно отобразить два состояния картинки: если мы ещё не загружали данные, то будет undefined
, если загрузили и сервер сказал, что её нет, то null
.
Вот ещё пример: у вас в данных могут быть как null
, так и undefined
:
{
"id": 1,
"name": "Evgeny Rodionov"
}
{
"id": 1,
"name": "Evgeny Rodionov",
"city": null
}
В первом случае пользователь ещё не задал город, во втором — уже указал, что не хочет его указывать.
Классы это грубо говоря (очень грубо говоря) объект с данными и функциями (они называются методами).
class Greeting {
sayThis(toWhom) {
return `Hello ${toWhom}!`;
}
}
// вызываем
new Greeting().sayThis("and fuck you"); // Hello and fuck you!
(да, я не смог перевести на русский)
Это тип данных, который отвечает за истинность: бывает либо true
, либо false
. Допустим, если вы сравниваете выражения друг с другом, то это сравнение приведется к булеану:
42 === 42; // true
42 === "42"; // false
42 == "42"; // true (потому что `==` это слабое сравнение, которое не учитывает типы — поэтому его нельзя использовать https://eslint.org/docs/rules/eqeqeq.html)
Как со всем этим работать? Прикольно, конечно, что есть какие-то данные, но дальше-то что?
А дальше у нас работа с этими данными через выражения. Выражения — это второй столп программирования, потому что весь код состоит из них. Код и есть выражения.
Но, конечно же, эти выражения должны быть как-то структурированы! Нельзя же просто написать файл my-awesome-site.js
и туда в полотно накидать 10 тысяч строк, верно же? Давайте поговорим про то, как структурируют код.
Неизменяемая ссылка на значение. Тут важно каждое слово:
- неизменяемая: заменить нельзя,
- ссылка: не само значение, а только ссылка на него.
Объявляется через const [название] = [значение]
, например
const x = "hello world";
Почему я сделал акцент на ссылке?
Если вы попытаетесь сделать x = 'fuck this'
, то вам выдаст ошибку: Uncaught TypeError: Assignment to constant variable.
.
Но если вы попытаетесь что-нибудь сделать с массивом или объектом, то всё пройдёт нормально.
const x = [0, 1, 2, 3];
x.push(4); // [0, 1, 2, 3, 4]
Это нюанс работы const
: нельзя изменять ссылку, но можно менять значение в ней.
Переменные названы так, потому что их значения можно сменить:
let x = "hello world";
// или var x = "hello world"
x = "fuck this";
Отличие let
от var
в области видимости. Об этом поговорим позже.
Когда использовать const
, а когда let
?
Всё просто: код должен быть предсказуемым. Сравните два сценария:
- при
const
: о, здесьX
равен42
, - при
let
илиvar
: так, здесьX
равен42
, НО ТЕПЕРЬ НУЖНО ПРОВЕСТИ РАССЛЕДОВАНИЕ И ПОНЯТЬ, ГДЕ ОН СТАНОВИТСЯ НЕ42
.
Предсказуемости мало.
Всегда использовать const
и никогда — let
и var
Операторы это специальная конструкция языка и они знакомы вам из математики: +
, -
, >
, <
. Складывание, вычитание, сравнение (которое, как вы помните, приводятся к булеанам).
Если вам нужно объединить несколько сравнений, то есть логические операторы И, ИЛИ и НЕ:
if (x && y) {
// блок кода который отработает если будет true
}
Вы заметили {}
? Это блок кода. И это следующая важная часть языка: каждый код (или выражения) можно сгруппировать в блок. Часто это используется в сравнениях как в примере выше, а ещё чаще — в функциях.
Функции это блок кода, в который можно передать аргументы и который может вернуть результат. Пример:
function sayHello(toWhom) {
const message = "Hello, " + toWhom;
return message;
}
sayHello("Evgeny"); // Hello, Evgeny.
Давайте разбирать?
function
— резервное слово, которое означает, что дальше будет код;sayHello
— название функции,()
— список аргументов, в нашем случае он один (toWhom
),const message = "Hello, " + toWhom
— вычисление выражения"Hello, " + toWhom
(тут используется оператор+
для конкатенации (складывания) строк) в константуmessage
;return message
— возврат значения, которое находится под константойmessage
;sayHello('Evgeny')
— вызов функции с одним аргументом'Evgeny'
(где'Evgeny'
это строка, без кавычек это было константой или переменной).
Функции бывают разные:
// Function Declaration
function f(...arguments) {}
// Function Expression
const f = function(...arguments) {};
// Named Function Expression
const f = function func(...arguments) {};
// Arrow functions (ES2015+)
const f = (...arguments) => {};
В чём отличие? Это то, что у вас будут спрашивать на собеседованиях и то, что вам нужно помнить, чтобы не выстрелить себе в ноги.
- FD — можно объявить ниже вызова, потому что создаётся на этапе парсинга (разбора программы);
- FE — нельзя объявить, потому что создаётся когда код отработает на строчке с этой функцией;
- NFE — внутри функции можно обратиться к функции по её имени
func
. - AF — когда вам нужно чтобы
this
был от родителя, а не от функции
Что когда использовать? Да без разницы, любой адекватный линтер запрещает писать говнокод и объявлять функции позже её использования — в этом самая большая проблема Function Declaration.
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>JavaScript is like LSD
— Andrzej Krzywda (@andrzejkrzywda) January 19, 2016
You have no idea where you are and what "this" even means.
this
это специальный объект, в котором находится то, что доступно этому блоку кода. Это часто используется в классах (чтобы обратиться к методу класса):
class MyTestClass {
constructor() {
this.myField = 1;
}
getMyField() {
return this.myField;
}
}
new MyTestClass().getMyField(); // 1
Джс построен на системе прототипов (об этом узнаете позже, когда подрастёте) и нюанс в том, что у каждого типа данных есть свой набор методов.
Например, у массива есть map
, filter
, reduce
и куча других методов. Они бывают мутабельными и иммутабельными: мутабельные изменяют изначальный массив, а иммутабельный возвращает новый. Разберём на примерах?
// заведём массив
const fruits = ["apple", "orange", "pear"];
// заведём функцию, которая разворачивает строку
// в Джсе, конечно же, такой функциональности по-умолчанию нет
// https://stackoverflow.com/a/959004/2389634
function reverseString(string) {
// мы вызываем метод .split() у строки — он строку превращает в массив с символом
// затем разворачиваем массив через метод .reverse()
// и собираем обратно через метод массива .join() — он превращает массив в строку
return string
.split("")
.reverse()
.join("");
}
// пройдёмся по нему мапом, чтобы получить новый массив и положить его в reversedFruits
const reversedFruits = fruits.map(function(fruit, index) {
// .map это функция, в неё аргументом передаётся другая функция,
// в которую приходят два аргумента — элемент и индекс
// эта функция вызывается на каждом элементе массива
// и эта функция должна вернуть новый элемент, который будет доступен по ключу
return reverseString(fruit);
});
// что у нас в reversedFruits?
console.log(reversedFruits); // ["elppa", "egnaro", "raep"]
Я рекомендую пройтись по MDN и прочитать про методы встроенных типов: String, Number, Float, Array, Object.
Урок получился насыщенным: больше полутора тысяч слов в нём, но вот что я бы хотел сказать.
Нам этот урок нужен как фундамент для будущих: вы должны знать немного Джаваскрипта, чтобы не плавать в Реакте.
Достаточно ли его? Да, ведь прежде чем придти к теории категорий, вы должны знать про числа и операторы сложения-умножения-деления-вычитания, базовую арифметику, в общем.
Очень часто люди думают что надо прочитать какую-нибудь книжку (часто советуют YDKJS или learn.javascript.ru) или пройти курс по "чистому" Джаваскрипту, причём обязательно нужно это сделать до изучения каких-либо библиотек или фреймворков!
Нюанс в том, что Джаваскрипт не очень сложный язык. Да, у него есть подводные камни (и мы их разберём обязательно), но по моей практике достаточно знать того, что выше, чтобы писать на Реакте — а как вы помните из предыдущего урока, Реакт это чистый Джаваскрипт (а не шаблонизатор), поэтому опыт вы наработаете на реальном проекте. Это не так скучно, как в консоли долбить alert('Hello world')
и намного полезнее.
Поработайте джуном или просто на фрилансе пару месяцев, попрактикуйтесь на реальных задачах, а потом — зашлифуйте свои знания на Хекслете, говорят, у них сильная фундаменталика (лично я сам не пробовал). Но помните, что без практики она будет лишь в тягость и непонятной.