JavaScript для Python-программистов: познание через сравнение
by matyushkinТекст представляет собой сокращённый (но всё равно очень подробный) перевод большого руководства Бартоша Зачиньского Python vs JavaScript for Pythonistas.
***
Если вы серьёзно занимаетесь веб-разработкой, в какой-то момент вы неизбежно столкнётесь с JavaScript. Год за годом многочисленные опросы показывают, что JavaScript является одним из самых популярных языков программирования в мире с большим и растущим сообществом разработчиков. Как и Python, современный JavaScript можно использовать практически везде, включая интерфейсы, серверную часть, десктопные и мобильные приложения, Интернет вещей (IoT).
Из этой статьи вы узнаете:
- Что общего у Python и JavaScript и чем они отличаются.
- Как выбрать язык под вашу задачу.
- Как пишутся и выполняются программы на JavaScript.
- Как генерировать динамический контент веб-страниц.
- Из чего состоит экосистема JavaScript.
- Как избежать распространённых ошибок работы с JavaScript.
Первый взгляд на JavaScript
Если вы уже знакомы с происхождением JavaScript или просто хотите увидеть код в действии, смело переходите к следующему разделу. В противном случае вас ждёт краткий урок истории, который проведёт вас через этапы эволюции JavaScript.
О названии
Изначально JavaScript назывался Mocha (читается «мо́кка», это американский вариант написания имени кофейного напитка), затем был переименован в LiveScript и, наконец, незадолго до выпуска было выбрано название JavaScript. В то время Java была многообещающей веб-технологией, но была сложноватой для технически неподкованных веб-мастеров. Поэтому JavaScript задумывался, как удобный для начинающих язык дополнения Java-апплетов.
Интересный факт
Java и JavaScript были выпущены в 1995 году. Python тогда было уже пять лет.
Чтобы ещё больше сбить всех с толку, Microsoft для использования в Internet Explorer 3.0 разработала собственную версию языка, которая из-за отсутствия лицензионных прав была названа JScript.
Хотя Java и JavaScript имеют несколько общих черт, обусловленных C-подобным синтаксисом, сегодня они используются для совершенно различных целей.
ECMAScript
JavaScript был разработан в первые дни интернета небольшой компанией Netscape. Чтобы отвоевать рынок у Microsoft и снизить различия между веб-браузерами, Netscape необходимо было стандартизировать их язык. После отказа международного консорциума World Wide Web (W3C) они обратились за помощью к ECMA – европейскому совету по стандартизации.
ECMA определила формальную спецификацию языка под названием ECMAScript, потому что имя JavaScript уже было зарегистрированным товарным знаком Sun Microsystems. JavaScript стал одной из реализаций спецификации, которую он изначально вдохновил.
Пояснение
Другими словами, JavaScript соответствует спецификации ECMAScript. Другим известным членом семейства ECMAScript является язык ActionScript, который используется на платформе Flash.
Хотя отдельные реализации спецификации в некоторой степени соответствовали ECMAScript, в браузерах они поставлялись с дополнительными проприетарными API. Это привело к различному отображению веб-страниц в разных браузерах и появлению таких библиотек, как jQuery.
То есть существуют и другие X-Scripts?
Сегодня JavaScript остаётся единственным языком программирования, поддерживаемым всеми веб-браузерами. Это своеобразный лингва франка Интернета, однако продолжаются попытки заменить или дополнить JavaScript другими технологиями:
- Насыщенные интернет-приложения (Rich Internet Applications, RIA): Flash, Silverlight, JavaFx
- Транспайлеры: Haxe, Google Web Toolkit, pyjs
- Диалекты JavaScript: CoffeScript, TypeScript
Эти попытки были вызваны не только личными предпочтениями, но и ограничениями веб-браузеров до появления HTML5. В те дни было нельзя использовать JavaScript для сложных вычислительных задач – рисования векторной графики или обработки звука.
Rich Internet Applications предполагают встраивание технологий в браузер с помощью плагинов. Они отлично подходят для игр и обработки медиа, но, к сожалению, большинство из них проприетарны. Некоторые имеют уязвимости безопасности или проблемы с производительностью на отдельных платформах. Наконец, все они сильно ограничивают возможности поисковых систем индексировать страницы, созданные с помощью этих плагинов.
Примерно в то же время появились транспайлеры, позволившие автоматизировать перевод других языков на JavaScript. Это сделало входной барьер для фронтенд-разработки намного ниже, потому что внезапно бэкенд-инженеры смогли использовать свои навыки в новой области. Однако недостатками стала ограниченная поддержка веб-стандартов и громоздкая отладка транспилируемого кода.
Примечание
В то время как компилятор переводит понятный человеку код, написанный на языке программирования, в машинный код, транспилятор переводит программу с одного языка на другой.
Чтобы перевести код Python на язык, понятный браузеру, можно использовать транспилятор, например Transcrypt или pyjs. Другой вариант – использовать такой инструмент, как Brython, запускающий упрощённую версию интерпретатора Python на чистом JavaScript. Но в придачу к удобствам использования знакомого языка будет низкая производительность и потеря совместимости кода.
Транспиляция привела к появлению множества новых языков, ставящих целью замену JavaScript и устранения его недостатков. Так появились CoffeeScript, созданный около десяти лет назад, или Google Dart, который, согласно GitHub, стал самым быстрорастущим языком в 2019 году. Ярким представителем является TypeScript, который в последние годы приобрёл большую популярность. Это полностью совместимый с JavaScript язык, который добавляет проверку статического типа.
Однако, так как JavaScript является своеобразным «ассемблером Интернета», вряд ли он исчезнет с арены в ближайшее время.
Стартовый набор для работы с JavaScript
Одно из первых сходств, которое вы заметите при сравнении Python с JavaScript, заключается в том, что барьеры входа для обоих языков довольно низкие. Для JavaScript единственным начальным требованием является наличие веб-браузера 🙂. Такая доступность только способствует популярности языка.
Инструменты веб-разработчика (Web Developer Tools)
Просматриваея эту страницу на десктопе, вы можете воспользоваться инструментами веб-разработчика, которые в современных веб-браузерах имеют схожие возможности. Для их вызова обычно используется одна из следующих комбинаций клавиш:
F12
Ctrl + Shift + I
Cmd + Option + I
Эта функция может быть по умолчанию отключена, если вы используете Apple Safari или Microsoft Edge. После активации инструментов веб-разработчика вы увидите множество вкладок и панелей инструментов с содержимым, подобным следующему:
Это мощная среда разработки, оснащённая отладчиком JavaScript, средствами профилирования, диспетчером сетевого трафика и многим другим. Есть даже удалённый отладчик для физических устройств, подключённых через USB-кабель!
Пока мы просто сосредоточимся на консоли, к которой можно получить доступ, щёлкнув вверху вкладку Console
. Она также показывается в нижней части окна на вкладках других инструментов при нажатии клавиши Esc
.
Консоль в основном применяется для проверки журналируемых сообщений, отправляемых текущей веб-страницей, но она же может служить отличным пособием для изучения JavaScript. Так же, как в интерактивном интерпретаторе Python, код JavaScript можно вводить прямо в консоль, чтобы он выполнялся на лету:
В консоли есть все, что мы ожидаем от типичного инструмента REPL. В частности, подсветка синтаксиса, контекстное автодополнение, история команд и возможность рендеринга интерактивных элементов. Возможности рендеринга особенно полезны для анализа объектов и табличных данных, для перехода к исходному коду из трассировки стека или просмотра элементов HTML.
Регистрировать пользовательские сообщения в консоли, можно, используя предопределённый объект JavaScript console.log()
– эквивалент функции print()
в Python:
console.log('hello world');
Это заставит сообщение появиться на вкладке консоли в инструментах веб-разработчика. Далее в этом пособии мы будем использовать консоль для запуска кода на JavaScript.
HTML-документ
Наиболее естественно для кода JavaScript находиться где-то рядом с документом HTML, которым он обычно манипулирует. На JavaScript из HTML можно ссылаться тремя различными способами:
Метод | Пример кода |
Атрибут HTML-элемента | <button onclick="alert('hello');">Click</button> |
Тег HTML <script> | <script>alert('hello');</script> |
Внешний файл | <script src="/path/to/file.js"></script> |
Можно использовать любой из методов. Первый и второй методы встраивают код JavaScript непосредственно в HTML-документ. Хотя это и удобно, нужно стараться отделять императивный JavaScript от декларативного HTML.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Домашняя страница</title>
</head>
<body>
<p>Это результат загрузки страницы. Загляните в консоль, там будет сумма 2 и 3.</p>
<script>
function add(a, b) {
return a + b;
}
console.log(add(2, 3));
</script>
</body>
</html>
Важно то, как веб-браузеры обрабатывают документы HTML. Документ читается сверху вниз. При обнаружении тега <script>
он сразу же запускается даже до полной загрузки страницы. Если скрипт попытается найти элементы HTML, которые ещё не были отрисованы, мы получим ошибку. Поэтому теги <script>
обычно располагают внизу тела документа. Перемещая эти теги вниз, вы позволяете пользователю увидеть страницу до начала загрузки файлов JavaScript.
Загрузку внешних файлов JavaScript можно отложить до загрузки страницы, добавив атрибут defer
:
<script src="https://server.com/library.js" defer></script>
Node.js
Коду JavaScript не обязательно выполняться в веб-браузере. Инструмент под названием Node.js обеспечивает среду выполнения серверного JavaScript.
Среда выполнения включает в себя движок JavaScript, а также API для взаимодействия. Существует несколько альтернативных движков, поставляемых с различными веб-браузерами:
Браузер | Движок JavaScript |
Apple Safari | JavaScriptCore |
Microsoft Edge, Google Chrome | V8 |
Mozilla Firefox | SpiderMonkey |
Microsoft IE | Chakra |
Каждый из них реализован и поддерживается его поставщиком. Для конечного пользователя заметной разницы нет, за исключением характеристик отдельных составляющих. Node.js использует движок V8, разработанный Google для своего браузера Chrome.
После установки Node.js, мы можем выполнять код JavaScript так же, как с интерпретатором Python:
Запуск команды node в терминале
$ node
> 2 + 2
4
Это похоже на консоль веб-разработчика, которую мы видели ранее. Однако, как только мы попытаемся сослаться на что-то, связанное с браузером, мы получим сообщение об ошибке:
Shell
> alert('hello world');
ReferenceError: alert is not defined
В среде выполнения отсутствует API браузера. В то же время Node.js предоставляет набор API-интерфейсов, которые полезны в бэкенд-приложении, например, API файловой системы:
Shell
> const fs = require('fs');
> fs.existsSync('/path/to/file');
false
Примечание
В целях безопасности API для работы с клиентскими файлами недоступны в браузере. Представьте себе, что какой-то случайный сайт может контролировать файлы на вашем компьютере!
Если стандартная библиотека не отвечает потребностям, вы всегда можете установить сторонний пакет с помощью диспетчера npm, поставляемого со средой Node.js. Публичный реестр npm похож на индекс пакетов Python (PyPI).
Подобно команде python
, программы на JavaScript в терминале можно запускать с помощью команды node
:
Shell
$ echo "console.log('hello world');" > hello.js
$ node hello.js
hello world
JavaScript vs Python
В этом разделе мы сравним Python и JavaScript с точки зрения питониста. Здесь будут некоторые новые концепции, но мы также обнаружим некоторые сходства языков.
Применение
Python – это универсальный, мультипарадигменный, кросс-платформенный, интерпретируемый язык программирования высокого уровня с богатой стандартной библиотекой и доступным синтаксисом. Python используется в широком спектре областей, в том числе для написания сценариев и автоматизации, создания прототипов, тестирования программного обеспечения, веб-разработки, программирования встроенных устройств и научных вычислений.
JavaScript, с другой стороны, зародился как клиентский язык сценариев, делающих HTML-документы более интерактивными. Он намеренно прост и имеет особую направленность: добавление поведения в пользовательский интерфейс. Это всё ещё верно сегодня, несмотря на его улучшенные возможности. С помощью JavaScript вы можете создавать не только веб-приложения, но также настольные и мобильные приложения. Индивидуальные среды выполнения позволяют выполнять JavaScript-код на сервере или даже на IoT-устройствах.
Философия
В Python сделан акцент на удобочитаемости и удобстве сопровождения кода. Язык даже накладывает ограничения на форматирование кода. Большинство операторов в Python – это английские слова. Некоторые люди шутят, что Python, благодаря его простому синтаксису, является исполняемым псевдокодом.
Как вы узнаете позже, JavaScript предлагает гораздо бóльшую гибкость, вызывающую и большее количество способов создания проблем. Например, в JavaScript нет единственного правильного способа создания пользовательских типов данных. Кроме того, даже если новый синтаксис устраняет проблему, язык должен оставаться обратно совместимым со старыми браузерами.
Версии
До недавнего времени на официальном сайте можно было скачать две несовместимые версии Python. Этот разрыв между Python 2.7 и Python 3.x вводил в заблуждение новичков и стал основным фактором, замедляющим внедрение последней ветки разработки.
В январе 2020 года, после нескольких лет отсрочки, поддержка Python 2.7 была окончательно прекращена. Но ещё многие проекты не перенесены на Python третьей версии.
Брендан Айх создал JavaScript в 1995 году, но известный нам сегодня ECMAScript был стандартизирован два года спустя.
Обратите внимание на разрыв между ES3 и ES5, который длился целое десятилетие. Из-за разногласий в техническом комитете стандарт ES4 так и не попал в веб-браузеры, но его использовали Macromedia (позже Adobe) в качестве основы для ActionScript.
Первый серьёзный пересмотр JavaScript произошёл в 2015 году, когда был представлен ES6, также известный как ES2015 или ECMAScript Harmony. Он принёс много новых синтаксических конструкций, которые сделали язык более зрелым, безопасным и удобным для программистов. Это стало поворотным моментом в графике выпусков ECMAScript, которые появляются теперь каждый год.
Такой быстрый темп означает, что последняя версия языка вряд ли будет сразу принята всеми веб-браузерами, поскольку для развё ртывания обновлений требуется время. Однако практически любой современный веб-браузер поддерживает ES5.
Среда выполнения
Чтобы запустить программу на Python, необходимо скачать, установить и, возможно, настроить интерпретатор для нашей платформы. Некоторые операционные системы предоставляют Python из коробки, но это может быть не та версия, которую мы хотим использовать. Существуют различные реализации Python: CPython, PyPy, Jython, IronPython или Stackless Python. Можно также выбрать один из дистрибутивов Python, таких как Anaconda, которые поставляются с предустановленными сторонними пакетами.
JavaScript не таков. Здесь нет отдельной программы для загрузки. Каждый веб-браузер поставляется с каким-либо движком JavaScript и API. Вместе они создают среду выполнения. В предыдущем разделе мы рассказали о Node.js, который позволяет запускать код JavaScript вне браузера. Ещё JavaScript можно «встраивать» в другие языки программирования.
Экосистема
В старые времена для написания JavaScript-кода было достаточно хорошего редактора. Ещё можно было загрузить библиотеку: jQuery, Underscore.js или Backbone.js. Или не загружать, а положиться на сеть доставки (CDN). Сегодня для построения даже самого простого проекта имеется пугающее количество инструментов.
Процесс сборки фронтенд-составляющей часто не менее сложен, чем бэкенд. Веб-проект проходит через линтинг, транспиляцию, полифиллинг, пакетирование, минификацию и многое другое. Даже таблицы стилей CSS могут компилироваться из языка расширений препроцессором, таким как Sass или Less.
Чтобы облегчить ситуацию, некоторые платформы предлагают утилиты, которые создают базовую структуру проекта, генерируют файлы конфигурации и загружают зависимости. Например, создать новое приложение React можно с помощью следующей короткой команды (у вас должен быть установлен Node.js):
Shell
npx create-react-app todo
На момент написания статьи этой команде потребовалось несколько минут, чтобы установить 166 Мб в 1815 пакетах! Сравните это с мгновенным запуском проекта Django в Python:
django-admin startproject blog
Современная JavaScript-экосистема огромна и продолжает развиваться, что делает невозможным полный обзор её элементов. В процессе изучения JavaScript вы столкнётесь с множеством инструментов. Однако концепции некоторых из них будут знакомы. Вот какие параллели можно провести с Python:
Python | JavaScript | |
Редактор кода / IDE | PyCharm, VS Code | Atom,VS Code,WebStorm |
Форматирование кода | black | Prettier |
Менеджер зависимостей | Pipenv , poetry | bower , npm , yarn |
Средство документации | Sphinx | JSDoc , sphinx-js |
Интерпретатор | bpython , ipython , python | node |
Библиотеки | requests , dateutil | axios , moment |
Линтер | flake8 , pyflakes , pylint | eslint ,tslint |
Менеджер пакетов | pip , twine | bower , npm , yarn |
Реестр пакетов | PyPI | npm |
Запуск пакетов | pipx | npx |
Менеджер сред выполнения | pyenv | nvm |
Скаффолдинг | cookiecutter | cookiecutter , Yeoman |
Тестирование | doctest , nose , pytest | Jasmine , Jest , Mocha |
Веб-фреймворк | Django, Flask, Tornado | Angular, React, Vue.js |
Этот список не является исчерпывающим. Некоторые из упомянутых выше инструментов имеют перекрывающиеся возможности, поэтому их трудно сравнивать.
Иногда между инструментами Python и JavaScript нет прямой аналогии. В то время как для создания виртуальных окружений Python нужно проводить дополнительную настройку, Node.js работает с этим из коробки – зависимости устанавливаются в локальную директорию.
И наоборот, проекты JavaScript могут требовать дополнительных инструментов, уникальных для фронтенда. Одним из таких инструментов является Babel, транспилирующий код в соответствии с различными плагинами, сгруппированными в пресеты. Он умеет работать с экспериментальными функциями ECMAScript, TypeScript и даже синтаксисом расширения React JSX.
Во время разработки полезно разбивать код на повторно используемые, тестируемые и автономные модули. К сожалению, в JavaScript изначально не поддерживалась модульность. До сих пор для этого требуется использовать отдельный инструмент. Популярными вариантами для пакетов модулей являются webpack, Parcel и Browserify.
Модель работы с памятью
Оба языка используют преимущества автоматического управления памятью для снижения нагрузки разработчика и уменьшения фактора человеческих ошибок. Тем не менее это не освобождает нас от риска утечек памяти и потери производительности.
Традиционная реализация CPython использует алгоритм подсчёта ссылок, а также недетерминированный сборщик мусора для работы со ссылочными циклами. В JavaScript реализация управления памятью определяется конкретным движком – модель работы с памятью не является частью спецификации языка. Основной стратегией сбора мусора, как правило, является алгоритм пометок (англ. mark-and-sweep ), но используются и различные методы оптимизации.
Система типов в JavaScript
Сравнение проверок типов Python и JavaScript
Python и JavaScript типизируются динамически. Они проверяют типы, когда приложение выполняется, а не во время компиляции. Это удобно, потому что мы не обязаны объявлять тип переменной, такой как int
или str
:
Python
>>> data = 42
>>> data = 'This is a string'
Здесь мы повторно используем одно и то же имя переменной для двух разных типов объектов, имеющих различные представления в памяти компьютера. И в Python, и в JavaScript информация о типе связана не с переменной, а с объектом, на который она указывает. Такая переменная является псевдонимом-указателем на некоторый объект в памяти.
Отсутствие объявлений типов прекрасно подходит для создания прототипов, но в крупных проектах это быстро становится узким местом в плане поддержки кода. Динамическая типизация менее безопасна из-за более высокого риска того, что ошибки останутся незамеченными внутри редко используемых путей выполнения кода.
Python решил эту проблему, введя хинтинг типов:
Python
data: str = 'This is a string'
По умолчанию подсказки типов в Python имеют лишь информативное значение – интерпретатор не заботится о них во время выполнения. Однако вы можете добавить средство проверки статического типа, чтобы получать предупреждения в случае несовпадения типов. Необязательность хинтинга позволяет комбинировать динамически типизированный код со статически типизированным. Этот подход известен как постепенная типизация (англ. gradual typing).
Python демонстрирует строгую типизацию, отказываясь работать с объектами несовместимых типов. Например, оператор сложения можно использовать для получения суммы чисел или конкатенации строк, но нельзя сложить число и строку:
Python
>>> '3' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
JavaScript использует слабую типизацию, которая автоматически приводит типы в соответствии с набором правил. К сожалению, эти правила трудно запомнить, поскольку они зависят от приоритета операторов.
> '3' + 2
'32'
> '3' - 2
1
Типы данных в JavaScript
В Python всё является объектами. JavaScript различает примитивные и ссылочные типы.
Примитивные типы:
boolean
null
number
string
symbol
(с ES6)undefined
А вот неполный список ссылочных типов данных:
Array
Boolean
Date
Map
Number
Object
RegExp
Set
String
Symbol
(...)
Примитивные типы – это простые значения без каких-либо атрибутов или методов. Когда мы пытаемся получить доступ к атрибуту с помощью точечной нотации, движок JavaScript мгновенно оборачивает примитивное значение в соответствующий объект:
> 'Lorem ipsum'.length
11
Хотя строковый литерал в JavaScript является примитивным типом данных, мы можем узнать его длину с помощью атрибута .length
. Наш код заменяется вызовом конструктора объекта String
:
> new String('Lorem ipsum').length
11
Конструктор – это специальная функция, которая создаёт новый экземпляр данного типа. Этот механизм известен как autoboxing
и был скопирован у языка программирования Java.
Более ощутимое различие между примитивными и ссылочными типами заключается в том, как они передаются. Когда вы присваиваете или передаёте значение примитива, в памяти создаётся копия этого значения:
> x = 42
> y = x
> x++ // Сокращенная форма инкремента x += 1
> console.log(x, y)
43 42
Однако, когда вы передаёте ссылку на литерал объекта, обе переменные указывают на один и тот же объект в памяти:
> x = {name: 'Person1'}
> y = x
> x.name = 'Person2'
> console.log(y)
{name: 'Person2'}
Примитивные типы являются неизменяемыми. Каждая модификация, такая как увеличение числа или перевод текста в верхний регистр, приводит к созданию новой копии.
Чтобы проверить, является ли переменная примитивом или ссылочным объектом, можно использовать встроенный оператор typeof
:
> typeof 'Lorem ipsum'
'string'
> typeof new String('Lorem ipsum')
'object'
Для всех ссылочных типов оператор typeof
возвращает значение 'object'
.
Если нужно получить более подробную информацию о конкретном типе, есть несколько вариантов:
> today = new Date()
> today.constructor.name
'Date'
> today instanceof Date
true
> Date.prototype.isPrototypeOf(today)
true
То есть вы можете проверить имя конструктора объекта с помощью оператора instanceof
или на основе определённого родительского типа с помощью свойства .prototype
.
Иерархия типов
Python и JavaScript являются объектно-ориентированными языками программирования. Оба языка позволяют выражать код в терминах объектов, которые инкапсулируют идентичность, состояние и поведение. Хотя большинство языков программирования, включая Python, используют наследование на основе классов, JavaScript – один из немногих, который этого не делает.
Чтобы создать иерархию пользовательских типов в JavaScript, нам необходимо ознакомиться с наследованием прототипов. Если у вас есть двадцать минут, то вы можете посмотреть отличное видео о прототипах, которое чётко объясняет концепцию.
Технически можно использовать ключевое слово class
, введённое в ES6, но это лишь синтаксический сахар, облегчающий жизнь новичкам. За кулисами используются прототипы, поэтому впоследствии придётся познакомиться с ними поближе.
Примечание
Если вам удобнее текст, чем видео, мы также описали концепцию в статье Классы на прототипах: как работает ООП в JavaScript.
Особенности функций
Функции являются интересной частью систем типов JavaScript и Python. На обоих языках их часто называют объектами первого класса: интерпретатор обрабатывает их иначе, чем другие типы данных. Вы можете передать функцию в качестве аргумента, вернуть из другой функции или сохранить в переменной. Это очень мощная особенность, позволяющая в полной мере использовать функциональную парадигму.
Синтаксис JavaScript
Одним из отличительных признаков Python является использование обязательного отступа для обозначения блока кода, что довольно необычно. Многие популярные языки программирования, включая JavaScript, используют вместо этого фигурные скобки или специальные ключевые слова:
function fib(n)
{
if (n > 1) {
return fib(n-2) + fib(n-1);
}
return 1;
}
В JavaScript каждый блок кода, состоящий из более чем одной строки, нуждается в открывающей и закрывающей фигурных скобках { }
. Это даёт свободу форматировать код так, как нравится. К сожалению, это же приводит к конфликтам между разработчиками с разными стилевыми предпочтениями. Поэтому необходимо всегда устанавливать стандарты кодирования для своей команды и использовать их последовательно, по возможности в автоматическом режиме.
Точка с запятой
Чтобы упростить вхождение программистов с Java и других языков программирования семейства C, JavaScript завершает выражения точкой с запятой (;
).
alert('hello world');
Примечание
Пропуск точки с запятой обычно не является ошибкой – интерпретатор сделает предположение и автоматически вставит символ. Однако иногда это может привести к неожиданным результатам. Поэтому общепринятая практика заключается в использовании этого разделителя.
Идентификаторы
Идентификаторы, такие как имена переменных или функций, в JavaScript и Python могут содержать только буквы, цифры и несколько специальных символов, но не могут начинаться с цифры. Хотя нелатинские символы разрешены, их нужно избегать.
Имена в обоих языках чувствительны к регистру: foo
и Foo
– это разные переменные. Соглашения об именах в JavaScript немного отличаются от Python.
Python | JavaScript | |
Классы Python и составные | ProjectMember | ProjectMember |
Переменная, атрибут или функция | first_name | firstName |
Обычно в Python для составных имен рекомендуется использовать «змеиный регистр»: lower_case_with_underscores
, чтобы отдельные слова разделялись символом подчёркивания (_
). Исключением из правила являются классы, имена которых должны соответствовать стилю CapitalizedWords
.
В JavaScript также используется верблюжий стиль для типов, но в остальных случаях используется lower camelCase, когда с прописной буквы пишутся все слова, кроме первого.
Комментарии
JavaScript поддерживает однострочные и многострочные комментарии:
x++; // Это однострочный комментарий
/*
Весь абзац является многострочным комментарием,
и будет пропущен при выполнении программы.
*/
Строковые литералы
Чтобы определить строковые литералы, вы можете использовать пару одинарных ('
) или двойных ("
) кавычек – взаимозаменяемо, как в Python.
В 2015 г. ES6 добавил шаблонные многострочные литералы, которые выглядят как гибрид f-строк и многострочных объектов Python:
var name = 'John Doe';
var message = `Hi ${name.split(' ')[0]},
We're writing to you regarding...
Kind regards,
Xyz
`;
Шаблон помещается в обратные кавычки (`
). Внутри шаблона с помощью знака доллара и фигурных скобок можно включить переменную или любое допустимое выражение.
Области видимости переменных
При определении переменной в JavaScript, как в Python, мы неявно создаём глобальную переменную. Поскольку глобальные переменные нарушают инкапсуляцию, в них обычно нет необходимости. Ранее единственным правильным способом объявления переменных в JavaScript было ключевое слово var
:
x = 42; // Это глобальная переменная. Вам это точно нужно?
var y = 15; // Это глобальная переменная только, если она объявлена в глобальном контексте.
К сожалению, такое объявление не создаёт действительно локальную переменную. В ES6 появился лучший способ объявления переменных и констант ключевыми словами let
и const
соответственно:
> let name = 'John Doe';
> const PI = 3.14;
> PI = 3.1415;
TypeError: Assignment to constant variable.
В отличие от констант, переменные в JavaScript не требуют начального значения. Вы можете предоставить значение позже:
let name;
name = 'John Doe';
Когда вы пропускаете начальное значение, вы объявляете переменную без её определения. Такие переменные автоматически получают специальное значение undefined
, которое является одним из примитивов JavaScript.
Стрелочные функции
До выхода ES6 мы могли определить функцию или анонимное функциональное выражение только с помощью ключевого слова function
:
function add(a, b) {
return a + b;
}
let add = function(a, b) {
return a + b;
};
Однако, чтобы уменьшить количество кода и исправить проблему привязки функций к объектам, теперь в дополнение к обычному синтаксису можно использовать стрелочные функции:
let add = (a, b) => a + b;
Символ стрелки (=>
) отделяет аргументы функции от её тела.
Функции-стрелки используются для небольших анонимных выражений, таких как лямбда-выражения в Python, но они могут содержать, если это необходимо, несколько операторов:
let add = (a, b) => {
const result = a + b;
return result;
}
Если из стрелочной функции нужно вернуть литерал объекта, его нужно заключить в скобки:
let add = (a, b) => ({
result: a + b
});
В противном случае тело функции будет перепутано с блоком кода.
Значения по умолчанию и функции с переменным числом аргументов
Начиная с ES6, аргументы функции могут иметь значения по умолчанию, как в Python:
> function greet(name = 'John') {
… console.log('Hello', name);
… }
> greet();
Hello John
Когда в Python мы хотим объявить функцию с переменным числом параметров, мы используем специальный синтаксис *args
. Эквивалентом в JavaScript служат остаточные параметры (...
):
> function average(...numbers) {
… if (numbers.length > 0) {
… const sum = numbers.reduce((a, x) => a + x);
… return sum / numbers.length;
… }
… return 0;
… }
> average();
0
> average(1);
1
> average(1, 2);
1.5
> average(1, 2, 3);
2
Оператор ...
может использоваться и для того, чтобы комбинировать итерируемые последовательности. Например, можно извлечь элементы одного массива в другой:
const redFruits = ['apple', 'cherry'];
const fruits = ['banana', ...redFruits];
Деструктурирующее присваивание
Чтобы распаковать итерируемый объект в отдельные переменные или константы, используется деструктурирующее присваивание:
> const fruits = ['apple', 'banana', 'orange'];
> const [a, b, c] = fruits;
> console.log(b);
banana
Точно так же можно деструктурировать и даже переименовать атрибуты объекта:
const person = {name: 'John Doe', age: 42, married: true};
const {name: fullName, age} = person;
console.log(`${fullName} is ${age} years old.`);
Это помогает избежать конфликтов имён переменных, определённых в одной области видимости.
Перебираемые объекты, итераторы и генераторы
Начиная с ES6, JavaScript получил протоколы итерируемых объектов, итераторов и функций-генераторов, которые работают схожим образом с соответствующими объектами Python. Чтобы превратить обычную функцию в функцию-генератор, после ключевого слова function
необходимо добавить звёздочку (*
) :
function* makeGenerator() {}
При вызове функции-генератора её тело не выполняется. Вместо этого она возвращает объект, соответствующий протоколу итератора. Чтобы его использовать, нужно вызвать метод .next()
, похожий на встроенную функцию Python next()
:
> const generator = makeGenerator();
> const {value, done} = generator.next();
> console.log(value);
undefined
> console.log(done);
true
В результате мы всегда получаем объект состояния с двумя атрибутами: следующее значение и флаг, указывающий, был ли исчерпан генератор. Когда в генераторе больше нет значений, Python генерирует исключение StopIteration
.
Чтобы вернуть некоторое значение из генератора, можно использовать ключевые слова yield
и return
:
let shouldStopImmediately = false;
function* randomNumberGenerator(maxTries=3) {
let tries = 0;
while (tries++ < maxTries) {
if (shouldStopImmediately) {
return 42; // The value is optional
}
yield Math.random();
}
}
Приведённый генератор будет выдавать случайные числа, пока не достигнет максимального числа попыток или мы не изменим флаг, чтобы он завершился досрочно.
Эквивалентом выражения yield from
в Python, которое делегирует итерацию другому итератору или итерируемому объекту, является выражение yield *
:
> function* makeGenerator() {
… yield 1;
… yield* [2, 3, 4];
… yield 5;
… }
> const generator = makeGenerator()
> generator.next();
{value: 1, done: false}
> generator.next();
{value: 2, done: false}
> generator.next();
{value: 3, done: false}
> generator.next();
{value: 4, done: false}
> generator.next();
{value: 5, done: false}
> generator.next();
{value: undefined, done: true}
Асинхронные функции
Начиная с ES8 в JavaScript появились асинхронные функции, обозначаемые с помощью ключевого слова async
. Асинхронные функции всегда возвращают промис (promise
) – специальный объект, умеющий хранить своё состояние и связывающий между собой «создающий» и «потребляющий» коды.
Когда мы возвращаем значение из асинхронной функции, оно автоматически оборачивается в промис, который может ожидаться (ключевое слово await
) в другой асинхронной функции:
async function greet(name) {
return `Hello ${name}`;
}
async function main() {
const promise = greet('John');
const greeting = await promise;
console.log(greeting); // "Hello John"
}
main();
Промисы значительно улучшают читабельность кода. Он начинает выглядеть как синхронный код, даже если функции многократно останавливают и возобновляют свою работу.
Заметное отличие от асинхронного кода в Python состоит в том, что в JavaScript не нужно вручную настраивать цикл обработки событий – он неявно выполняется в фоновом режиме.
Объекты и конструкторы
Новые объекты можно создавать, используя литералы объектов, которые выглядят как словари Python:
let person = {
name: 'John Doe',
age: 42,
married: true
};
Доступ к отдельным атрибутам можно получить, используя точечный синтаксис или квадратные скобки:
> person.age++;
> person['age'];
43
Подобно словарю и некоторым объектам в Python, объекты в JavaScript имеют динамические атрибуты. Это означает, что вы можете добавить новые атрибуты или удалить существующие из объекта:
> let person = {name: 'John Doe'};
> person.age = 42;
> console.log(person);
{name: "John Doe", age: 42}
> delete person.name;
true
> console.log(person);
{age: 42}
Начиная с ES6, объекты могут иметь атрибуты с вычисляемыми именами:
> let person = {
… ['full' + 'Name']: 'John Doe'
… };
> person.fullName;
'John Doe'
Словари Python и объекты JavaScript в качестве своих ключей и атрибутов могут содержать функции. Есть способы привязать такие функции к их владельцу, чтобы они вели себя как методы класса. Например, можно использовать циклическую ссылку:
> let person = {
… name: 'John Doe',
… sayHi: function() {
… console.log(`Hi, my name is ${person.name}.`);
… }
… };
> person.sayHi();
Hi, my name is John Doe.
Немного лучший подход использует преимущества неявной переменной this
(похожей на self
в классах Python):
> let jdoe = {
… name: 'John Doe',
… sayHi: function() {
… console.log(`Hi, my name is ${this.name}.`);
… }
… };
> jdoe.sayHi();
Hi, my name is John Doe.
Канонический способ создания пользовательских типов данных в JavaScript – определить функцию-конструктор. Для обозначения того, что такая функция имеет особое значение, принято начинать имя конструктора с заглавной буквы:
function Person() {
console.log('Calling the constructor');
}
На синтаксическом уровне, однако, это просто функция. Особой её делает вызов с ключевым словом new
:
> new Person();
Calling the constructor
Person {}
Конструктор возвращает новый экземпляр объекта JavaScript. Роль конструктора состоит в том, чтобы придать объекту начальное состояние. Для ссылки на новый экземпляр используется ключевое слово this
:
function Person(name) {
this.name = name;
this.sayHi = function() {
console.log(`Hi, my name is ${this.name}.`);
}
}
Теперь можно создавать отдельные объекты Person
:
const jdoe = new Person('John Doe');
const jsmith = new Person('John Smith');
Прототипы
Как правило, бизнес-логику переносят из конструктора, который занимается данными, в прототип:
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, my name is ${this.name}.`);
};
У каждого объекта есть прототип, доступный по атрибуту .prototype
. У прототипа конструктора уже есть несколько предопределённых методов, таких как .toString()
. Это методы общие для всех объектов в JavaScript. Вы можете дополнить атрибуты и методы пользовательскими.
Когда JavaScript ищет атрибут объекта, он начинает с попытки найти его в этом объекте. Иначе он переходит к соответствующему прототипу. Поэтому атрибуты, определённые в прототипе, являются общими для всех экземпляров соответствующего типа.
Прототипы объединяются в цепочки, поэтому поиск атрибутов продолжается до тех пор, пока в цепочке не останется прототипов. Это аналогично иерархии типов через наследование.
Чтобы проиллюстрировать мощь прототипов, вы можете попытаться расширить поведение существующих объектов или даже встроенного типа данных. Давайте добавим новый метод к строковому типу в JavaScript, указав его в прототипе объекта:
String.prototype.toSnakeCase = function() {
return this.replace(/\s+/g, '')
.split(/(?<=[a-z])(?=[A-Z])/g)
.map(x => x.toLowerCase())
.join('_');
};
> "loremIpsumDolorSit".toSnakeCase();
'lorem_ipsum_dolor_sit'
В этом прототипе мы переопределили содержание, приведя строку к змеиному регистру с помощью регулярного выражения.
Классы
Начиная с ES6, существует альтернативный способ определения прототипов, который использует более знакомый синтаксис:
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`Hi, my name is ${this.name}.`);
}
}
Хотя это выглядит так, как будто мы определяем класс, это всего лишь удобная высокоуровневая метафора для определения пользовательских типов данных в JavaScript.
В классе можно использовать геттеры и сеттеры, которые близки по смыслу к свойствам классов Python:
> class Square {
… constructor(size) {
… this.size = size;
… }
… set size(value) { // сеттер, срабатывает при записи
… this._size = value; // Приватное поле
… }
… get area() { // геттер, срабатывает при чтении
… return this._size**2;
… }
… }
> const box = new Square(3);
> console.log(box.area);
9
> box.size = 5;
> console.log(box.area);
25
Иногда вы хотите определить фабричную или служебную функцию, которая логически принадлежит вашему классу. В Python для этого есть декораторы @classmethod
и @staticmethod
, которые позволяют связывать статические методы с классом. Чтобы достичь того же результата в JavaScript, нужно использовать модификатор метода static
:
class Color {
static brown() {
return new Color(244, 164, 96);
}
static mix(color1, color2) {
return new Color(...color1.channels.map(
(x, i) => (x + color2.channels[i]) / 2
));
}
constructor(r, g, b) {
this.channels = [r, g, b];
}
}
const color1 = Color.brown();
const color2 = new Color(128, 0, 128);
const blended = Color.mix(color1, color2);
«Странности» JavaScript
Брендану Эйху потребовалось десять дней, чтобы создать прототип будущего JavaScript. После того как прототип был представлен заинтересованным сторонам на деловой встрече, язык был признан готовым к производству и не претерпевал существенных изменений в течение многих лет.
К сожалению, это сделало язык печально известным из-за его странностей. Сегодня язык дружелюбнее, чем раньше. Тем не менее, стоит знать, чего следует избегать, поскольку многие конструкции JavaScript всё ещё встречаются на практике.
Фиктивный массив
Списки и кортежи Python похожи на традиционные массивы, тогда как тип Array
в JavaScript местами ведёт себя не совсем очевидно. Так, при удалении элемента из массива JavaScript создаётся пропуск:
> const fruits = ['apple', 'banana', 'orange'];
> delete fruits[1];
true
> console.log(fruits);
['apple', empty, 'orange']
> fruits[1];
undefined
Массив не меняет свой размер после «удаления» одного из его элементов:
> console.log(fruits.length);
3
И наоборот, вы можете поместить новый элемент с отсутствующим индексом, даже если массив намного короче:
> fruits[10] = 'watermelon';
> console.log(fruits.length);
11
> console.log(fruits);
['apple', empty, 'orange', empty × 7, 'watermelon']
Пояснение
При удалении элемента мы удаляем значение позиционного ключа, но сам ключ остаётся. Для удаления и ключа, и элемента можно использовать метод массива .splice
. Подробнее о методах массива читайте в руководстве learn.javascript.ru.
Сортировка массива
Python упрощает работу с сортировкой, учитывая типы элементов. Например, когда вы сортируете список чисел, он по умолчанию помещает их в порядке возрастания:
Python
>>> sorted([53, 2020, 42, 1918, 7])
[7, 42, 53, 1918, 2020]
Если нужно отсортировать список строк, та же функция отсортирует элементы в лексикографическом порядке:
Python
>>> sorted(['lorem', 'ipsum', 'dolor', 'sit', 'amet'])
['amet', 'dolor', 'ipsum', 'lorem', 'sit']
Однако смешение типов не работает:
Python
>>> sorted([42, 'not a number'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'int'
Как мы обсудили выше, Python является строго типизированным языком и не любит смешения типов. JavaScript наоборот, с готовностью преобразует элементы несовместимых типов так, чтобы выполнить действие.
Для сортировки элементов массива используется метод .sort()
:
> ['lorem', 'ipsum', 'dolor', 'sit', 'amet'].sort();
['amet', 'dolor', 'ipsum', 'lorem', 'sit']
Сортировка строк работает как положено. Посмотрим, как метод справляется с числами:
> [53, 2020, 42, 1918, 7].sort();
[7, 42, 53, 1918, 2020]
Элементы массива неявно преобразуются в строки и сортируются лексикографически. Чтобы это предотвратить, нам нужно предоставить собственную стратегию сортировки, как функцию двух сравниваемых элементов, например:
> [53, 2020, 42, 1918, 7].sort((a, b) => a - b);
[7, 42, 53, 1918, 2020]
Многообразие циклов
Тогда как в Python есть всего два типа циклов, в JavaScript их целое множество. Основной тип – это цикл for, перенесённый из Java:
const fruits = ['apple', 'banana', 'orange'];
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
Описание цикла состоит из трёх частей, каждая из которых является необязательной:
- Инициализация:
let i = 0
- Условие:
i < fruits.length
- Итерация:
i++
Первая часть выполняется только один раз перед началом цикла и обычно устанавливает начальное значение счётчика. После каждой итерации выполняется третья операция для обновления счётчика. Сразу после этого оценивается условие, чтобы определить, должен ли цикл продолжаться.
Цикл будет работать и без этих инструкций (for(;;)
) – тогда получится бесконечный цикл. Более принятый подход – цикл while
, похожий на знакомый цикл в Python:
while (true) {
const age = prompt('How old are you?');
if (age >= 18) {
break;
}
}
В дополнение к этому в JavaScript есть цикл do ... while
, который гарантированно запускается хотя бы один раз, поскольку проверяет условие после выполнения тела цикла. Вы можете переписать пример следующим образом:
do {
age = prompt('How old are you?');
} while (age < 18);
Как в Python, в JavaScript есть ключевые слова break
, continue
, else
, имеющие те же значения.
Кроме того, есть цикл for ... in
, итерируйщийся по перебираемым элементам объекта:
> const object = {name: 'John Doe', age: 42};
> for (const attribute in object) {
… console.log(`${attribute} = ${object[attribute]}`);
… }
name = John Doe
age = 42
Однако в случае массивов JavaScript они вновь ведут себя похоже на словари Python:
> const fruits = ['apple', 'banana', 'orange'];
… for (const fruit in fruits) {
… console.log(fruit);
… }
0
1
2
С другой стороны, у массивов есть метод .forEach()
, который заменяет работу с циклом (кстати, он более производителен):
const fruits = ['apple', 'banana', 'orange'];
fruits.forEach(fruit => console.log(fruit));
Цикл for ... of
является ближайшим по отношению к циклу for
в Python. С его помощью можно перебирать любой итерируемый объект, включая строки и массивы:
const fruits = ['apple', 'banana', 'orange'];
for (const fruit of fruits) {
console.log(fruit);
}
Конструктор без ключевого слова new
Если вы забудете правильно вызвать конструктор, указав перед вызовом new
, то он без сообщений об ошибке просто оставит вас с примитивом undefined
:
function Person(name) {
this.name = name;
this.sayHi = function() {
console.log(`Hi, my name is ${this.name}.`);
}
}
> let bob = Person('Bob');
> console.log(bob);
undefined
Есть возможность защититься от этой ошибки. При пропуске ключевого слова new
не будет создано никакого объекта. Переменная this
внутри конструктора будет указывать на глобальный объект – обычно объект window
в веб-браузере. Вы можете обнаружить это и делегировать действительный вызов конструктора:
> function Person(name) {
… if (this === window) {
… return new Person(name);
… }
… this.name = name;
… this.sayHi = function() {
… console.log(`Hi, my name is ${this.name}.`);
… }
… }
> let person = Person('John Doe');
> console.log(person);
Person {name: 'John Doe', sayHi: ƒ}
Глобальная область видимости по умолчанию
Пока вы находитесь в глобальной области видимости, ваши переменные автоматически становятся глобальными, если им не предшествуют объявления var
, let
или const
.
Попасть в эту ловушку легко, особенно если вы пришли из Python. Например, такая переменная, определенная в функции, станет видимой вне её:
> function call() {
… global = 42;
… let local = 3.14
… }
> call();
> console.log(global);
42
> console.log(local);
ReferenceError: local is not defined
Область видимости внутри функции
Эта особенность присутствует только в устаревшем коде, использующем ключевое слово var
для объявления переменных. Независимо от того, как глубоко в функции определена такая переменная, она будет доступна во всей функции:
> function call() {
… if (true) {
… for (let i = 0; i < 10; i++) {
… var notGlobalNorLocal = 42 + i;
… }
… }
… notGlobalNorLocal--;
… console.log(notGlobalNorLocal);
… }
> call();
50
Переменная видна, и всё ещё доступна на верхнем уровне функции прямо перед выходом. Однако вложенные функции не предоставляют свои переменные внешней области видимости:
> function call() {
… function inner() {
… var notGlobalNorLocal = 42;
… }
… inner();
… console.log(notGlobalNorLocal);
… }
> call();
ReferenceError: notGlobalNorLocal is not defined
Иллюзорные сигнатуры функций
Вы можете передать любое количество аргументов функции, которая ничего не ожидает, и они будут просто проигнорированы:
> function currentYear() {
… return new Date().getFullYear();
… }
> currentYear(42, 'foobar');
2020
Также можно забыть передать аргументы, даже если они выглядят, как обязательные:
> function truthy(expression) {
… return !!expression;
… }
> truthy();
false
Формальные параметры служат лишь документацией и позволяют ссылаться на аргументы по имени. В противном случае они не используются. У любой функции есть доступ к специальной переменной arguments
, которая представляет фактически переданные параметры:
> function sum() {
… return [...arguments].reduce((a, x) => a + x);
… }
> sum(1, 2, 3, 4);
10
arguments
– объект, похожий на массив. Он итерируем, имеет числовые индексы, но, к сожалению, не работает с .forEach()
. Чтобы обернуть его в массив, можно использовать оператор ...
.
Неявное приведение типов
JavaScript – это слабо типизированный язык программирования, что проявляется в способности неявно приводить несовместимые типы. Это может дать ложные срабатывания при сравнении двух значений:
if ('2' == 2) { // Будет true
В общем случае оператор строгого сравнения (===
) оказывается более безопасным:
> '2' === 2;
false
> '2' !== 2;
true
Этот оператор сравнивает и значения, и типы операндов.
Нет целочисленного типа
В Python есть несколько типов данных для представления чисел:
int
float
complex
В JavaScript есть только один числовой тип: Number
, который соответствует типу данных float
в Python. Под капотом это 64-битное число с двойной точностью, соответствующее спецификации IEEE 754. Этого было достаточно для ранней веб-разработки, но сегодня вызывает несколько проблем.
Во-первых, в большинстве ситуаций это удивительно расточительно. Чтобы представить пиксели одного видеокадра Full HD с помощью Number
, пришлось бы выделить около 50 МБ памяти.
Во-вторых, числа с плавающей точкой страдают проблемой неточного округления из-за того, как они представлены в памяти компьютера. Они не подходят для приложений, требующих высокой точности, например, денежных расчётов:
> 0.1 + 0.2;
0.30000000000000004
Такой тип данных также небезопасен для представления очень больших и очень малых чисел:
> const x = Number.MAX_SAFE_INTEGER + 1;
> const y = Number.MAX_SAFE_INTEGER + 2;
> x === y;
true
Чтобы решить проблему обработки больших чисел в JavaScript, недавно был добавлен ещё один примитив BigInt
, который может надёжно представлять целые числа любого размера. Некоторые веб-браузеры уже поддерживают этот тип данных:
> const x = BigInt(Number.MAX_SAFE_INTEGER) + 1n;
> const y = BigInt(Number.MAX_SAFE_INTEGER) + 2n;
> x === y;
false
null против undefined
Многие языки программирования имеют способ представления отсутствующего значения. В Python есть None
, в Java – null
и т. д. В JavaScript есть два значения: null
и undefined
. Переменные, которые объявлены, но не инициализированы, неявно получают значение undefined
. Значение null
никогда не присваивается автоматически:
let x; // undefined
let y = null;
Значение undefined
можно присвоить и явно:
let z = undefined;
Разница между null
и undefined
часто использовалась для реализации аргументов функции по умолчанию до ES6:
function fn(required, optional) {
if (typeof optional === 'undefined') {
optional = 'default';
}
// ...
}
Если по какой-то причине вы хотите сохранить пустое значение, нужно это сделать явно:
fn(42); // optional = "default"
fn(42, undefined); // optional = "default"
fn(42, null); // optional = null
Что дальше?
Как любой питонист, вы знаете, что знакомство с языком программирования и его экосистемой – лишь начало пути к успеху. Есть более абстрактные концепции.
Объектная модель документа (DOM)
Если вы планируете заниматься какой-либо разработкой на стороне клиента, вам не избежать знакомства с DOM.
Примечание
На популярном сайте learn.javascript.ru объектной модели документа посвящена вторая часть. Можно порекомендовать на начальном этапе после беглого ознакомления с первой частью о синтаксисе языка сделать упор на второй части этого учебника. И уже по мере необходимости возвращаться к первой. Так будет проще не растерять интерес.
Чтобы JavaScript мог манипулировать HTML-документами, веб-браузеры предоставляют стандартный интерфейс DOM, который включает в себя различные объекты и методы. Когда страница загружается, скрипт получает доступ к внутреннему представлению документа через предопределённый экземпляр document
:
const body = document.body;
Это глобальная переменная, доступная в любом месте кода.
Каждый документ – это дерево элементов. Чтобы пройти эту иерархию, вы можете начать с корня и использовать следующие атрибуты для перемещения в разных направлениях:
- Вверх (наружу):
.parentElement
- Влево:
.previousElementSibling
- Вправо:
.nextElementSibling
- Вниз (внутрь):
.children
,.firstElementChild
,.lastElementChild
Эти атрибуты доступны для всех элементов дерева DOM, что идеально подходит для рекурсивного обхода:
const html = document.firstElementChild;
const body = html.lastElementChild;
const element = body.children[2].nextElementSibling;
Обычно нет необходимости точно знать, где находится элемент. Объект документа, как и любой другой элемент в дереве, имеет несколько методов для поиска. Вы можете искать один элемент зараз или несколько одновременно по имени тега, id или селектору CSS.
Чтобы сопоставить элементы с селектором, можно вызвать один из двух методов:
.querySelector(selector)
.querySelectorAll(selector)
Первый метод возвращает первое вхождение совпадающего элемента. Второй метод возвращает массивоподобный объект c соответствующими запросу элементами. Вызов методов для объекта document
приведёт к поиску по всему документу. Вы можете ограничить область поиска, вызвав те же методы для ранее найденного элемента:
const div = document.querySelector('div'); // Первый div во всем документе
div.querySelectorAll('p'); // Все параграфы внутри этого div-блока
Как только у вас есть ссылка на элемент HTML, вы можете сделать с ним множество разных вещей:
- Прикрепить новые данные.
- Обновить стиль.
- Изменить содержимое.
- Поменять положение.
- Сделать интерактивным.
- Скрыть или удалить.
Можно создавать новые элементы и добавлять их в дерево DOM:
const parent = document.querySelector('.content');
const child = document.createElement('div');
parent.appendChild(child);
Самая сложная часть в использовании DOM – это умение задавать корректные селекторы CSS. Можно попрактиковаться, используя одну из интерактивных игровых площадок, доступных онлайн.
Фреймворки JavaScript
Интерфейс DOM представляет собой набор примитивных строительных блоков для создания интерактивных пользовательских интерфейсов. По мере роста клиентского кода его становится труднее поддерживать. Бизнес-логика и представление данных начинают пересекаться, нарушая принцип разделения задач, накапливается дублирование кода.
Веб-браузеры добавляют масла в огонь, не предоставляя единого интерфейса. Иногда функция, которую вы хотите использовать, в каких-то браузерах недоступна или по-разному реализована.
Чтобы справиться с этими проблемами, люди стали обмениваться библиотеками JavaScript, которые инкапсулировали шаблоны и делали API-интерфейс браузеров менее раздражающими. Самой популярной библиотекой стала jQuery, которая до недавнего времени даже превосходила по популярности фронтенд-фреймворки.
Примечание
Библиотека содержит низкоуровневые служебные функции, в то время как фреймворк полностью контролирует жизненный цикл вашего кода.
У jQuery хорошая документация и минималистичный API: есть только одна функция, которую нужно запомнить – универсальная функция $()
, которую можно использовать для создания и поиска элементов, изменения стиля, обработки событий и многого другого.
Современные браузеры стали намного лучше с точки зрения согласованности и поддержки веб-стандартов. Настолько, что некоторые люди предпочитают разрабатывать код на стороне клиента на чистом JavaScript (Vanilla JavaScript) без помощи какой-либо интерфейсной среды.
Почему же большинство людей предпочитают использовать фреймворки? Самое большое преимущество заключается в том, что фреймворки позволяют работать на более высоком уровне абстракции. Вместо того чтобы думать о веб-страницах в терминах элементов документа, можно создавать автономные и повторно используемые компоненты.
Какой фреймворк выбрать, зависит от вашей цели. Чтобы оставаться востребованным специалистом, вам всё равно придётся потратить время на изучение чистого JavaScript.
Заключение
Из этого руководства мы узнали о происхождении JavaScript, его альтернативах и о том, куда движется язык. Мы сравнили Python с JavaScript, внимательно изучили их сходства и различия в синтаксисе, средах выполнения, инструментах и жаргоне. Узнали, как избежать различных фатальных ошибок в JavaScript.
Попробуйте использовать Node.js для следующего скриптового проекта или создания интерактивного клиентского приложения в веб-браузере. Объедините ваши навыки Python c новыми знаниями о JavaScript, и для вас не будет ничего невозможного 💫