Если вы серьезно относитесь к веб‑разработке, то когда-нибудь вам понадобится изучить JavaScript. Год за годом многочисленные опросы показывают, что в мире JavaScript — один из самых популярных языков программирования с большим и растущим сообществом разработчиков. Как и Python, современный JavaScript можно использовать практически везде, включая интерфейс, серверную часть, настольный компьютер, мобильный телефон и Интернет вещей (IoT). Иногда выбор между Python и JavaScript может быть не очевиден.

Если вы никогда раньше не использовали JavaScript или были ошеломлены быстрыми темпами его развития в последние годы, то эта статья направит вас на верный путь. Чтобы получить пользу от этого туториала в полной мере, вы уже должны знать основы Python.

В этой статье:

  • Вы узнаете, результаты сравнения Python с JavaScript.
  • Поймёте как сделать выбор подходящего языка реализации для каждой конкретной работы.
  • Попробуете на зуб оболочку сценария на JavaScript.
  • Поймёте, как создавать динамический контент на веб‑странице.
  • Узнаете о преимуществах экосистемы JavaScript.
  • Узнаете о самых распространенных, «детских», ошибках в JavaScript.

Содержание

Коротко о JavaScript    ↑

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

Это не Java!    ↑

Многие люди, особенно некоторые ИТ-рекрутеры, считают, что JavaScript и Java — это один и тот же язык. Однако их трудно винить, потому что придумать такое знакомое имя было маркетинговым трюком.

Первоначально JavaScript назывался Mocha, а затем был переименован в LiveScript и окончательно переименован в JavaScript незадолго до его выпуска. В то время Java была многообещающей веб‑технологией, но для нетехнических веб‑мастеров это было слишком сложно. JavaScript был задуман как несколько похожий, но удобный для начинающих язык для дополнения Java-апплет в веб‑браузерах.

Интересный факт: и Java, и JavaScript были выпущены в 1995 году. Python было уже пять лет.

Чтобы усугубить путаницу, Microsoft разработала собственную версию языка, которую она назвала JScript из-за отсутствия лицензионных прав, для использования с Internet Explorer 3.0. Сегодня люди часто называют JavaScript JS.

Хотя Java и JavaScript имеют несколько общих черт в синтаксисе, подобном C, а также в своих стандартных библиотеках, они используются для разных целей. Java перешла от клиентской части к языку более общего назначения. JavaScript, несмотря на его простоту, был достаточен для проверки HTML‑форм и добавления небольших анимаций.

Это ECMAScript    ↑

JavaScript был разработан на заре Интернета относительно небольшой компанией, известной как Netscape. Чтобы завоевать рынок у Microsoft и уменьшить различия между веб‑браузерами, Netscape потребовалось стандартизировать свой язык. После отказа от международного консорциума World Wide Web (W3C) они обратились за помощью в европейский орган по стандартизации под названием ECMA (сегодня Ecma International).

ECMA определила формальную спецификацию для языка под названием ECMAScript, потому что название JavaScript было зарегистрировано торговой маркой Sun Microsystems. JavaScript стал одной из реализаций спецификации, которую он изначально вдохновил.

Примечание. Другими словами, JavaScript соответствует спецификации ECMAScript. Еще один заметный член семейства ECMAScript — ActionScript, который используется на платформе Flash.

Хотя отдельные реализации спецификации в некоторой степени соответствовали ECMAScript, они также поставлялись с дополнительными проприетарными API. Это привело к неправильному отображению веб‑страниц в разных браузерах и появлению таких библиотек, как jQuery.

Существуют ли другие скрипты?    ↑

По сей день JavaScript остается единственным языком программирования, изначально поддерживаемым веб‑браузерами. Это международный язык Интернета. Некоторым это нравится, а другим — нет.

Было и продолжается множество попыток заменить JavaScript другими технологиями, в том числе:

  • Rich Internet Applications: Flash, Silverlight, JavaFX
  • Transpilers: Haxe, Google Web Toolkit, pyjs
  • Диалекты JavaScript: CoffeeScript, TypeScript

Эти попытки были вызваны не только личными предпочтениями, но и ограничениями веб‑браузеров до появления HTML5. В те дни, JavaScript не мог быть использован для ресурсоёмких задач, таких как рисование векторной графики или обработка звука.

С другой стороны, Rich Internet Applications (RIA) (Насыщенное интернет‑приложение) предлагали иммерсивный рабочий стол в браузере через плагины. Они отлично подходили для игр и обработки мультимедиа. К сожалению, большинство из них были с закрытым кодом. У некоторых были уязвимости безопасности или проблемы с производительностью на определенных платформах. В довершение всего, все они сильно ограничивали способность поисковых систем индексировать страницы, созданные с помощью этих плагинов.

Примерно в то же время появились transpilers, которые позволили автоматически переводить другие языки на JavaScript. Это значительно снизило входной барьер для front‑end разработки, потому что back‑end программисты могли использовать свои навыки в новой области. Однако недостатками были более медленное время разработки, ограниченная поддержка веб‑стандартов и громоздкая отладка перенесенного кода JavaScript. Чтобы связать его с исходным кодом, вам понадобится исходная карта.

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

Чтобы написать код Python для браузера, можно использовать один из доступных transpilers, например Transcrypt или pyjs. Последний является портом Google Web Toolkit (GWT), который был чрезвычайно популярным transpiler Java-to-JavaScript. Другой вариант — использовать такой инструмент, как Brython, который запускает упрощенную версию интерпретатора Python на чистом JavaScript. Однако, преимущества могут быть нивелированы низкой производительностью и несовместимостью.

Transpilers позволили появиться множеству новых языков с целью замены JavaScript и устранения его недостатков. Некоторые из этих языков были тесно связанными диалектами JavaScript. Возможно, первым был CoffeeScript, который был создан около десяти лет назад. Одним из последних стал Google Dart, который был самым быстрорастущим языком в 2019 году по данным GitHub. За этим последовало еще много языков, но большинство из них теперь устарели из-за недавних достижений в JavaScript.

Одно вопиющее исключение — это Microsoft TypeScript, который в последние годы приобрел большую популярность.Это полностью совместимый надмножество JavaScript, которое добавляет дополнительную проверку статического типа. Если вам это кажется знакомым, то это потому, что подсказки типов в Python были вдохновлены TypeScript.

Хотя современный JavaScript является зрелым и активно развивается, transpilers по-прежнему является распространенным подходом для обеспечения обратной совместимости со старыми браузерами. Даже если вы не используете TypeScript, который кажется предпочтительным языком для многих новых проектов, вам все равно нужно будет перенести ваш блестящий новый JavaScript в старую версию языка. В противном случае вы рискуете получить ошибку времени выполнения.

Некоторые transpilers синтезируют передовые веб‑API, который может быть недоступен в некоторых браузерах, с так называемым полифил.

Сегодня, JavaScript называют ассемблером Интернета. Многие профессиональные front-end разработчики больше не пишут его от руки. В таком случае он создается с нуля путем транспилирования.

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

В дополнение к этому стоит упомянуть, что современные браузеры поддерживают стандарт WebAssembly, который является довольно новой технологией. Он определяет двоичный формат кода, который может работать в браузере с почти собственной производительностью. Он быстрый, переносимый, безопасный и позволяет кросс-компиляцию кода, написанного на таких языках, как C++ или Rust.С его помощью, например, можно взять код своей любимой видеоигры десятилетней давности и запустить его в браузере.

На данный момент WebAssembly помогает оптимизировать производительность критически важных с точки зрения вычислений частей кода, но за это приходится платить. Для начала вам необходимо знать один из поддерживаемых в настоящее время языков программирования. Вы должны познакомиться с низкоуровневыми концепциями, такими как управление памятью, поскольку сборщика мусора еще нет. Интеграция с кодом JavaScript сложна и требует больших затрат. Кроме того, нет простого способа вызывать из него веб‑API.

Похоже, что по прошествии стольких лет JavaScript не исчезнет в ближайшее время.

Стартовый комплект JavaScript    ↑

Одно из первых, что вы заметите при сравнении Python и JavaScript, заключается в довольно низком входном барьере. Для новичков, которые хотели бы научиться, это свойство обоих языков очень привлекательно. Единственное начальное требование для JavaScript — наличие веб‑браузера. Если вы читаете сей труд, то значит, вы уже это поняли. Такая доступность способствует популярности языка.

Адресная строка    ↑

Чтобы получить представление о написании кода JavaScript, можно прекратить читать сейчас и ввести следующий текст в адресную строку, прежде чем переходить к ней:

Буквальный текст — это javascript: alert('Hello, World!'), но не копируйте и вставляйте его просто так!

Эта часть после префикса javascript: представляет собой фрагмент кода JavaScript. После подтверждения в вашем браузере должно отображаться диалоговое окно с сообщением ‘Hello, World!’. Каждый браузер отображает это диалоговое окно по-своему. Например, Opera отображает это так:

Копирование и вставка такого фрагмента в адресную строку в большинстве браузеров не получится. Для обеспечения безопасности и предотвращения внедрения вредоносного кода, браузеры фильтруют префикс javascript:.

Некоторые браузеры, например, Mozilla Firefox, идут еще дальше и полностью блокируют выполнение кода из адресной строки. В любом случае, это не самый удобный вариант работы с JavaScript, где вы ограничены только одной строкой с определенным количеством символов. Есть способы и получше.

Инструменты веб‑разработчика    ↑

Если вы просматриваете эту страницу на настольном или портативном компьютере, то можете воспользоваться инструментами веб‑разработчика, которые обеспечивают сопоставимый опыт работы с конкурирующими веб‑браузерами.

Примечание. В следующих примерах используется Google Chrome версии 80.0. Сочетания клавиш в других браузерах могут отличаться, но интерфейс должен быть в основном таким же.

