ЧАСТЬ 2

March 21, 2024

Аліса Лісняк

Full Stack веб-разработчик. Ментор учебной программы Full Stack JavaScript

Основные понятия ДжаваСкрипт

Основные понятия ДжаваСкрипт

JS – язык, поддерживающий много разных подходов к написанию кода, придающий ей дополнительной гибкости. Объектно-ориентированный подход (ООП) позволяет представлять все сущности данных в качестве объектов и манипулировать ими с помощью методов, а функциональный подход позволяет структурировать код как набор функций, взаимодействующих с другими функциями. Различные подходы можно также комбинировать для упрощения и оптимизации кода.

Синтаксис і структура

В целом JavaScript очень лаконичен в плане синтаксиса и языковых конструкций (в отличие от той же Java), и не требует постоянно следить за форматированием кода (отступами и пробелами), в отличие от Python.

Блоки кода заключаются в фигурные скобки ({ }) поэтому отследить, где начинается один блок и где он заканчивается - довольно просто. В конце команды ставится точка с запятой (;), и хотя ее отсутствие в определенных случаях не приведет к ошибке в коде, расставлять “;” является "хорошим тоном", а в некоторых случаях действительно необходимостью.

Динамическая типизация

Языки программирования бывают разные, и по-разному работают с данными. Некоторые языки имеют "жесткую типизацию" - это значит, что при создании определенной структуры данных мы обязаны определить тип данных, который собираемся в ней хранить, и в такую ​​структуру может помещаться значение только такого типа и больше никакого.

Другие же языки, среди которых есть и JavaScript, работают несколько иначе и имеют так называемую динамическую типизацию - когда тип данных переменной определяется в процессе работы с этим значением. Условно говоря, вы можете создать переменную и сначала сохранять в ней значение одного типа данных, а затем заменить его иным значением другого типа. Как обрабатывать значение, интерпретатор будет выяснять в процессе обработки кода - объект определяется его текущим набором методов и свойств, а не наследованием от определенного класса.

Иногда можно встретить другое название этого механизма. "утиная типизация"- которая происходит от шутливого "утиного теста". В оригинале он звучит так: «If it looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck» («Если оно выглядит, как утка, плавает, как утка и крякает, как утка, тогда вероятно это утка”).

Зачем JS утиная типизация? Для упрощения работы с объектами и уменьшения обязательных связей и подражаний между ними. Дело в том, что в джаваскрипте наследование реализуется за счет прототипов, что позволяет писать такие интерфейсы, которые способны работать с разными объектами. Это называется "полиморфизм" - когда единый интерфейс может использоваться для разнотипных сущностей, если они обладают определенными одинаковыми свойствами или методами.

Благодаря динамической типизации работа с данными на самом деле упрощается. Когда мы заранее не уверены, данные какого типа мы получим от пользователя на веб-странице или какие данные придут с сервера, возможность использовать переменные и объекты, сохраняя данные разных типов – это очень удобно.

Операции с данными

Утиная типизация упрощает написание кода, но и накладывает определенные ограничения. Например, операции со значениями разных типов будут автоматически конвертироваться («приводиться») в один тип данных. Если автоматически привести значение не получится, вместо ошибки результатом операции станет специальное значение. К примеру, при попытке перемножить число и букву, код не сломается, а результатом станет особое значение NaN (“not-a-number”).

А вот операция сложения может работать как обычный плюс для чисел, так и на склеивание строк (это называется конкатенация), и если хотя бы один из операндов окажется строкой, то результатом операции всегда будет строка:

Другие математические операторы (*, /, -) работают только с числами и при невозможности конвертировать операнды в число результатом станет Not-a-Number (NaN)

Функции

Функции – это краеугольный камень программирования. Если кратко, это изолированный блок кода, который может принимать определенные входные данные и выдавать результат по окончании своей работы. Входные данные называются параметрами функции, а исходящие – возвращаемым результатом.

Функции нужны для переиспользования одной и той же логической работы с разными аргументами. Например, мы имеем задачу: сложить числа 10 и 35 и умножить на 4.

Мы выполняем действие: (10 + 35) * 4. Результат будет равен 180.

А завтра нам нужно будет сложить 23 и 46 и умножить на 3. Выполняем действие: (23+46)*3=207

А еще впоследствии нужно сделать (67+45)*8=896.

Что общего в этих задачах? Действие, которое мы выполняем (сложение двух чисел и умножение на третье). Нам бы хотелось не делать одно и то же действие много раз, а построить механизм, который может выполнить одно и то же действие (или перечень действий) с разными входными данными. Такой механизм и есть функция. Она принимает входящие данные, производит логическую работу и возвращает результат.

Пример выше можно выразить в виде функции:

В JavaScript есть целых три синтаксиса для определения функций:

  1. Декларируемые функции (function declaration)
  2. Функциональные выражения (function expression)
  3. Стрелочные функции (arrow function)