Чтобы переключить эти инструменты, обратитесь к документации вашего браузера или попробуйте одно из следующих распространенных сочетаний клавиш:

  • F12
  • Ctrl + Shift + I
  • Cmd + Option + I

Эта функция может быть отключена по умолчанию, например, если вы используете Apple Safari или Microsoft Edge. После активации инструментов веб‑разработчика вы увидите множество вкладок и панелей инструментов с таким содержанием:

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

На данный момент, однако, сосредоточьтесь на консоли,к которому можно получить доступ, щелкнув вкладку, расположенную вверху. Кроме того, можно быстро вывести его на передний план, нажав Esc в любое время при использовании инструментов веб‑разработчика.

Консоль в основном используется для проверки сообщений журнала, отправляемых текущей веб‑страницей, но она также может быть отличным подспорьем в обучении JavaScript. Как и в случае с интерактивным интерпретатором Python, можно ввести код JavaScript прямо в консоль, чтобы он выполнялся на лету:

В нем есть все, что вы ожидаете от типичного инструмента REPL, и многое другое. В частности, консоль поставляется с подсветкой синтаксиса, контекстным автозаполнением, историей команд, редактированием строк, аналогичным GNU Readline, и возможностью отображать интерактивные элементы. Его возможности визуализации могут быть особенно полезны для самоанализа объектов и табличных данных, переход к исходному коду из трассировки стека или просмотр элементов HTML.

Вы можете записывать пользовательские сообщения в консоль, используя предопределенный объект консоли. JavaScript console.log() эквивалентен print() в Python:

console.log('hello world');

Это заставит сообщение появиться на вкладке консоли в инструментах веб‑разработчика. Кроме того, в объекте консоли доступно еще несколько полезных методов.

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, чтобы повысить удобочитаемость.Чаще встречается один или несколько тегов <script>, ссылающихся на внешние файлы с помощью кода JavaScript. Эти файлы могут обслуживаться как локальным, так и удаленным веб‑сервером.

Тег <script> может появляться в любом месте документа, если он вложен в тег <head> или <body>:

<html>
<head>
  <meta charset="UTF-8">
  <title>Home Page</title>
  <script src="https://server.com/library.js"></script>
  <script src="local/assets/app.js"></script>
  <script>
    function add(a, b) {
      return a + b;
    }
  </script>
</head>
<body>
  <p>Lorem ipsum dolor sit amet (...)</p>
  <script>
    console.log(add(2, 3));
  </script>
</body>
</html>

Важно то, как веб‑браузеры обрабатывают HTML‑документы. Документ читается сверху вниз. Каждый раз, когда обнаруживается тег <script>, он немедленно выполняется даже до того, как страница будет полностью загружена. Если ваш скрипт пытается найти элементы HTML, которые еще не были обработаны, вы получите сообщение об ошибке.

Чтобы обезапасить себя, всегда помещайте теги <script> внизу тела документа:

<html>
<head>
  <meta charset="UTF-8">
  <title>Home Page</title>
</head>
<body>
  <p>Lorem ipsum dolor sit amet (...)</p>
  <script src="https://server.com/library.js"></script>
  <script src="local/assets/app.js"></script>
  <script>
    function add(a, b) {
      return a + b;
    }
  </script>
  <script>
    console.log(add(2, 3));
  </script>
</body>
</html>

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

<script src="https://server.com/library.js" defer></script>

Если вы хотите узнать больше о смешивании JavaScript с HTML, взгляните на Учебное пособие по JavaScript от W3Schools.

Node.js    ↑

Вам больше не нужен веб‑браузер для выполнения кода JavaScript. Существует инструмент под названием Node.js, который предоставляет среду выполнения для серверного JavaScript.

Среда выполнения включает движок JavaScript, который является интерпретатором или компилятором языка, а также API для взаимодействия с миром. Есть несколько альтернативных движков, которые поставляются с разными веб‑браузерами:

Web Browser Движок JavaScript
Apple Safari JavaScriptCore
Microsoft Edge V8
Microsoft IE Chakra
Mozilla Firefox SpiderMonkey
Google Chrome V8

Каждый из них реализуется и поддерживается поставщиком. Однако для конечного пользователя заметной разницы нет, за исключением производительности отдельных движков. Node.js использует тот же движок V8, который был разработан Google для своего браузера Chrome.

При запуске JavaScript в веб‑браузере вы обычно хотите иметь возможность реагировать на щелчки мыши, динамически добавлять HTML‑элементы или получать изображение с веб‑камеры. Но это не имеет смысла в приложении Node.js, которое работает вне браузера.

После того, как вы установили Node.js для своей платформы, можно выполнять код JavaScript так же, как с интерпретатором Python. Чтобы начать интерактивный сеанс, перейдите в свой терминал и введите node:

$ node
> 2 + 2
4

Это похоже на консоль веб‑разработчика, которую вы видели ранее. Однако, как только вы попытаетесь сослаться на что-то, связанное с браузером, то получите сообщение об ошибке:

> alert('hello world');
Thrown:
ReferenceError: alert is not defined

Это потому, что в вашей среде выполнения отсутствует другой компонент, которым является API браузера. В то же время Node.js предоставляет набор API, которые полезны во внутреннем приложении, например API файловой системы:

> const fs = require('fs');
> fs.existsSync('/path/to/file');
false

По соображениям безопасности вы не найдете эти API в браузере. Представьте, что вы позволяете случайному веб‑сайту контролировать файлы на вашем компьютере!

Если стандартная библиотека вам не подходит, вы всегда можете установить сторонний пакет с помощью Node Package Manager (npm), который поставляется со средой Node.js. Чтобы просмотреть или найти пакеты, перейдите в общедоступный реестр npm, который похож на индекс пакетов Python (PyPI).

Подобно команде python, можно запускать скрипты с помощью Node.js:

$ echo "console.log('hello world');" > hello.js
$ node hello.js
hello world

Предоставляя путь к текстовому файлу с кодом JavaScript внутри, вы даете Node.js указание запустить этот файл вместо запуска нового интерактивного сеанса.

В Unix-подобных системах вы даже можете указать, какую программу запускать файл, используя комментарий Шебанг (Unix) в самой первой строке файла:

#!/usr/bin/env node
console.log('hello world');

Комментарий должен быть путем к исполняемому файлу Node.js. Однако, чтобы избежать жесткого кодирования абсолютного пути, который может отличаться в зависимости от установки, лучше позволить средству env выяснить, где на вашем компьютере установлен Node.js.

Затем вам нужно сделать файл исполняемым, прежде чем вы сможете запускать его, как если бы это был скрипт Python:

$ chmod +x hello.js
$ ./hello.js
hello world

Путь к созданию полноценных веб‑приложений с помощью Node.js долог и извилист, как и путь к написанию приложений Django или Flesk на Python.

Иностранный язык    ↑

Иногда среда выполнения для JavaScript может быть другим языком программирования. Это типично для языков сценариев в целом. Например, Python широко используется при разработке плагинов. Вы найдете его в редакторе Sublime Text, GIMP и Blender.

Вот вам пример для оценки кода JavaScript в программе Java с использованием сценария API:

package org.example;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class App {

    public static void main(String[] args) throws ScriptException {

        final ScriptEngineManager manager = new ScriptEngineManager();
        final ScriptEngine engine = manager.getEngineByName("javascript");

        System.out.println(engine.eval("2 + 2"));
    }
}

Это расширение Java, хотя оно может быть недоступно на вашей конкретной Java Virtual Machine. Последующие поколения Java объединяют альтернативные механизмы написания сценариев, такие как Rhino, Nashorn и GraalVM.

Почему это полезно?

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

JavaScript против Python    ↑

В этом разделе мы сравним Python и JavaScript с точки зрения Pythonista. Впереди появятся некоторые новые концепции, но и некоторое сходство между двумя языками увидим.

Примеры использования    ↑

Python — это универсальный, многопарадигмальный, высокоуровневый, кросс-платформенный интерпретируемый язык программирования с богатой стандартной библиотекой и доступным синтаксисом.

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

С другой стороны, JavaScript возник исключительно как язык сценариев на стороне клиента, чтобы сделать HTML‑документы немного более интерактивными. Он намеренно прост и имеет единственную цель: добавление поведения в пользовательский интерфейс. Это актуально и сегодня, несмотря на его улучшенные возможности. С помощью Javascript можно создавать не только веб‑приложения, но также настольные программы и мобильные приложения. Специально разработанные среды выполнения позволяют выполнять JavaScript на сервере или даже на устройствах Интернета вещей.

Философия    ↑

Python подчеркивает удобочитаемость и ремонтопригодность кода ценой его выразительности. В конце концов, вы не можете даже слишком сильно отформатировать свой код, не нарушив его. Вы также не найдете эзотерических операторов, как в C++ или Perl, поскольку большинство операторов Python являются английскими словами. Некоторые шутят, что Python — это исполняемый Псевдокод (язык описания алгоритмов) благодаря простому синтаксису.

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

Версии    ↑

До недавнего времени вы могли найти две в значительной степени несовместимые версии Python, доступные для загрузки на его официальном сайте. Значительное различие между Python 2.7 и Python 3.x сбивало с толку новичков и было основным фактором, замедляющим внедрение последней ветки разработки.

В январе 2020 года, после многих лет отсрочки, поддержка Python 2.7 была окончательно прекращена. Однако, несмотря на надвигающееся отсутствие обновлений безопасности и предупреждений, выпущенных некоторыми государственными учреждениями, все еще есть много проектов, которые еще не были перенесены:

Хронология версий JavaScript и Python
Хронология версий JavaScript и Python

Брендан Эйх (Brendan Eich) создал JavaScript в 1995 году, но ECMAScript, который мы знаем сегодня, был стандартизирован двумя годами позже. С тех пор было выпущено всего несколько выпусков, что выглядит застойным по сравнению с несколькими новыми версиями Python, выпускаемыми каждый год в течение того же периода.

Обратите внимание на разрыв между ES3 и ES5, который длился целое десятилетие! Из-за политических конфликтов и разногласий в техническом комитете ES4 так и не попал в веб‑браузеры, но был использован Macromedia (позже Adobe) в качестве основы для ActionScript.

Первый капитальный ремонт JavaScript произошел в 2015 году с появлением ES6, также известного как ES2015 или ECMAScript Harmony. Он принес много новых синтаксических конструкций, которые сделали язык более зрелым, безопасным и удобным для программиста. Это также стало поворотным моментом в графике выпуска ECMAScript, который теперь обещает новую версию каждый год. Такой быстрый прогресс означает, что нельзя предполагать, что последняя языковая версия была принята всеми основными веб‑браузерами, поскольку для развертывания обновлений требуется время. Поэтому преобладают Транспайлер и Полифил. Сегодня практически любой современный веб‑браузер может поддерживать ES5, который является целью по умолчанию для Транспайлер.

Время выполнения    ↑

Чтобы запустить программу 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:

$ 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
Linter 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
Web-фреймворки Django, Flask, Tornado Angular, React, Vue.js

Этот список, конечно, не полный. Более того, некоторые из упомянутых выше инструментов имеют перекрывающиеся возможности, поэтому сложно провести сравнение яблок с яблоками в разных вареньях.

Иногда нет прямой аналогии между Python и JavaScript. Например, хотя вы, возможно, привыкли создавать изолированные виртуальные среды для своих проектов Python, Node.js решает это из коробки, устанавливая зависимости в локальную папку.

И наоборот, для проектов JavaScript могут потребоваться дополнительные инструменты, уникальные для интерфейсной разработки. Одним из таких инструментов является Babel, который переносит ваш код в соответствии с различными плагинами, сгруппированными в пресеты. Он может обрабатывать экспериментальные функции ECMAScript, а также синтаксис расширения TypeScript и даже JSX React.

Другой категорией инструментов является сборщик модулей, роль которого заключается в объединении нескольких независимых исходных файлов в один, который может быть легко использован веб‑браузером.

Во время разработки вы хотите разбить свой код на многоразовые, тестируемые и автономные модули. Это разумно для опытного программиста на Python. К сожалению, изначально в JavaScript не было поддержки модульности. Для этого по-прежнему нужно использовать отдельный инструмент, хотя это требование меняется. Популярными вариантами сборщиков модулей являются webpack, Parcel и Browserify, который также может обрабатывать статические активы.

Затем у вас есть инструменты автоматизации сборки, такие как Grunt и gulp. Они отдаленно похожи на Fabric и Ansible в Python, хотя используются локально. Эти инструменты автоматизируют утомительные задачи, такие как копирование файлов или запуск транспайлятора.

В крупномасштабном одно-страничное приложение (SPA) с множеством интерактивных элементов пользовательского интерфейса, вам может потребоваться специализированная библиотека, такая как Redux или MobX, для управления состоянием. Эти библиотеки не привязаны к какой-либо конкретной интерфейсной структуре, но их можно быстро подключить.

Как видите, изучение экосистемы JavaScript — это бесконечное путешествие.

Модель памяти    ↑

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

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

Ортодоксальная реализация CPython использует подсчет ссылок, а также недетерминированную сборку мусора (GC — garbage collection) для работы со ссылочными циклами. Иногда вам может потребоваться вручную выделить и освободить память, когда вы решитесь написать собственный модуль расширения C.

В JavaScript фактическая реализация управления памятью также остается на усмотрение вашего конкретного движка и версии, поскольку она не является частью спецификации языка. Основной стратегией для сборки мусора обычно является алгоритм отметки и очистки (mark-and-sweep), но существуют различные методы оптимизации.

Например, куча может быть организована в поколения, которые отделяют короткоживущие объекты от долгоживущих. Сборка мусора может выполняться одновременно, чтобы разгрузить основной поток выполнения. Постепенный подход может помочь избежать полной остановки программы на время очистки памяти.

Система типов JavaScript
Вам, должно быть, не терпится узнать о синтаксисе JavaScript, но сначала давайте кратко рассмотрим его систему типов. Это один из самых важных компонентов, определяющих любой язык программирования.

Проверка типа    ↑

И Python, и JavaScript динамически типизируются, потому что они проверяют типы во время выполнения, когда приложение выполняется, а не во время компиляции. Это удобно, потому что вам не нужно объявлять тип переменной, такой как int или str:

>>> data = 42
>>> data = 'Это строка'

Здесь вы повторно используете одно и то же имя переменной для двух разных типов сущностей, которые имеют разные представления в памяти компьютера. Сначала это целое число, а затем — кусок текста.

Примечание. Стоит отметить, что некоторые языки со статической типизацией, такие как Scala, также не требуют явного объявления типа, если оно может быть получено из контекста.

Динамическую типизацию часто неправильно понимают как отсутствие каких-либо типов. Это происходит из языков, в которых переменная работает как ящик, который может соответствовать только определенному типу объекта. И в Python, и в JavaScript информация о типе привязана не к переменной, а к объекту, на который она указывает. Такая переменная — это просто псевдоним, метка или указатель на какой-то объект в памяти.

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

Более того, это значительно усложняет рассуждение о коде как для людей, так и для редакторов кода. Python решил эту проблему, введя подсказку типов, которую можно добавить к переменным:

data: str = Это строка'

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

Идея постепенного набора текста была заимствована из TypeScript, который по сути представляет собой JavaScript с типами, которые можно преобразовать обратно в простой старый JavaScript.

Еще одна общая черта обоих языков — использование утиной печати (duck typing) для проверки совместимости типов. Однако область, в которой Python и JavaScript значительно отличаются, — это сила их механизмов проверки типов.

Python демонстрирует строгую типизацию, отказываясь воздействовать на объекты несовместимых типов. Например, можно использовать оператор плюс (+) для сложения чисел или объединения строк, но вы не можете смешивать их:

>>> '3' + 2
Traceback (most recent call last):
  File "", line 1, in 
TypeError: can only concatenate str (not "int") to str

Интерпретатор не будет косвенно преобразовывать один тип в другой. Вам нужно решить для себя и произвести назначения подходящего типа вручную. Если вам нужна алгебраическая сумма, вы должны сделать так:

>>> int('3') + 2
5

Чтобы соединить две строки вместе, необходимо соответственно преобразовать тип второго операнда:

>>> '3' + str(2)
>>> '32'

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

В том же примере, что и раньше, JavaScript будет неявно преобразовывать числа в строки, когда вы используете оператор плюс (+):

> '3' + 2
'32'

Это замечательно, если это желаемое поведение. В противном случае вам придется выдергивать волосы, пытаясь найти первопричину логической ошибки. Но это становится еще более забавным, чем это. Посмотрим, что произойдет, если вы измените оператор на другой:

> '3' — 2
1

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

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

Примечание. Сильная и слабая типизация не зависит от статической и динамической типизации. Например, язык программирования C статически и слабо типизирован одновременно.

Напомним, что JavaScript не только динамически типизирован, но и поддерживает утиную типизацию.

Типы JavaScript    ↑

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

Во-первых, есть только несколько предопределенных примитивных типов, о которых вам нужно позаботиться, потому что вы не можете создать свои собственные. Большинство встроенных типов данных, поставляемых с JavaScript, являются ссылочными типами.Это единственные примитивные типы, доступные в JavaScript:

  • boolean
  • null
  • number
  • string
  • symbol (since ES6)
  • undefined

С другой стороны, вот несколько ссылочных типов, которые поставляются с готовым JavaScript:

  • Array
  • Boolean
  • Date
  • Map
  • Number
  • Object
  • RegExp
  • Set
  • String
  • Symbol
  • (…)

Также предлагается включить в ES11 новый числовой тип BigInt, который уже поддерживается некоторыми браузерами. Помимо этого, любые пользовательские типы данных, которые можно определить, будут ссылочными типами.

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

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

> 'Lorem ipsum'.length
11

Несмотря на то, что строковый литерал в JavaScript является примитивным типом данных, можно проверить его атрибут .length. Под капотом происходит то, что ваш код заменяется вызовом конструктора объекта String:

> new String('Lorem ipsum').length
11

Конструктор — это специальная функция, которая создает новый экземпляр данного типа. Вы можете видеть, что атрибут .length определяется объектом String. Этот механизм упаковки известен как автобоксирование (autoboxing) и был скопирован непосредственно с языка программирования Java.

Другое и более ощутимое различие между примитивными и ссылочными типами заключается в том, как они передаются. В частности, всякий раз, когда вы присваиваете или передаете значение примитивного типа, вы фактически создаете копию этого значения в памяти. Вот пример:

> x = 42
> y = x
> x++  // This is short for x += 1
> console.log(x, y)
43 42

Присваивание y = x создает новое значение в памяти. Теперь у вас есть две отдельные копии числа 42, на которые ссылаются x и y, поэтому увеличение одной не влияет на другую.

Однако, когда вы передаете ссылку на литерал объекта, обе переменные указывают на один и тот же объект в памяти:

> x = {name: 'Person1'}
> y = x
> x.name = 'Person2'
> console.log(y)
{name: 'Person2'}

Объект — это ссылочный тип в JavaScript. Здесь у вас есть две переменные, x и y, относящиеся к одному и тому же экземпляру объекта Person. Изменение одной из переменных отражается в другой переменной.

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

Примечание: честно говоря, это почти идентично тому, как Python работает с передаваемыми объектами, несмотря на отсутствие примитивных типов. Изменяемые типы, такие как list и dict, не создают копий, в то время как неизменяемые типы, такие как int и str, делают.

Чтобы проверить, является ли переменная примитивным типом или ссылочным типом в JavaScript, можно использовать встроенный оператор typeof:

> typeof 'Lorem ipsum'
'string'
> typeof new String('Lorem ipsum')
'object'