Все три вида функций имеют одинаковый синтаксисы вызова: сначала указывается имя функции, далее круглые скобки, и в них перечисляются входные данные (параметры, ожидаемые функцией), то есть аргументы. Если аргументов не передается, то круглые скобки все равно обязательны. Аргументов может быть столько, сколько функции нужно.

Результат работы функции возвращается туда, откуда была вызвана функция.

Декларация функции (Function Declaration) выглядит вот так:

Обязательны ключевое слово “function”, название функции, круглые скобки (входных параметров может не быть, а вот скобки обязательны), а фигурные скобки определяют начало и конец блока кода функции.

Ключевое слово return возвращает значение в качестве результата и завершает работу функции.

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

Функциональные выражения (Function Expression) – это выражения вида:

В таком случае функция "присваивается" переменной по ссылке. Особенностью такой разновидности функций является то, что вызывать такую ​​функцию можно только ниже по коду после ее определения.

Стрелочные функции (Arrow Function) - это разновидность функциональных выражений, в которой опускается ключевое слово "function", но после списка параметров обязательно должна стоять "стрелка" следующего вида: =>

Такие функции, как и функциональные выражения, могут вызываться только ниже в коде после определения, а кроме этого имеют отличие от всех других видов функций - не имеют собственного "контекста исполнения", присутствующего в function declaration и function expression, поэтому при исполнении получают контекст окружения. Эта особенность бывает очень полезна при использовании методов коллекций. Детальнее об этом - чуть позже.

Отдельно можно выделить"функции немедленного вызова" (Immediately Invoked Function Expression — IIFE) – они не являются отдельной разновидностью, это просто функция, которая заключена в оператор группировки (круглые скобки) и сразу вызывается. Выглядит это так:

Особенностью этой записи является то, что у функции нет имени, она вызывается сразу, когда интерпретатор ее видит и она одноразовая, то есть ее невозможно вызвать повторно. Поскольку в большинстве случаев функции нужны нам для многократного пере-использования логики с разными входными данными, IIFE в современном коде используются крайне редко.

Объекты и массивы

JavaScript имеет 7 примитивных типов данных (string, boolean, number, null, undefined, Symbol, BigInt) и отдельно тип Object. По сути, все что не является примитивами – является объектом (даже функции).

Объект – это структура данных, обладающая свойствами и методами. Свойства – это пары из ключа и значения, а методы – это функции, связанные именно с этим объектом. Например, если мы захотим описать объект машины, которая имеет красный цвет, объем бака 40 литров и умеет делать “вжум-вжум”, то в коде это можно выразить так:

В данном примере color, fuelVolume будут свойствами (или их еще называют "полями") объекта, а функция go() - методом (функцией) объекта. Эта функция не существует в отрыве от объекта myCar и может быть вызвана только с его помощью:

Особым отличием джаваскрипт-объектов от примитивных значений является то, что примитивы копируются и сравниваются по самому значению. К примеру:

При сравнении переменных по этому примеру они будут сравниваться по значению: здесь 5 и здесь 5, значит одинаковые.

С объектами это будет работать по-другому. Рассмотрим пример:

При создании объекта в переменную cat1 помещается только адрес этого объекта в памяти, соответственно при копировании объекта копируется только адрес, а сам объект находится где-нибудь в памяти по этому адресу.

Изменяя значение в объекте по его адресу, мы изменяем объект в памяти, а ссылка на эту область памяти будет одинаковой для переменных cat1 и cat2.

При сравнении объектов действительно сравниваются адреса в памяти

Массивы

Все структуры, кроме переменных и примитивов в JavaScript представляют собой объекты. И массивы – не исключение. Массив – это объект, у которого в качестве ключей выступают индексы элементов – порядковый номер (а нумерация в программировании всегда идет с нуля), и значения могут быть любого типа. Например, массив [12, 5, привет, true]. У элемента под индексом 0 будет значением число 12, с индексом 1 – число 5, с индексом 2 – «привет» и с индексом 3 – значение true.

В джаваскрипте массивы динамичны, поэтому при создании не обязательно указывать, какого размера планируется массив, а также в него можно добавлять или удалять элементы.

Как и любые объекты в js, массивы имеют свойства и методы для работы с ним. Например, в свойстве length сохраняется текущий размер массива (количество элементов), а с помощью методов можно проводить разную работу над элементами массива – join способен склеить все элементы массива в строку, push – добавить новый элемент в конец массива, а reverse разворачивает массив задом наперед .

Методы массива можно разделить на две группы - те, которые в процессе выполнения создают новый массив и возвращают ссылку на него (а старый в своей ячейке памяти остается неизменным) (к таким относятся concat, filter, map) и работающие с начальным массивом и мутирующие его, то есть они изменяют сам массив в памяти (среди таких методов есть reverse, push, pop, shift, unshift и другие).