Для ссылочных типов оператор typeof всегда возвращает универсальную строку «объекта».

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

> typeof noSuchVariable === 'undefined'
true
> noSuchVariable === undefined
ReferenceError: noSuchVariable is not defined

Сравнение несуществующей переменной с любым значением вызовет исключение!

Если вы хотите получить более подробную информацию о конкретном типе, у вас есть несколько вариантов:

> today = new Date()
> today.constructor.name
'Date'
> today instanceof Date
true
> Date.prototype.isPrototypeOf(today)
true

Вы можете попробовать проверить имя конструктора объекта с помощью оператора instanceof или проверить, является ли он производным от определенного родительского типа с помощью свойства .prototype.

Иерархия типов    ↑

Python и JavaScript — это объектно-ориентированные языки программирования. Оба они позволяют выражать код в терминах объектов, которые инкапсулируют идентичность, состояние и поведение. В то время как большинство языков программирования, включая Python, используют наследование на основе классов, JavaScript — один из немногих, которые этого не делают.

Примечание. Класс — это шаблон для объектов. Вы можете думать о таких классах, как формочки для печенья или фабрики объектов.

Чтобы создавать иерархии пользовательских типов в JavaScript, вам необходимо познакомиться с прототипным наследованием. Часто это одна из самых сложных концепций для понимания, когда вы переходите от более классической модели наследования.

Примечание. В отличие от Python, в JavaScript невозможно множественное наследование, поскольку у любого заданного объекта может быть только один прототип. Тем не менее, можно использовать прокси-объекты, которые были введены в ES6, чтобы смягчить это.

Суть истории в том, что в JavaScript нет классов. Технически можно использовать ключевое слово class, которое было введено в ES6, но это чисто синтаксический сахар, облегчающий жизнь новичкам. Прототипы по-прежнему используются за кадром, поэтому стоит взглянуть на них поближе, что у вас будет возможность сделать позже.

Тип функции    ↑

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

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

В отношении функций JavaScript даже более гибок, чем Python. Вы можете определить выражение анонимной функции, полное операторов с побочными эффектами, тогда как лямбда-функция Python должна содержать ровно одно выражение и никаких операторов:

let countdown = 5;
const id = setInterval(function() {
  if (countdown > 0) {
    console.log(`${countdown--}...`);
  } else if (countdown === 0) {
    console.log('Go!');
    clearInterval(id);
  }
}, 1000);

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

Синтаксис JavaScript    ↑

JavaScript и Python — это языки сценариев высокого уровня, которые имеют довольно много синтаксического сходства. Особенно это касается их последних версий. Тем не менее, JavaScript был разработан, чтобы напоминать Java, тогда как Python был смоделирован после языков ABC (язык программирования) и Modula-3.

Блоки кода    ↑

Одной из отличительных черт Python является использование обязательного отступа для обозначения блока кода, что довольно необычно и не одобряется новыми преобразователями Python. Многие популярные языки программирования, включая JavaScript, вместо них используют фигурные скобки или специальные ключевые слова:

function fib(n)
{
  if (n > 1) {
    return fib(n-2) + fib(n-1);
  }
  return 1;
}

В JavaScript каждый блок кода, состоящий из более чем одной строки, нуждается в открытии {и закрытии}, что дает вам свободу форматировать код, как вам нравится. Вы можете смешивать табуляции с пробелами, и вам не нужно обращать внимание на расположение скобок.

К несчастью, это может привести к запутанному коду и межконфессиональным конфликтам между разработчиками с разными стилевыми предпочтениями. Это делает проверку кода проблематичной. Следовательно, вы всегда должны устанавливать стандарты кодирования для своей команды и использовать их последовательно, желательно в автоматическом режиме.

Примечание: можно упростить тело функции выше, воспользовавшись тройным условием if (? :), который иногда называют оператором Элвиса, потому что он похож на прическу известного певца:

return (n > 1) ? fib(n-2) + fib(n-1) : 1;

Это эквивалентно условному выражению в Python.

Говоря об отступах, код JavaScript обычно форматируется с использованием двух пробелов на уровень отступа вместо четырех, рекомендованных в Python.

Операторы    ↑

Чтобы уменьшить трудности для тех, кто переключается с Java или другого языка программирования семейства C, JavaScript завершает операторы знакомой точкой с запятой (;). Если вы когда-либо программировали на одном из этих языков, то вы знаете, что установка точки с запятой после инструкции становится мышечной памятью:

alert('hello world');

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

Примечание: вы также можете использовать точку с запятой в Python! Хотя они не очень популярны, они помогают изолировать несколько утверждений в одной строке:

import pdb; pdb.set_trace()

Люди твердо придерживаются мнения о том, следует ли использовать точку с запятой явно или нет. Хотя в некоторых случаях это имеет значение, в основном это просто условность.

Идентификаторы    ↑

Идентификаторы, такие как имена переменных или функций, должны быть буквенно-цифровыми в JavaScript и Python. Другими словами, они могут содержать только буквы, цифры и несколько специальных символов. В то же время они не могут начинаться с цифры. Хотя использование нелатинских символов разрешено, их обычно следует избегать:

  • Правильно: foo, foo42, _foo, $foo, fößar
  • Неправильно: 42foo

Имена на обоих языках чувствительны к регистру, поэтому такие переменные, как foo и Foo, различны. Тем не менее, соглашения об именах в JavaScript немного отличаются от Python:

Python JavaScript
Тип ProjectMember ProjectMember
Переменная, атрибут или функция first_name firstName

В общем, Python рекомендует использовать lower_case_with_underscores, также известный как snake_case, для составных имен, чтобы отдельные слова разделялись символом подчеркивания (_). Единственным исключением из этого правила являются классы, имена которых должны соответствовать стилю CapitalizedWords или Pascal. JavaScript также использует CapitalizedWords для типов, но mixedCase или более низкий CamelCase для всего остального.

Комментарии    ↑

В JavaScript есть как однострочные, так и многострочные комментарии:

x++;  // This is a single-line comment

/*
 This whole paragraph
 is a comment and will
 be ignored.
*/

Вы можете начать комментарий в любом месте строки, используя двойную косую черту (//), что похоже на знак решетки Python (#). Хотя в Python нет многострочных комментариев, можно имитировать их, заключив фрагмент кода в тройную кавычку ('''), чтобы создать многострочную строку. В качестве альтернативы, можно заключить его в оператор if, который никогда не оценивается как True:

if False:
    ...

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

Строковые литералы    ↑

Чтобы определить строковые литералы в JavaScript, можно использовать пару одинарных кавычек (') или двойных кавычек (") взаимозаменяемо, как и в Python. Однако долгое время в JavaScript не было возможности определять многострочные строки. Только ES6 в 2015 году представил литералы шаблонов, которые выглядят как гибрид f-строки и многострочные строки, заимствованные из Python:

var name = 'John Doe';
var message = `Hi ${name.split(' ')[0]},

We're writing to you regarding...

Kind regards,
Xyz
`;

Шаблон начинается с обратной кавычки (`), также известной как серьезный акцент, вместо обычных кавычек. Чтобы интерполировать переменную или любое допустимое выражение, вы должны использовать знак доллара, за которым следует пара соответствующих фигурных скобок: ${...}. Это отличается от f-строк в Python, для которых не нужен знак доллара.

Области действия переменных    ↑

Когда вы определяете переменную в 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. Это отличается от Python, где вы всегда определяете переменные, за исключением аннотаций переменных. Но даже тогда эти переменные технически не объявлены:

name: str
name = 'John Doe'

Такая аннотация не влияет на жизненный цикл переменной. Если вы упомянули имя перед назначением, вы получите исключение NameError.

Операторы switch    ↑

Если вы жаловались на то, что Python не имеет правильного оператора switch, вы будете рады узнать, что в JavaScript он есть:

// Как и в C, предложения будут пропадать, если вы не выйдете из них.
switch (expression) {
  case 'kilo':
    value = bytes / 2**10;
    break;
  case 'mega':
    value = bytes / 2**20;
    break;
  case 'giga':
    value = bytes / 2**30;
    break;
  default:
    console.log(`Unknown unit: "${expression}"`);
}

Выражение может оцениваться как любой тип, включая строку, что не всегда имело место в старых версиях Java, которые повлияли на JavaScript. Кстати, вы заметили знакомый оператор возведения в степень (**) в приведенном выше фрагменте кода? Он не был доступен в JavaScript до ES7 в 2016 году.

Перечисления    ↑

В чистом JavaScript нет собственного типа перечисления (enumeration), но можно использовать тип enum в TypeScript или эмулировать его с чем-то похожим на это:

const Sauce = Object.freeze({
  BBQ: Symbol('bbq'),
  CHILI: Symbol('chili'),
  GARLIC: Symbol('garlic'),
  KETCHUP: Symbol('ketchup'),
  MUSTARD: Symbol('mustard')
});

Замораживание объекта предотвращает добавление или удаление его атрибутов. Это отличается от константы, которая может изменяться! Константа всегда будет указывать на один и тот же объект, но сам объект может изменить свое значение:

> const fruits = ['apple', 'banana'];
> fruits.push('orange'); // ['apple', 'banana', 'orange']
> fruits = [];
TypeError: Assignment to constant variable.

Вы можете добавить апельсин ('orange') в массив, который является изменяемым, но вы не можете изменить константу, указывающую на него.

Стрелочные функции    ↑

До ES6 вы могли определять функцию или анонимное выражение функции только с помощью ключевого слова function:

function add(a, b) {
  return a + b;
}

let add = function(a, b) {
  return a + b;
};

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

let add = (a, b) => a + b;

Обратите внимание, что ключевого слова function больше нет, а оператор return является неявным. Символ стрелки (=>) отделяет аргументы функции от ее тела. Иногда люди называют это функцией толстой стрелки, потому что она изначально была заимствована из CoffeeScript, который также имеет аналог в виде тонкой стрелки (->).

Стрелочные функции больше всего подходят для небольших анонимных выражений, таких как лямбды в 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, значения по умолчанию разрешаются каждый раз, когда функция вызывается, а не только тогда, когда она определена. Это позволяет безопасно использовать изменяемые типы, а также динамически ссылаться на другие аргументы, передаваемые во время выполнения:

> function foo(a, b=a+1, c=[]) {
…   c.push(a);
…   c.push(b);
…   console.log(c);
… }
> foo(1);
[1, 2]
> foo(5);
[5, 6]

Каждый раз, когда вы вызываете foo(), ее аргументы по умолчанию извлекаются из фактических значений, переданных в функцию.

Функции с изменяемым количеством параметров    ↑

Если вы хотите объявить функцию с переменным количеством параметров в Python, вы воспользуетесь специальным синтаксисом *args. Эквивалентом в JavaScript был бы параметр rest, определенный с помощью оператора spread (...):

> 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.`);

Это помогает избежать конфликтов имен для переменных, определенных в одной области.

Оператор with    ↑

Существует альтернативный способ детализации атрибутов объекта с помощью немного устаревшего оператора with:

const person = {name: 'John Doe', age: 42, married: true};
with (person) {
  console.log(`${name} is ${age} years old.`);
}

Он работает как конструкция в Object Pascal, в которой локальная область видимости временно дополняется атрибутами данного объекта.

Примечание. Операторы with в Python и JavaScript — ложные друзья переводчика. В Python вы используете оператор with для управления ресурсами через диспетчеры контекста.

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

Итераторы, итераторы и генераторы    ↑

Начиная с ES6, в JavaScript есть протоколы итераций и итераторов, а также функции генераторов, которые выглядят почти идентичными итерациям, итераторам и генераторам Python. Чтобы превратить обычную функцию в функцию-генератор, вам нужно добавить звездочку (*) после ключевого слова функции:

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. Генератор будет продолжать подавать значения до тех пор, пока не будет больше операторов yield или пока вы не вернетесь преждевременно:

let shouldStopImmediately = false;

function* randomNumberGenerator(maxTries=3) {
  let tries = 0;
  while (tries++ < maxTries) {
    if (shouldStopImmediately) {
      return 42; // Значение не обязательно
    }
    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}

Что интересно, законно возвращать и отдавать одновременно:

function* makeGenerator() {
  return yield 42;
}

Однако, из-за грамматических ограничений вам придется использовать круглые скобки для достижения того же эффекта в Python:

def make_generator():
    return (yield 42)

Чтобы объяснить, что происходит, вы можете переписать этот пример, введя вспомогательную константу:

function* makeGenerator() {
  const message = yield 42;
  return message;
}

Если вы знакомы с сопрограммами в Python, вы должны помнить, что объекты-генераторы могут быть как производителями, так и потребителями. Вы можете отправить произвольные значения в приостановленный генератор, указав необязательный аргумент для .next():

> function* makeGenerator() {
…   const message = yield 'ping';
…   return message;
… }
> const generator = makeGenerator();
> generator.next();
{value: "ping", done: false}
> generator.next('pong');
{value: "pong", done: true}

Первый вызов .next() запускает генератор до первого выражения yield, которое возвращает «ping». Второй вызов передает «pong», который сохраняется в константе и немедленно возвращается из генератора.

Асинхронные функции    ↑

Изящный механизм, рассмотренный выше, стал основой для асинхронного программирования и принятия ключевых слов async и await в Python. JavaScript пошел по тому же пути, добавив асинхронные функции с ES8 в 2017 году.

В то время как функция генератора возвращает особый вид итератора, объект генератора, асинхронные функции всегда возвращают обещание, что впервые было введено в ES6. Обещание представляет собой будущий результат асинхронного вызова, такого как fetch(), из Fetch API.

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

async function greet(name) {
  return `Hello ${name}`;
}

async function main() {
  const promise = greet('John');
  const greeting = await promise;
  console.log(greeting); // "Hello John"
}

main();

Обычно вы ждете и назначаете результат за один раз:

const greeting = await greet('John');

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

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

Объекты и конструкторы    ↑

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

let person = {
  name: 'John Doe',
  age: 42,
  married: true
};

person ведет себя как словарь в том смысле, что вы можете получить доступ к отдельным атрибутам, используя точечный синтаксис или квадратные скобки:

> person.age++;
> person['age'];
43

Атрибуты объекта не нужно заключать в кавычки, если они не содержат пробелов, но это не обычная практика:

> let person = {
…   'full name': 'John Doe'
… };
> person['full name'];
'John Doe'
> person.full name;
SyntaxError: Unexpected identifier

Так же, как словарь и некоторые объекты в 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.

sayHi() тесно связан с объектом, которому он принадлежит, потому что он ссылается на переменную человека по имени. Если бы вам пришлось переименовать эту переменную в какой-то момент, вам пришлось бы пройти через весь объект и обязательно обновить все вхождения этого имени переменной. Немного лучший подход использует преимущества неявной переменной this, которая предоставляется функциям. Значение этого может быть разным в зависимости от того, кто вызывает функцию:

> let jdoe = {
…   name: 'John Doe',
…   sayHi: function() {
…     console.log(`Hi, my name is ${this.name}.`);
…   }
… };
> jdoe.sayHi();
Hi, my name is John Doe.

После замены жестко запрограммированного person this, который похож на сам Python, не будет иметь значения, каково имя переменной, и результат будет таким же, как и раньше.

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

Это здорово, но как только вы решите ввести больше объектов одного типа Person, вам придется повторить все атрибуты и переопределить все функции в каждом объекте. Вы бы предпочли шаблон для объектов Person.

Канонический способ создания пользовательских типов данных в JavaScript — определить конструктор, который является обычной функцией:

function Person() {
  console.log('Calling the constructor');
}

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

Однако на синтаксическом уровне это просто функция, которую вы можете вызывать обычным образом:

> Person();
Calling the constructor
undefined

Что делает её особенной, так это то, как вы её называете:

> new Person();
Calling the constructor
Person {}

Когда вы добавляете ключевое слово new перед вызовом функции, оно неявно возвращает совершенно новый экземпляр объекта JavaScript. Это означает, что ваш конструктор не должен содержать оператора return.

Хотя интерпретатор отвечает за выделение памяти и формирование нового объекта, роль конструктора — придать объекту начальное состояние. Вы можете использовать ранее упомянутое ключевое слово 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');

Хорошо, но вы по-прежнему дублируете определения функций во всех экземплярах типа Person. Конструктор — это просто фабрика, которая связывает одни и те же значения с отдельными объектами. Это расточительно и может привести к непоследовательному поведению, если вы в какой-то момент измените его. Учти это:

> const jdoe = new Person('John Doe');
> const jsmith = new Person('John Smith');
> jsmith.sayHi = _ => console.log('What?');
> jdoe.sayHi();
Hi, my name is John Doe.
> jsmith.sayHi();
What?

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

Прототипы    ↑

Как правило, вы должны переместить бизнес-логику из конструктора, который занимается данными, в объект-прототип:

function Person(name) {
  this.name = name;
}

Person.prototype.sayHi = function() {
  console.log(`Hi, my name is ${this.name}.`);
};

У каждого объекта есть прототип. Вы можете получить доступ к прототипу вашего пользовательского типа данных, обратившись к атрибуту .prototype вашего конструктора. У него уже есть несколько предопределенных атрибутов, таких как .toString(), которые являются общими для всех объектов в JavaScript. Вы можете добавить дополнительные атрибуты с помощью собственных методов и значений.

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

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

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

> Person.prototype.PI = 3.14;
> new Person('John Doe').PI;
3.14
> new Person('John Smith').PI;
3.14

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

String.prototype.toSnakeCase = function() {
  return this.replace(/\s+/g, '')
             .split(/(?<=[a-z])(?=[A-Z])/g)
             .map(x => x.toLowerCase())
             .join('_');
};

Здесь используются регулярные выражения для преобразования текста в snake_case. Неожиданно от этого могут выиграть строковые переменные, константы и даже строковые литералы:

> "loremIpsumDolorSit".toSnakeCase();
'lorem_ipsum_dolor_sit'

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

Классы    ↑

Начиная с ES6, появился альтернативный способ определения прототипов, использующий гораздо более знакомый синтаксис:

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log(`Hi, my name is ${this.name}.`);
  }
}

Несмотря на то, что это выглядит так, как будто вы определяете класс, это всего лишь удобная высокоуровневая метафора для определения пользовательских типов данных в JavaScript. За кадром настоящих классов нет! По этой причине некоторые люди заходят так далеко, что вообще выступают против использования этого нового синтаксиса.

У вас могут быть getters и setters в вашем классе, которые похожи на свойства класса Python:

> class Square {
…   constructor(size) {
…     this.size = size; // Triggers the setter
…   }
…   set size(value) {
…     this._size = value; // Sets the private field
…   }
…   get area() {
…     return this._size**2;
…   }
… }
> const box = new Square(3);
> console.log(box.area);
9
> box.size = 5;
> console.log(box.area);
25

Когда вы опускаете setters, то создаете свойство только для чтения. Однако это вводит в заблуждение, потому что вы все еще можете получить доступ к базовому частному полю, как и в Python.

Распространенным шаблоном для инкапсуляции внутренней реализации в JavaScript является выражение немедленного вызова функции (Immediately Invoked Function Expression — IIFE), которое может выглядеть следующим образом:

> const odometer = (function(initial) {
…   let mileage = initial;
…   return {
…     get: function() { return mileage; },
…     put: function(miles) { mileage += miles; }
…   };
… })(33000);
> odometer.put(65);
> odometer.put(12);
> odometer.get();
33077

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

const odometer = ((initial) => {
  let mileage = initial;
  return {
    get: _ => mileage,
    put: (miles) => mileage += miles
  };
})(33000);

Именно так JavaScript исторически эмулировал модули, чтобы избежать конфликтов имен в глобальном пространстве имен. Без IIFE, который использует замыкания и область действия для предоставления только ограниченного общедоступного API, все было бы доступно из вызывающего кода.

Иногда вы хотите определить фабрику или служебную функцию, которая логически принадлежит вашему классу. В Python у вас есть декораторы @classmethod и @staticmethod, которые позволяют связывать статические методы с классом. Чтобы добиться того же результата в JavaScript, вам нужно использовать модификатор статического метода:

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);

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

Цепочка прототипов может напоминать наследование классов, когда вы расширяете один класс из другого:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

class Gentleman extends Person {
  signature() {
    return 'Mr. ' + super.fullName()
  }
}

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

Декораторы    ↑

Декораторы — это еще одна функция, которую JavaScript скопировал из Python. Технически они по-прежнему являются предложениями, которые могут быть изменены, но вы можете протестировать их с помощью онлайн-площадки или местного транспилятора. Однако имейте в виду, что они требуют небольшой настройки. В зависимости от выбранного плагина и его опций, вы получите другой синтаксис и поведение.

Некоторые фреймворки уже используют собственный синтаксис для декораторов, который необходимо преобразовать в простой JavaScript. Если вы выберете предложение TC-39, то сможете украсить только классы и их участников. Похоже, в JavaScript не будет специального синтаксиса для декораторов функций.

Особенности JavaScript    ↑

Брендану Эйху понадобилось десять дней, чтобы создать прототип того, что позже стало JavaScript. После того, как он был представлен заинтересованным сторонам на деловой встрече, язык был признан готовым к эксплуатации и не претерпевал много изменений в течение многих лет.

К сожалению, это сделало язык печально известным своими странностями. Некоторые люди даже не считали JavaScript «настоящим» языком программирования, что сделало его жертвой множества шуток и мемов.

Сегодня язык стал намного более дружелюбным, чем раньше. Тем не менее, стоит знать, чего следует избегать, поскольку многие устаревшие версии JavaScript все еще ждут, чтобы вас укусить.

Фиктивный массив    ↑

Списки и кортежи Python реализованы как массив (тип данных) в традиционном смысле, тогда как тип Array в JavaScript имеет больше общего со словарем Python. Что же тогда такое массив?

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

В Python список представляет собой массив указателей, которые обычно представляют собой целые числа, которые ссылаются на разнородные объекты, разбросанные по различным областям памяти.

Примечание. Для низкоуровневых массивов в 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']

В Python это не сработает.

Сортировка массивов    ↑

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

>>> sorted([53, 2020, 42, 1918, 7])
[7, 42, 53, 1918, 2020]

Однако, если вы хотите отсортировать список строк, он волшебным образом узнает, как сравнивать элементы, чтобы они приобретут лексикографический порядок:

>>> sorted(['lorem', 'ipsum', 'dolor', 'sit', 'amet'])
['amet', 'dolor', 'ipsum', 'lorem', 'sit']

Все становится сложнее, когда вы начинаете смешивать различные типы:

>>> sorted([42, 'not a number'])
Traceback (most recent call last):
  File "", line 1, in 
TypeError: '<' not supported between instances of 'str' and 'int'

Теперь, вы знаете, что Python является строго типизированным языком, а не как смешение типов. В JavaScript, с другой стороны, все наоборот. Он с энтузиазмом преобразует элементы несовместимых типов по каким-то непонятным правилам.

Вы можете использовать .sort(), чтобы делать сортировку в JavaScript:

> ['lorem', 'ipsum', 'dolor', 'sit', 'amet'].sort();
['amet', 'dolor', 'ipsum', 'lorem', 'sit']

Оказывается, сортировка строк работает должным образом. Посмотрим, как он справляется с числами:

> [53, 2020, 42, 1918, 7].sort();
[1918, 2020, 42, 53, 7]

Здесь произошло то, что элементы массива были неявно преобразованы в строки и отсортированы лексикографически. Чтобы предотвратить это, необходимо предоставить свою собственную стратегию сортировки как функцию двух элементов для сравнения, например:

> [53, 2020, 42, 1918, 7].sort((a, b) => a — b);
[7, 42, 53, 1918, 2020]

Контракт между вашей стратегией и методом сортировки заключается в том, что ваша функция должна возвращать одно из трех значений:

  • Ноль, когда два элемента равны
  • Положительное число, когда элементы нужно поменять местами
  • Отрицательное число, когда элементы расположены в правильном порядке

Это общий шаблон, присутствующий в других языках и это старый способ сортировки в Python.

Автоматическая вставка точки с запятой    ↑

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

При некоторых обстоятельствах это может привести к неожиданным результатам:

function makePerson(name) {
  return
    ({
      fullName: name,
      createdAt: new Date()
    })
}

В этом примере вы можете ожидать, что движок JavaScript вставит отсутствующую точку с запятой в самом конце вашей функции, сразу после закрывающей скобки литерала объекта. Однако при вызове функции происходит следующее:

> const jdoe = makePerson('John Doe');
> console.log(jdoe);
undefined

Ваша функция изменила предполагаемое действие, вернув неопределенное значение, поскольку вместо одной были вставлены две точки с запятой:

function makePerson(name) {
  return;
    ({
      fullName: name,
      createdAt: new Date()
    });
}

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

Чтобы исправить этот пример, вам нужно просто изменить форматирование кода так, чтобы возвращаемое значение начиналось в той же строке, что и оператор return:

function makePerson(name) {
  return {
    fullName: name,
    createdAt: new Date()
  };
}

В некоторых случаях вы не можете полагаться на автоматическую вставку точки с запятой, и вам нужно указать её явно. Например, вы не можете опускать точку с запятой, когда начинаете новую строку с круглой скобки:

const total = 2 + 3
(4 + 5).toString()

Это приводит к ошибке времени выполнения из-за отсутствия точки с запятой, что приводит к сворачиванию двух строк в одну:

const total = 2 + 3(4 + 5).toString();

Числовой литерал нельзя вызывать как функцию.

Странные циклы    ↑

Циклы в JavaScript особенно сбивают с толку, потому что их очень много и они похожи друг на друга, тогда как в Python их всего два. Основным типом цикла в JavaScript является цикл for, перенесенный из Java:

const fruits = ['apple', 'banana', 'orange'];
for (let i = 0; i < fruits.length; i++) {
  console.log(fruits[i]);
}

Он состоит из трех частей, все из которых не являются обязательными:

  1. Инициализация: let i = 0
  2. Условие завершения: i < fruits.length
  3. Правило изменения параметра цикла: i++

Первая часть выполняется только один раз перед запуском цикла и обычно устанавливает начальное значение для счетчика. Затем после каждой итерации выполняется правило изменения параметра для обновления счетчика. Сразу после этого оценивается условие завершения, чтобы определить, следует ли продолжать цикл. Это примерно эквивалентно итерации по списку индексов в Python:

fruits = ['apple', 'banana', 'orange']
for i in range(len(fruits)):
    print(fruits[i])

Обратите внимание, сколько работы Python делает за вас. С другой стороны, открытая внутренняя часть цикла дает вам большую гибкость. Этот тип цикла обычно детерминирован, потому что вы знаете, сколько раз он будет повторяться с самого начала.

В JavaScript вы можете сделать обычный цикл for не-детерминированным и даже бесконечный, опуская одну или несколько его частей:

for (;;) {
  // Бесконечный цикл
}

Однако, более идиоматический способ сделать такую ​​итерацию будет включать цикл while, который очень похож на тот, который вы найдете в Python:

while (true) {
  const age = prompt('How old are you?');
  if (age >= 18) {
    break;
  }
}

В дополнение к этому в JavaScript есть цикл do ... while, который гарантированно запускается хотя бы один раз, поскольку он проверяет условие после своего тела. Вы можете переписать этот пример следующим образом:

let age;
do {
  age = prompt('How old are you?');
} while (age < 18);

Помимо остановки итерации на полпути с помощью ключевого слова break, вы можете перейти к следующей итерации, используя ключевое слово continue, как в Python:

for (let i = 0; i < 10; i++) {
  if (i % 2 === 0) {
    continue;
  }
  console.log(i);
}

Однако вы не можете использовать предложение else в циклах.

У вас может возникнуть соблазн попробовать цикл for ... in в JavaScript, думая, что он будет перебирать значения, как цикл for Python. Хотя он выглядит похожим и имеет похожее имя, на самом деле он ведет себя совсем по-другому!

А для for ... in в JavaScript выполняет итерацию по атрибутам данного объекта, включая те, которые находятся в цепочке прототипов:

> const object = {name: 'John Doe', age: 42};
> for (const attribute in object) {
…   console.log(`${attribute} = ${object[attribute]}`);
… }
name = John Doe
age = 42

Если вы хотите исключить атрибуты, прикрепленные к прототипу, вы можете вызвать hasOwnProperty(). Она проверит, принадлежит ли данный атрибут экземпляру объекта.

Когда вы вводите в цикл for ... in массив, он будет перебирать числовые индексы массива. Как вы уже знаете, массивы в JavaScript — это просто прославленные словари:

> 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));

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

Примечание. Чтобы проверить, определен ли один атрибут в объекте, используйте оператор in:

> 'toString' in [1, 2, 3];
true
> '__str__' in [1, 2, 3];
false

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

Цикл for ... of наиболее близок к циклу for в Python. С этим,вы можете перебирать любой повторяемый объект, включая строки и массивы:

const fruits = ['apple', 'banana', 'orange'];
for (const fruit of fruits) {
  console.log(fruit);
}

Это, вероятно, наиболее интуитивно понятный способ для программиста Python выполнять итерацию в JavaScript.

Конструктор без new    ↑

Вернемся к определенному ранее типу Person:

function Person(name) {
  this.name = name;
  this.sayHi = function() {
    console.log(`Hi, my name is ${this.name}.`);
  }
}

Если вы забудете правильно вызвать этот конструктор, поставив перед ним ключевое слово new, он автоматически завершится ошибкой и оставит вас с неопределенной переменной:

> let bob = Person('Bob');
> console.log(bob);
undefined

Одной уловкой можно защитить себя от этой ошибки. Если вы опустите ключевое слово new, не будет никакого объекта для привязки, поэтому переменная this внутри конструктора будет указывать на глобальный объект, такой как объект окна в веб‑браузере. Вы можете обнаружить это и делегировать действительный вызов конструктора:

> 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: ƒ}

Это единственная причина, по которой вы можете захотеть включить оператор return в свой конструктор.

Примечание. Тройной знак равенства (===) является намеренным и связан со слабой типизацией в JavaScript. Подробнее об этом вы узнаете ниже.

Глобальная по умолчанию область действия    ↑

Если вы уже не находитесь в глобальной области видимости, то переменные автоматически становятся глобальными, если перед их объявлением не указано одно из следующих ключевых слов:

  • var
  • let
  • const

В эту ловушку легко попасть, особенно если вы Pythonist. Например, такая переменная, определенная в функции, станет видимой вне ее:

 function call() {
…   global = 42;
…   let local = 3.14
… }
> call();
> console.log(global);
42
> console.log(local);
ReferenceError: local is not defined

Интересно, что правила, определяющие, объявляете ли вы локальную или глобальную переменную в Python, намного сложнее, чем здесь. В Python есть и другие виды области видимости переменных.

Область видимости функции    ↑

Эта причуда присутствует только в устаревшем коде, который использует ключевое слово 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

Однако это работает наоборот. Внутренние функции могут видеть переменные из внешней области видимости, но это становится еще более интересным, когда вы возвращаете внутреннюю функцию для дальнейшего использования. Таким образом создаётся замыкание.

Подъемник    ↑

Это связано с предыдущей причудой и, опять же, связано с переменными, объявленными с печально известным ключевым словом var.

Начнем с маленькой загадки:

var x = 42;

function call() {
  console.log(x); // A = ???
  var x = 24;
  console.log(x); // B = ???
}

call();
console.log(x); // C = ???

Рассмотрим варианты возможных результатов:

  1. A = 42, B = 24, C = 42
  2. A = 42, B = 24, C = 24
  3. A = 24, B = 24, C = 42
  4. A = 24, B = 24, C = 24
  5. SyntaxError: Identifier 'x' has already been declared

Пока вы обдумываете ответ, давайте подробнее рассмотрим, что такое подъем. Коротко, это неявный механизм в JavaScript, который перемещает объявления переменных в начало функции, но только те, которые используют ключевое слово var. Имейте в виду, что он перемещает объявления, а не определения.

В некоторых языках программирования, таких как C, все переменные должны быть объявлены в начале функции. Другие языки, такие как Паскаль, пошли дальше, выделив специальный раздел для объявлений переменных. JavaScript попытался сделать что-то подобное.

Хорошо, готовы? Правильный ответ — ни один из вышеперечисленных! Он напечатает следующее:

  • A = undefined
  • B = 24
  • C = 42

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

var x = 42;

function call() {
  var x;
  console.log(x); // A = undefined
  x = 24;
  console.log(x); // B = 24
}

call();
console.log(x); // C = 42

Глобальная переменная временно маскируется локальной переменной, потому что поиск имени идет наружу. Объявление x внутри функции перемещается вверх. Когда переменная объявлена, но не инициализирована, она имеет неопределенное значение.

Переменные — не единственная конструкция, на которую влияет подъем. Обычно, когда вы определяете функцию в JavaScript, вы можете вызвать её даже до её определения:

call(); // Prints "hello"

function call() {
  console.log('hello');
}

Это не будет работать в интерактивной оболочке, где отдельные фрагменты кода интерпритируюся немедленно.

Теперь, когда вы объявляете переменную с помощью ключевого слова var и назначаете этой переменной выражение функции, она будет поднята:

call(); // TypeError: call is not a function

var call = function() {
  console.log('hello');
};

В результате ваша переменная останется неопределенной, пока вы ее не инициализируете.

Иллюзорные сигнатуры функций    ↑

Сигнатуры функций в JavaScript не существуют. Какие бы формальные параметры вы не объявляли, они не влияют на вызов функции.

В частности, вы можете передать любое количество аргументов функции, которая ничего не ожидает, и они будут просто проигнорированы:

> 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 перед параметром rest в ES6.

Неявное приведение типов    ↑

JavaScript — это язык программирования со слабой типизацией, что проявляется в его способности неявно приводить несовместимые типы.

Это может дать ложные срабатывания при сравнении двух значений:

if ('2' == 2) { // Возвращает true

В общем случае для безопасности надо использовать оператор строгого сравнения (===):

> '2' === 2;
false
> '2' !== 2;
true

Этот оператор сравнивает значения и типы их операндов.

Без целочисленного типа    ↑

В Python есть несколько типов данных для представления чисел:

  • int
  • float
  • complex

Предыдущее поколение Python также имело тип long, который в конечном итоге был объединен в int.

Другие языки программирования еще более обширны, предоставляя вам детальный контроль над потреблением памяти, диапазоном значений, точностью с плавающей запятой и обработкой знака.
В JavaScript есть только один числовой тип: Number, который соответствует типу данных Python с плавающей запятой. Под капотом это 64-битное число с двойной точностью, соответствующее спецификации IEEE 754. Это было просто и достаточно для ранней веб‑разработки, но сегодня это может вызвать некоторые проблемы.

Примечание. Чтобы получить целую часть числа с плавающей запятой в JavaScript, вы можете использовать встроенный parseInt().

Во-первых, в большинстве ситуаций это чрезвычайно расточительно. Если бы вы представляли пиксели одного видеокадра FHD с помощью JavaScript Number, вам бы пришлось выделить около 50 МБ памяти. В языке программирования с поддержкой потока байтов, таком как Python, вам потребуется небольшая часть этого объема памяти.

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

> 0.1 + 0.2;
0.30000000000000004

Представлять очень большие и очень маленькие числа небезопасно:

> const x = Number.MAX_SAFE_INTEGER + 1;
> const y = Number.MAX_SAFE_INTEGER + 2;
> x === y;
true

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

Когда Node.js стал популярным, люди начали использовать его для написания серверных приложений. Им нужен был способ доступа к локальной файловой системе. Некоторые операционные системы идентифицируют файлы по произвольным целым числам. Иногда эти числа не могли иметь точного представления в JavaScript, поэтому вы не могли открыть файл или читали какой-то случайный файл, не зная об этом.

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

> const x = BigInt(Number.MAX_SAFE_INTEGER) + 1n;
> const y = BigInt(Number.MAX_SAFE_INTEGER) + 2n;
> x === y;
false

Поскольку вы не можете смешивать новый тип данных BigInt с обычными числами, вы должны либо заключить их в оболочку, либо использовать специальный литерал:

> typeof 42;
'number'
> typeof 42n;
'bigint'
> typeof BigInt(42);
'bigint'

Кроме того, число BigInt будет совместимо с двумя несколько родственными типизированными массивами для целых чисел со знаком и без знака:

  1. BigInt64Array
  2. BigUint64Array

В то время как обычный BigInt может хранить сколь угодно большие числа, элементы этих двух массивов ограничены 64 битами.

null или undefined    ↑

Языки программирования предоставляют способы представить отсутствие значения. Например, в Python есть None, в Java — null, а в Pascal — nil. В JavaScript вы получаете не только null, но и undefined.

Может показаться странным иметь более одного способа представления отсутствующих значений, когда одного уже слишком много:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. (…) This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

— Tony Hoare

Я называю это своей ошибкой на миллиард долларов. Это было изобретение null‑ссылки в 1965 году. (…) Это привело к бесчисленным ошибкам, уязвимостям и системным сбоям, которые, вероятно, причинили боль и ущерб на миллиард долларов за последние сорок лет.

- Тони Хоар

Разница между null и undefined довольно тонкая. Объявленные, но неинициализированные переменные неявно получат значение undefined. С другой стороны, нулевое значение никогда не присваивается автоматически:

let x; // undefined
let y = null;

В любой момент вы можете вручную присвоить неопределенное значение переменной:

let z = undefined;

Различие между null и undefined часто использовалось для реализации аргументов функции по умолчанию до ES6. Одна из возможных реализаций была такой:

function fn(required, optional) {
  if (typeof optional === 'undefined') {
    optional = 'default';
  }
  // ...
}

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

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

let x; // undefined
let y = null;

В любой момент вы можете вручную присвоить неопределенное значение переменной:

let z = undefined;

Различие между null и undefined часто использовалось для реализации аргументов функции по умолчанию до ES6. Одна из возможных реализаций была такой:

fn(42);            // optional = "default"
fn(42, undefined); // optional = "default"
fn(42, null);      // optional = null

Помимо необходимости иметь дело с null и undefined, иногда может возникать исключение ReferenceError:

> foobar;
ReferenceError: foobar is not defined

Это означает, что вы пытаетесь сослаться на переменную, которая не была объявлена ​​в текущей области, undefined означает объявленную, но неинициализированную, тогда как null означает объявленную и инициализированную переменную, но с пустым значением.

Область this    ↑

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

В JavaScript, как и в Java, вы можете воспользоваться специальным ключевым словом this, что соответствует текущему экземпляру. Но что означает текущий экземпляр? Это зависит от того, как вы вызываете свою функцию.

Напомним синтаксис объектных литералов:

> let jdoe = {
…   name: 'John Doe',
…   whoami: function() {
…     console.log(this);
…   }
… };
> jdoe.whoami();
{name: "John Doe", whoami: ƒ}

Использование параметра this в функции позволяет вам ссылаться на конкретный объект, которому принадлежит эта функция, без жесткого кодирования имени переменной. Не имеет значения, определена ли функция как анонимное выражение или это обычная функция, подобная этой:

> function whoami() {
…   console.log(this);
… }
> let jdoe = {name: 'John Doe', whoami};
> jdoe.whoami();
{name: "John Doe", whoami: ƒ}

Важен объект, для которого вы вызываете функцию:

> jdoe.whoami();
{name: "John Doe", whoami: ƒ}
> whoami();
Window {…}

В первой строке вы вызываете whoami() через атрибут объекта jdoe. В этом случае значение этой переменной такое же, как у переменной jdoe. Однако, когда вы вызываете ту же самую функцию напрямую, она становится глобальным объектом.

Примечание. Это правило не применяется к функции-конструктору, вызываемой с новым ключевым словом. В таком случае ссылка this функции будет указывать на вновь созданный объект.

Вы можете думать о функциях JavaScript как о методах, прикрепленных к глобальному объекту. В веб‑браузере окно является глобальным объектом, поэтому на самом деле приведенный выше фрагмент кода является сокращением для этого кода:

> jdoe.whoami();
{name: "John Doe", whoami: ƒ}
> window.whoami();
Window {…}

Вы видите здесь закономерность?

По умолчанию значение this внутри функции зависит от объекта, стоящего перед оператором точки. Пока вы контролируете, как вызывается ваша функция, все будет в порядке. Это становится проблемой только тогда, когда вы не вызываете функцию самостоятельно, что является обычным случаем для обратных вызовов.

Давайте определим еще один литерал объекта, чтобы продемонстрировать это:

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {
    this.items.forEach(function(item) {
      console.log(`${item} is a ${this.type}`);
    });
  }
};

collection — это набор элементов, имеющих общий тип. Здесь .show() не работает должным образом, потому что не раскрывает тип элемента:

> collection.show();
apple is a undefined
banana is a undefined
orange is a undefined

Хотя this.items правильно относится к массиву фруктов, функция обратного вызова, похоже, получила другую ссылку this. Это потому, что обратный вызов не привязан к литералу объекта. Это как если бы это было определено в другом месте:

function callback(item) {
  console.log(`${item} is a ${this.type}`);
}

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {
    this.items.forEach(callback);
  }
};

Самый простой способ обойти это — заменить this в обратном вызове пользовательской переменной или константой:

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {
    const that = this;
    this.items.forEach(function(item) {
      console.log(`${item} is a ${that.type}`);
    });
  }
};

Вы сохраняете значение this в локальной константе, на которую затем ссылается обратный вызов. Поскольку обратный вызов определяется как внутренняя функция, он может обращаться к переменным и константам из внешней области. Это было бы невозможно с автономной функцией.

Теперь результат правильный:

> collection.show();
apple is a fruit
banana is a fruit
orange is a fruit

Этот шаблон настолько распространен, что .forEach() принимает необязательный параметр для замены его в обратном вызове:

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {
    this.items.forEach(function(item) {
      console.log(`${item} is a ${this.type}`);
    }, this);
  }
};
collection.show();

Это более элегантно, чем пользовательский прием, который вы видели ранее, и он также более универсален, поскольку позволяет передавать обычную функцию. Хотя не все встроенные методы в JavaScript настолько хороши, есть еще три способа поработать с этой ссылкой:

  1. .apply()
  2. .bind()
  3. .call()

Это методы, доступные для объектов функций. С участием .apply() и .call(), вы можете вызывать функцию, добавляя к ней произвольный контекст. Оба они работают одинаково, но передают аргументы с использованием разного синтаксиса:

> function whoami(x, y) {
…   console.log(this, x, y);
… }
> let jdoe = {name: 'John Doe'};
> whoami.apply(jdoe, [1, 2]);
{name: "John Doe"} 1 2
> whoami.call(jdoe, 1, 2);
{name: "John Doe"} 1 2

Из трех .bind() является наиболее мощным, потому что он позволяет вам постоянно изменять значение this для будущих вызовов. Он работает немного иначе, поскольку возвращает новую функцию, привязанную к данному контексту:

> const newFunction = whoami.bind(jdoe);
> newFunction(1, 2);
{name: "John Doe"} 1 2
> newFunction(3, 4);
{name: "John Doe"} 3 4

Это может быть полезно при решении более ранней проблемы несвязанного обратного вызова:

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {
    this.items.forEach(callback.bind(this));
  }
};

Ничего из того, что вы только что прочитали об этом в JavaScript, не применимо к функциям стрелок в ES6. Это, кстати, хорошо, потому что устраняет много двусмысленности. В стрелочных функциях нет привязки контекста и нет неявной ссылки this. Вместо этого это рассматривается как обычная переменная и подчиняется правилам лексической области видимости.
Давайте перепишем один из предыдущих примеров, используя стрелочные функции:

> const collection = {
…   items: ['apple', 'banana', 'orange'],
…   type: 'fruit',
…   show: () => {
…     this.items.forEach((item) => {
…       console.log(`${item} is a ${this.type}`);
…     });
…   }
… };
> collection.show();
TypeError: Cannot read property 'forEach' of undefined

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

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {
    this.items.forEach((item) => {
      console.log(`${item} is a ${this.type}`);
    });
  }
};
collection.show();

Как видите, стрелочные функции не являются полной заменой традиционных функций.

Этот раздел был лишь верхушкой айсберга. Чтобы узнать больше о необычном поведении JavaScript, взгляните на сложные примеры кода, которые также доступны в виде устанавливаемого модуля Node.js.

Что дальше?    ↑

Как Pythonist, вы знаете, что овладение языком программирования и его экосистемой — это только начало вашего пути к успеху. Попутно придется усвоить более абстрактные концепции.

объектная модель документа (DOM)    ↑

Если вы планируете какую-либо разработку на стороне клиента, вам не избежать знакомства с DOM.

Примечание. Возможно, раньше вы использовали тот же интерфейс DOM для обработки XML-документов в Python.

Чтобы позволить манипулировать HTML-документами в JavaScript, веб‑браузеры предоставляют стандартный интерфейс, называемый DOM, который включает в себя различные объекты и методы. Когда страница загружается, ваш сценарий может получить доступ к внутреннему представлению документа через предопределенный экземпляр документа:

const body = document.body;

Это глобальная переменная, доступная вам в любом месте вашего кода.

Каждый документ — это дерево элементов. Чтобы пройти по этой иерархии, вы можете начать с корня и использовать следующие атрибуты для перемещения в разных направлениях:

  • Up: .parentElement
  • Left: .previousElementSibling
  • Right: .nextElementSibling
  • Down: .children, .firstElementChild, .lastElementChild

Эти атрибуты удобно доступны для всех элементов в дереве DOM, что идеально подходит для рекурсивного обхода:

const html = document.firstElementChild;
const body = html.lastElementChild;
const element = body.children[2].nextElementSibling;

Большинство атрибутов будут нулевыми, если они не ведут к элементу в дереве. Единственным исключением является свойство .children, которое всегда возвращает объект в виде массива, который может быть пустым.

Часто вы не знаете, где находится элемент. У объекта документа, как и у любого другого элемента в дереве, есть несколько методов поиска элемента.
Вы можете искать элементы по имени тега, атрибуту ID, имени класса CSS или даже с помощью сложного селектора CSS.

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

  1. .querySelector (селектор)
  2. .querySelectorAll (селектор)

Первый возвращает первое вхождение совпадающего элемента или null, а второй метод всегда возвращает объект, подобный массиву, со всеми совпадающими элементами. Вызов этих методов для объекта документа приведет к поиску всего документа.

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

const div = document.querySelector('div'); // 1-й 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. В пиковое время у него было больше хитов, чем у всех других основных фреймворков и библиотек, вместе взятых. Несмотря на то, что он немного устаревший и немодный, он все еще используется во многих старых проектах, а иногда и в новых.

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

jQuery имеет хорошую документацию и минимальный API, так как нужно запомнить только одну функцию — универсальную $(). Вы можете использовать эту функцию для создания и поиска элементов, изменения их стиля, обработки событий и многого другого.

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

Так почему же большинство людей вообще склонны использовать интерфейсную структуру?

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

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

Что касается того, какой популярный фреймворк выбрать, то это зависит от вашей цели. Чтобы оставаться актуальным, следует сначала потратить время на изучение чистого JavaScript, поскольку интерфейсные фреймворки, как известно, появляются и исчезают быстро.

Если вы хотите получить работу фронтенд-разработчика или даже инженера полного цикла, вам следует ознакомиться с описанием должностей. Скорее всего, они будут ожидать кого-то с опытом работы в одной из этих систем:

На момент написания этой статьи именно они были, пожалуй, самые популярные JavaScript-фреймворки.

Заключение    ↑

В этом руководстве вы узнали об истоках JavaScript, его альтернативах и о том, куда движется язык. Вы сравнили Python и JavaScript, внимательно изучив их сходства и различия в синтаксисе, средах выполнения, связанных инструментах и ​​жаргоне. Наконец, вы узнали, как избежать фатальных ошибок в JavaScript.
Теперь вы можете:

  • Сравнить Python с JavaScript
  • Выбрать подходящий язык для работы
  • Написать сценарий оболочки на JavaScript
  • Создать динамический контент на веб‑странице
  • Воспользоваться преимуществами экосистемы JavaScript
  • Избежать распространенных ошибок в JavaScript

Попробуйте использовать Node.js для своего следующего проекта сценариев или создать интерактивное клиентское приложение в веб-браузере. Когда вы объединяете то, что только что узнали, с уже имеющимися знаниями Python, вашим возможностям не будет предела.

На основе Python vs JavaScript for Pythonistasranslation of "You Don't Know JS" book series

Print Friendly, PDF & Email

Опубликовано Вадим В. Костерин

ст. преп. кафедры ЦЭиИТ. Автор более 130 научных и учебно-методических работ. Лауреат ВДНХ (серебряная медаль).

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *