Как работает JavaScript
Ранее JavaScript предназначался для использования в веб-браузерах, однако ситуация изменилась с развитием Node. Мы знаем, как, где и когда его использовать. Но известно ли, что происходит за этими сценариями?
Даже если вы знаете это, то все равно сможете извлечь полезную информацию из данной статьи.
JavaScript — это высокоуровневый ЯП, а компьютер понимает только единицы и нули. Каким образом компьютер понимает написанный код? В этой статье мы рассмотрим ответ на один единственный вопрос: как работает JavaScript?
Движок JavaScript
Это главный герой, который отвечает за понимание компьютером JS-кода. Движок JavaScript принимает код и преобразует его в машиночитаемый язык. Он выполняет работу переводчика, преобразующего JS-код на понятный компьютеру язык. Как и JS, каждый ЯП также обладает движком, делающий написанный код понятным для компьютера.
У JavaScript есть множество различных движков, доступных для использования. На этой странице Википедии можно найти их список. Они также называются движками ECMAScript (подробнее об этом ниже).
Попробуем заглянуть внутрь движка, чтобы узнать, как преобразуются файлы JavaScript.
Что скрывает движок JavaScript
Движок — это язык, который разбивает код и переводит его. А V8 — это один из самых популярных движков JavaScript, который используется в Chrome и NodeJS и написан на низкоуровневом языке C++. Написать движок может кто угодно, что может привести к путанице.
Для поддержания контроля за этими механизмами был создан стандарт ECMA, который предоставляет характеристики написания движка и всех функций JavaScript. По этой причине в ECMAScript 6, 7, 8 и т. д. реализованы новые функции JavaScript, а движки обновлены для поддержки этих новых функций. Следовательно, необходимо проверять доступность расширенной функции JS в браузерах во время разработки.
Для примера возьмем движок V8, однако основные концепции остаются неизменными во всех движках.
Теперь рассмотрим с более технической точки зрения.
Движок JavaScript V8
Так выглядит движок JS изнутри. Вводимый код проходит через следующие стадии:
- Парсер
- AST
- Интерпретатор выдает байт-код
- Профайлер
- Компилятор выдает оптимизированный код
Не волнуйтесь, подробности рассмотрим в течение нескольких минут.
Однако для начала разберем важное различие.
Интерпретатор и Компилятор
Есть два способа преобразования кода в машиночитаемый язык. Концепция, о которой мы поговорим, применима не только к JavaScript, но и к большинству ЯП, таких как Python, Java и т. д.
- Интерпретатор читает код построчно и сразу выполняет его.
- Компилятор читает весь код, выполняет оптимизации, а затем производит оптимизированный код.
Рассмотрим на примере.
В приведенном выше примере функция add , которая добавляет два числа и возвращает сумму, вызывается 1000 раз.
- При передаче этого файла интерпретатору, он читает его построчно и сразу выполняет функцию, пока цикл не закончится. Таким образом, он просто переводит код в то, что компьютер понимает на ходу.
- При передаче этого файла компилятору, он читает всю программу, выполняет анализ действий и производит оптимизированный код на языке, который понимает машина. Это как взять X (JS-файл) и создать Y (оптимизированный код, понятный машине). Если использовать интерпретатор для Y (оптимизированный код), результат будет таким же, как при интерпретации X (JS-код).
На изображении выше показано, что байт-код — это просто промежуточный код, который необходимо интерпретировать для обработки компьютером. Как интерпретатор, так и компилятор, преобразуют исходный код в машинный код, единственное отличие состоит в том, как выполняется это преобразование.
- Интерпретатор построчно преобразует исходный код в эквивалентный машинный код.
- Компилятор сразу преобразует весь исходный код в машинный код.
Плюсы и минусы интерпретатора и компилятора
- Преимущество интерпретатора заключается в немедленном выполнении кода без необходимости компиляции, что может быть полезно для запуска JS-файлов в браузере. Однако процесс замедляется при необходимости преобразования большего количества JS-кода. Вспомните маленький фрагмент кода, в котором функция вызывалась 1000 раз. В этом случае вывод остается прежним, даже если функция add была вызвана 1000 раз. Такие ситуации замедляют работу интерпретатора.
- В таких случаях компилятор может выполнить некоторые оптимизации, заменив цикл одним числом 2 (1 + 1 добавлялось каждый раз), поскольку он остается неизменным для всех 1000 итераций. Окончательный код, который выдает компилятор, оптимизирован и выполняется намного быстрее.
Таким образом, интерпретатор сразу начинает выполнение кода, но не выполняет оптимизацию. Компилятору требуется время для компиляции кода, однако он выдает более оптимизированный код.
Теперь вернемся к основной схеме движка JS.
Итак, зная плюсы и минусы компилятора и интерпретатора, можно использовать преимущества каждого. Здесь и появляется компилятор JIT (Just In Time). Он представляет собой комбинацию интерпретатора и компилятора, и большинство браузеров теперь реализуют эту функцию для повышения эффективности. Движок V8 также использует эту функцию.
Движок V8 JavaScript
В этом процессе:
- Парсер идентифицирует, анализирует и классифицирует различные части программы. Например, устанавливает, является ли элемент функцией, переменной и т.д. с помощью ключевых слов JavaScript.
- Затем AST (абстрактные синтаксические деревья) создают древовидную структуру на основе классификации парсера. В AST Explorer можно узнать о том, как строится дерево.
- Затем дерево передается интерпретатору, который создает байт-код. Как мы узнали ранее, байт-код не является кодом самого низкого уровня, однако его можно интерпретировать. На этой стадии браузер работает с доступным байт-кодом с помощью движка V8, чтобы пользователю не приходилось ждать.
- В то же время профайлер ищет оптимизации кода, а затем передает входные данные компилятору. Компилятор выдает оптимизированный код, в то время как байт-код временно используется для рендеринга в браузере. Как только компилятор создает оптимизированный код, временный байт-код полностью заменяется новым оптимизированным кодом.
- Таким образом используются лучшие качества интерпретатора и компилятора. Интерпретатор выполняет код, в то время как профайлер ищет оптимизацию, а компилятор создает оптимизированный код. Затем байт-код заменяется оптимизированным кодом, который является кодом более низкого уровня, таким как машинный код.
Это означает, что производительность будет постепенно улучшаться, не блокируя время выполнения.
Примечание по байт-коду
Как и в случае с машинным кодом, не все компьютеры понимают байт-код. Чтобы интерпретировать его на машиночитаемый язык, необходимо промежуточное ПО, такое как виртуальная машина, или движок (например, Javascript V8). По этой причине браузеры могут выполнять этот байт-код из интерпретатора во время вышеупомянутых 5-ти стадий с помощью движков JavaScript.
В результате возникает следующий вопрос:
Является ли JavaScript интерпретируемым языком?
Да, но не совсем. На ранних этапах JavaScript Брендан Айк создал движок JavaScript ‘SpiderMonkey’. У движка был интерпретатор, который говорил браузеру, что нужно делать. Сейчас есть не только интерпретаторы, но и компиляторы, а код не только интерпретируется, но и компилируется для оптимизации. Технически все зависит от реализации.
Как работает JavaScript: часть первая
JavaScript — самый популярный язык программирования в репозиториях Гитхаба. Любой фронтенд-разработчик имеет с ним дело, а Node.js активно используется в бэкенд-разработке. Но понимаете ли вы, как на самом деле устроен JavaScript?
Это перевод сразу двух статей о внутреннем устройстве языка. Первая часть — общий обзор движка, среды выполнения и стека вызовов. Вторая часть — о том, как устроен V8 и как он оптимизирует код. В конце — советы по оптимизации кода для разработчиков.
Если вы в целом понимаете, как работает стек вызовов и петля событий в JavaScript, советую пропустить первую часть и перейти сразу к V8. Первая часть будет интереснее тем, кто только начинает изучать язык.
Как работает JavaScript: движок, рантайм, стек вызовов
По мере роста популярности JavaScript команды разработчиков используют его на многих уровнях своих приложений: на веб-страницах, в бэкенде, гибридных приложениях, на мобильных устройствах. Как можно увидеть на Github Stats, JavaScript сейчас на первом месте рейтинга по количеству активных репозиториев и общему количеству пушей, и не сильно отстает в других категориях.
Прим. переводчика: вот актуальная статистика по языкам: madnight.github.io/githut
Если проекты становятся настолько зависимыми от JavaScript, это значит, что разработчики вынуждены лучше знакомиться с языком и углубляться в тонкости его реализации, чтобы продолжать делать хорошие приложения.
Как выясняется, многие разработчики используют JavaScript, но в целом не очень понимают, что на самом деле происходит под капотом.
Почти все слышали про движок V8, и многие знают, что JavaScript — однопоточный язык, или что он использует очередь обратных вызовов. В этом посте мы детально углубимся в эти концепции, и выясним, как JavaScript на самом деле работает. Понимая эти детали, вы сможете писать хорошие неблокирующиеся приложения, которые правильно используют имеющиеся API.
Если вы относительно недавно знакомы с JavaScript, этот пост поможет вам понять, почему JavaScript такой «странный» относительно других языков. А если вы уже опытный JavaScript–разработчик, я надеюсь, что статья покажет что-то новое о том, как работает язык, с которым вы работаете каждый день.
Движок JavaScript
Популярный пример движка JavaScript — это V8 от Google. Он используется, к примеру, внутри Chrome и Node.js. Вот сильно упрощенная модель того, как он выглядит изнутри:
Движок состоит из двух основных частей:
- Куча — место, где происходит выделение памяти
- Стек вызовов — место, где выполняется код, организованный в кадры вызовов
Среда выполнения
В браузере есть куча API, которыми пользуется почти каждый разработчик, например setTimeout. Эти API, однако, не предоставляются движком. Так откуда они берутся? Оказывается, что реальность несколько сложнее.
Итак, у нас есть движок, но кроме этого еще куча всего. У нас есть эти штуки, которые мы называем Web API, предоставляемые браузером, всякие DOM, AJAX, setTimeout и еще много всего. И еще у нас есть петля событий и очередь обратных вызовов.
Стек вызовов
JavaScript — однопоточный язык, и это значит — один стек вызовов. Иными словами, одна операция в один момент времени.
Стек вызовов — структура данных, которая попросту хранит информацию о том, какой участок кода выполняется сейчас. Если мы входим в функцию, мы помещаем ее в стек. Когда мы возвращаемся из функции, мы удаляем ее из стека. Это все, что делает стек.
Давайте разберемся на примере. Посмотрите на код:
В начале выполнения стек пустой. После этого идут такие шаги:
Каждый элемент в стеке называется кадром вызова. Стектрейс, который выводится при выбрасывании исключения — по сути состояние стека вызовов на момент исключения. Посмотрите код:
В Chrome вы увидите такую картинку:
«Раздувание стека» — это то, что случается, когда стек увеличивается до его максимума. И это может произойти очень просто, особенно при использовании рекурсии без должного тестирования. Вот пример:
Когда движок исполняет этот код, он сперва вызывает функцию «foo». Эта функция рекурсивна, и постоянно вызывает саму себя без условия остановки. Так что на каждом шаге одна и та же функция добавляется в стек вызовов. Вот как это выглядит:
В какой-то момент количество вызовов в стеке превышает размер стека, и браузер решает вмешаться, выбросив исключение, которое выглядит примерно так:
Выполнение кода в одном потоке может быть достаточно простым, поскольку вам не нужно беспокоиться о сложных вещах, которые возникают в мультипоточном окружении, например о дедлоках. Но выполнение кода в одном потоке также достаточно ограниченно. Поскольку в JavaScript один стек, что произойдет, если методы будут выполняться медленно?
Одновременность и петля событий
Что произойдет, если в стеке есть вызов функции, требующей много времени на выполнение? Например, представьте, что вы хотите сделать какую-то сложную обработку изображения, используя JavaScript в браузере.
Вы можете спросить, а в чем проблема-то? Проблема в том, что пока стек вызовов содержит функции, ждущие очереди на выполнение, браузер не может ничего делать, он блокирован. Это значит, что браузер не может отрисовывать страницу, не может выполнять какой-то иной код, он просто завис. И это создает проблемы, если вы хотите сделать клевый плавный интерфейс вашего приложения.
И это не все проблемы. При большом стеке, заполненном функциями, браузер может надолго задуматься. Большинство браузеров вмешиваются, выбрасывая ошибку, спрашивая, не хотите ли вы остановить процесс на странице?
Не самый хороший UX, а?
Так как мы можем выполнять тяжелый код без блокировки интерфейса и зависания браузера? Ну, решение — асинхронные обратные вызовы.
Прим. переводчика: в оригинальной статье в конце идет ссылка на сервис SessionStack. Так как этот перевод объединяет две статьи, я помещу ссылку на сервис в конце. Кроме того, в конце статьи автор обещает рассказать про асинхронные обратные вызовы во второй статье цикла, но по факту этого не делает. Рассказ об асинхронных вызовах и петле событий — тема четвертой статьи, которую я переведу чуть позже.
Как работает JavaScript: устройство V8 и пять советов по оптимизации кода
Чуть выше мы разобрались с тем, как работает стек вызовов, и какие проблемы могут с ним быть у неопытных разработчиков. Теперь мы поговорим о движке V8. Движок JavaScript — программа, интерпретирующая код JS. Он может быть реализован как стандартный интерпретатор, или как JIT-компилятор байткода.
Вот список популярных реализаций движка JavaScript:
- V8 — движок с открытым кодом, написанный на C++ Гуглом.
- Rhino — разработка Mozilla Foundation, написан на Java, код открыт.
- SpiderMonkey — первый движок JavaScript, который когда-то использовался в Netscape Navigator, и теперь используется в Firefox.
- JavaScriptCore — движок, используемый в Safari, также известен как Nitro, разработка Apple с открытым кодом.
- KJS — движок, разработанный Гарри Портеном для браузера Konqueror
- Chakra(JScript9) — Internet Explorer.
- Chakra(JavaScript) — Microsoft Edge.
- Nashorn — часть проекта OpenJDK, разработка Oracle с открытым кодом.
- JerryScript — легковесный движок для IoT.
Зачем был создан V8
Движок V8, созданный Гуглом — разработка с открытым исходным кодом на C++. Движок используется в Google Chrome. В отличие от остальных движков, V8 используется также как рантайм Node.JS.
Изначально V8 был создан для улучшения производительности JavaScript внутри браузеров. В целях производительности V8 транслирует JavaScript в более эффективный машинный код вместо интерпретации. Он компилирует код в байткод на лету, используя JIT, аналогично некоторым современным движкам вроде SpiderMonkey и Rhino. Основная разница в том, что V8 не перегоняет в байткод любой промежуточный код.
Компиляторы V8
До версии 5.9, выпущенной в апреле 2017 года, движок использовал два компилятора:
- full-codegen, простой и очень быстрый компилятор, производящий простой и относительно медленный байткод.
- Crankshaft — более сложный JIT–компилятор, производящий сильно оптимизированный байткод.
В V8 также используется несколько потоков выполнения:
- Основной поток делает то, что ожидается: берет код, компилирует и выполняет.
- Кроме того, есть отдельный поток, который занимается только компиляцией, так что основной поток может заниматься своим делом, пока другие потоки занимаются оптимизацией.
- Поток профилирования сообщает движку, какие методы выполняются дольше других, так что Crankshaft может их оптимизировать.
- И еще несколько потоков занимаются сборкой мусора.
При первом выполнении кода V8 использует full-codegen, который напрямую транслирует код в байткод без какой-либо оптимизации. Это позволяет запуститься очень быстро. Заметим, что V8 не использует промежуточный байткод, что позволяет обойтись без интерпретатора.
По истечении некоторого времени в дело вступает профилировщик, который к этому моменту собрал достаточно данных, чтобы определить методы, требующие оптимизации.
Наконец, в дело вступает Crankshaft в отдельном потоке. Он транслирует абстрактное синтаксическое дерево в SSA (static single-assignment representation), называемое Hydrogen, и пытается оптимизировать полученный граф. Большинство оптимизаций происходят на этом уровне.
Прим. переводчика: я не нашел адекватной и развернутой статьи по SSA на русском языке. Если у вас нет проблем с английским, взгляните на материал по SSA «Static Single Assignment Book», в противном случае придется ограничиться статьей в Википедии.
Встраивание
Первая оптимизация состоит в том, чтобы встроить как можно больше кода. Встраивание — процесс замены вызывающего кода на тело вызываемого метода. Этот шаг сильно облегчит последующие оптимизации.
Скрытый класс
JavaScript — прототипный язык, в нем нет классов и объектов, создаваемых клонированием. Кроме того, это динамический язык, так что свойства могут быть легко добавлены или удалены из объекта после его создания.
Большинство JavaScript–интерпретаторов используют структуры типа словарь, основанные на хеш-функциях, чтобы хранить свойства объектов в памяти. Такие структуры делают получение значений свойств более дорогим, чем в статических языках вроде Java или C#. В Java все свойства объектов определяются фиксированной схемой объекта (классом) до этапа компиляции, и эта схема не может быть изменена в процессе выполнения. В C# есть тип dynamic, но это тема отдельного разговора.
В результате значения свойств объектов или указатели на значения сохраняются в памяти как последовательности байтов с определенными смещениями. Смещения могут быть легко вычислены, опираясь на тип свойства.
Такой подход невозможен в JavaScript, поскольку тип свойства может быть изменен в процессе выполнения.
В силу того, что использование словарей для хранения свойств объектов в памяти очень неэффективно, V8 использует другой метод, называемый скрытыми классами. Они похожи на классы в языках типа Java, но создаются в процессе выполнения кода. Давайте посмотрим, как это выглядит.
Как только выполнение дойдет до строки «new Point(1, 2)», V8 создаст скрытый класс C0.
Пока у Point нет свойств, так что C0 пока пуст.
Как только первое выражение «this.x = x» выполнится внутри функции Point, V8 создаст второй скрытый класс C1, основанный на C0. С1 описывает то, где в памяти находится значение свойства x относительно указателя на объект. В нашем случае x сохранен по смещению 0, то есть при поиске в памяти объекта первое смещение будет соответствовать свойству x. Кроме того, V8 обновит класс C0 с помощью «классового перехода»: если к объекту Point добавится свойство x, следует опираться на скрытый класс C1. Нашему экземпляру объекта Point также соответствует класс C1.
Всякий раз при добавлении нового свойства старый скрытый класс обновляет путь поиска актуального класса. Скрытые классы важны, поскольку они могут быть разделены между объектами, создаваемыми одинаково. Если два объекта разделяют один класс, и в оба объекта добавляется одно и то же свойство, движок гарантирует, что оба класса получат один и тот же новый скрытый класс.
Аналогичный процесс повторится на выражении «this.y = y».
Переходы между скрытыми классами основаны на порядке добавления свойств. Взгляните на кусок кода:
Вы можете предположить, что для p1 и p2 будет использован один скрытый класс и один переход. Ну, не совсем. Для p1 сначала добавлено свойство a, потом b. Для p2, напротив, сначала b, потом a. Так что в итоге p1 и p2 будут использовать разные скрытые классы, и разные пути переходов. Будет разумно по возможности инициализировать динамические свойства в одном и том же порядке для переиспользования динамических классов.
Встроенное кеширование
V8 использует еще один подход для оптимизации динамически типизированных языков, известный как встроенное кеширование. Он основан на наблюдении повторных вызовов одного и того же метода для одного и того же типа объекта. Подробное объяснение принципов работы можно прочесть в материале «Optimizing dynamic JavaScript with inline caches». Мы рассмотрим общие принципы встроенного кеширования, на случай, если у вас нет времени читать подробный разбор.
Так как это работает? V8 хранит типы объектов, передаваемых в качестве параметров для недавних вызовов, и использует эту информацию для предположения того, какой тип объекта будет использоваться для будущих вызовов. Если V8 сможет сделать точное предположение, он сможет пропустить процесс поиска свойств объекта, и вместо этого обратиться напрямую к скрытому классу.
Итак, как соотносятся концепции встроенного кеширования и скрытых классов? Когда метод вызывается для конкретного объекта, V8 ищет скрытый класс объекта, чтобы определить смещение нужного свойства. После двух успешных выполнений метода для того же скрытого класса движок пропускает все привычные операции и просто прибавляет смещение свойства к указателю на объект, сразу получая значение параметра. Для последующих вызовов V8 делает прямой вызов к памяти, удостоверившись предварительно, что скрытый класс не изменился. Это значительно ускоряет дело.
Встроенное кеширование — причина того, почему так важно разделять скрытые классы между объектами одного типа. Если для двух однотипных объектов будут использованы разные скрытые классы, V8 не сможет применить встроенное кеширование даже между двумя объектами одного типа, поскольку их разные скрытые классы будут иметь разное смещение для свойств.
Два объекта по сути эквивалентны, но их свойства созданы в разном порядке.
Компиляция в машинный код
Как только Hydrogen граф будет оптимизирован, Crankshaft преобразует его в низкоуровневое представление под названием Lithium. Большинство реализаций Lithium специфичны для конкретной архитектуры. Распределение регистров происходит на этом уровне.
В итоге Lithium компилируется в машинный код. Затем происходит операция под названием on-stack replacement, OSR. Прежде чем движок начнет компилировать и оптимизировать очевидно долгоиграющие методы, мы скорее всего уже запустим их. V8 не собирается забывать то, что он уже выполнял, и просто запустить оптимизированные версии. Вместо этого он трансформирует контекст, стек и регистры, так что мы можем переключиться на оптимизированную версию прямо в процессе выполнения. Это действительно сложная задача, учитывая то, что в числе прочих оптимизаций V8 уже встроил некоторое количество кода. Но V8 не единственный движок, способный на это.
Существует защитный механизм, называемый деоптимизацией. Он проводит обратное преобразование, возвращая обратно неоптимизированный код в случае, если движок ошибся с предположением.
Сборка мусора
Для сборки мусора V8 использует традиционный подход поколений и mark–and–sweep для очистки старого поколения. Фаза разметки предполагает приостановку выполнения кода. В целях контроля затрат на сборку и более плавной работы V8 использует постепенную разметку: вместо обхода всей кучи в попытках пометить каждый объект, он обходит часть кучи и продолжает нормальную работу. Следующая сборка начнется с места, где остановилась предыдущая. Это позволяет делать небольшие паузы в процессе работы. Как мы выяснили выше, фаза очистки происходит в отдельном потоке.
Ignition и TurboFan
Начиная с версии 5.9 в V8 появится новый подход к выполнению кода. Он обеспечит большую производительность и значительно меньший расход памяти на реальных задачах. Новый V8 построен поверх Ignition, интерпретатора V8 и TurboFan, нового оптимизирующего компилятора. Вы можете узнать больше об этом из поста в блоге разработчиков V8.
С версии 5.9 full-codegen и Crankshaft больше не будут использоваться в V8, а разработчики движка постараются реализовать новые возможности JavaScript и соответствующие оптимизации. Это значит, что в целом V8 получит более простую и поддерживаемую архитектуру.
Бенчмарк новой версии
Эти улучшения — только начало. Ignition и TurboFan проложат путь для дальнейших оптимизаций, которые увеличат производительность JavaScript и уменьшат размер движка в Chrome и Node.JS.
Прим. переводчика: статья не очень новая, так что на самом деле уже вышел V8 версии 6.0. Я просмотрел список изменений по диагонали, и не нашел ничего, что напрямую касается каких-то новых технологий компиляции и интерпретации.
Наконец, вот несколько советов, которые помогут вам писать хорошо оптимизированный JavaScript код. Вы не найдете ничего такого, чего бы мы не разобрали выше. Это просто обобщение уже известных вещей для вашего удобства:
- Порядок добавления свойств: объявляйте свойства в том же порядке для однотипных объектов, чтобы они переиспользовали одни и те же скрытые классы.
- Динамические свойства: добавление свойств к объекту во время выполнения заставляет движок построить новый скрытый класс и лишает все предыдущие скрытые классы уже готовой оптимизации. По возможности объявляйте все свойства в конструкторе.
- Методы: код, в котором один и тот же метод выполняется несколько раз подряд, быстрее того, где всегда выполняются разные методы. Причина — встроенное кеширование.
- Массивы: избегайте sparse-массивов, в которых ключи — не последовательные числа. Sparse-массивы, которые не содержат каждый элемент внутри — по сути хеш-таблицы. Доступ к элементам таких массивов обходится дороже. Кроме того, старайтесь не объявлять сразу большие массивы. Увеличение массивов по необходимости обойдется дешевле. И наконец, не удаляйте элементы из массивов, потому что это превратит их в sparse-массивы.
- Помеченные значения: V8 представляет объекты и целые числа 32-мя битами, и использует один бит для хранения типа: объект это (flag=1) или число (flag=0). Таким образом, целые числа представляются как SMI (SMall Integer), поскольку на самом деле занимают только 31 бит. Если число больше 31 бита, V8 упаковывает его, преобразуя в double, и создает новый объект, помещая число внутрь. Старайтесь укладывать знаковые целые числа в 31 бит, чтобы избежать больших расходов на упаковку-распаковку.
Прим. переводчика: пятый пункт в оригинале звучит как «tagged values». Я не смог до конца понять, почему у этого пункта такое название, когда речь идет просто о накладных расходах на автоупаковку. Думаю, что в итоге это не помешает понять смысл совета.
В SessionStack мы стараемся следовать этим практикам для написания хорошо оптимизированного кода. Причина в том, что при интеграции SessionStack в ваше работающее приложение сервис записывает все: изменения DOM, взаимодействие с пользователем, исключения, стектрейсы, неудачные сетевые запросы и отладочные сообщения. С SessionStack вы можете воспроизвести проблемы вашего приложения и увидеть своими глазами все, что произошло, глазами пользователя. И все это — без влияния на производительность вашего кода.
Если вам интересно, у нас есть бесплатный тарифный план, чтобы можно было опробовать сервис.
Ресурсы
Это первые две из четырех статей цикла. Следующая статья — об устройстве памяти в JavaScript и борьбе с утечками памяти. Она выйдет в среду на этой неделе. Не переключайтесь!
Как работает javascript
Всегда было интересно узнать, как же работает js? Он исполняется на стороне клиента или на стороне сервера? Т.е. он несет нагрузку на сервер? И можно ли как-то запретить просмотр кода js, но разрешить исполнение?
JavaScript это просто язык. Так же, как, скажем, русский. Это абстрактная сущность. Чтобы она работала нужно две вещи — программа и исполнитель. А сам JavaScript — это только соглашение о том, как программист будет писать программу и как исполнитель (машина) будет ее трактовать.
Соответственно идея «выполнять, но не читать» имеет принципиальный дефект. Нельзя (намеренно, по приказу) сделать что-то, не зная, собственно, что делать. Можно не видеть всей программы, а знать только очередную инструкцию. Можно получить не саму программу, а ее запутанную-преобразованную версию, функционально аналогичную (т.е. результат будет тот же самый), но труднодоступную для понимания. Это относится в равной мере и к коду на JavaScript, и к приказу на русском языке. И вообще к любой системе программа-исполнитель.
Т.е. можно код на JS скомпилировать в какой-то другой код (правда, его должен понять браузер, а из браузеров очень плохие полиглоты) или обфусцировать, сделав «нечитаемым» человеком. Других вариантов в голову не приходит.
Выполняется, соответственно, там где есть исполнитель. Один из возможных, и наиболее частых исполнителей — браузер. Отправляем туда текст программы, браузер «видит» понятные ему директивы и действует. Сервер, в этом случае, JS-код не выполняет — код предназначен не для него — он его только отдает. И все это, разумеется, только если браузер поддерживает JavaScript (его поддержки может не быть, как, например, в старых мобильных телефонах, или он может быть отключен).
Другой возможный исполнитель — сервер. Разумеется, само по себе ничего не произойдет — серверу, как и браузеру, нужно дать код и явным образом сказать «выполняй вот это.» И, разумеется, сервер должен иметь средства для выполнения JavaScript-кода (интерпретатор или компилятор). Примеры таких технологий — старый-древний ASP (там был JScript а не JavaScript, но разница невелика) или современные Rhino или node.js. Тут нет разницы между JavaScript, PHP, Python или любым другим языком. Ну, просто, вместо интерпретатора и кода на PHP заставили сервер запустить интерпретатор и код на JS.
Но это все общие слова. Ваш вопрос, как я догадываюсь, подразумевает одну вполне конкретную ситуацию — JS, который отдается в браузер. И тогда здесь простой и короткий ответ: «Нет, не несет и, нет, нельзя.»
Основы работы и применения JavaScript
Когда сервер получает команду на выдачу страницы от браузера и передаёт её, браузер получает страницу с сервера как программный код (увидеть исходный код можно, выполнив команду в браузере, «просмотр кода страницы»).
Далее в браузере идёт обработка и исполнение кода, выведение страницы.
Как встроить JavaScript код в страницу сайта
Основные способы это :
- вставка ссылкой
- использование событий
- подстановка
- контейнер <script></script>
Способ — вставка JavaScript кода ссылкой.
В HTML применение гипертекстового перехода ( по ссылке) означает открытие другой страницы или переход в другое место на странице.
С помощью Javascript можно изменить поведение HTML элемента.
Поменять стандартные варианты обработки гипертекстовых переходов можно прописав код:
<A HREF=»JavaScript: ЗДЕСЬ_БУДЕТ_ВСТАВЛЕН_КОД»>. </A>
Здесь приведёна общая схема. Сам код, задающий команду, пока не вписан. Но основной принцип, думаю понятен.
Использование событий
Об основах использования событий вызова JS говорилось ранее в.
Для определённого элемента в HTML коде назначается событие, при исполнении которого выполняется javascript код.
Для примера:
<INPUT TYPE=button VALUE=»Нажмите)t »
onMouseOver=»window.alert(‘HELLO)))’);»>
Подстановки
Подстановка — устаревший способ и почти не используется для вызова JavaScript.
К атрибуту элемента применяется конструкция &<..>, которая сообщает браузеру, что следует выполнить JScript команды. Подстановка малоиспользуема, так как имеются более удобные и эффективные способы.
Контейнер <script></script>
Применение способа контейнер <Script></Script>, позволяет генерировать Html код страницы.
Действие происходит так. Обработчик HTML кодов встречаясь с контейнером <Script>, отдаёт дальнейшее управление интерпретатору JS, который обходит Javascript и генерирует на выходе HTML код, который сканируется уже HTML обработчиком.
Контейнер JavaScript может быть размещен либо внутри <HEAD></HEAD>, либо внутри <BODY></BODY>.
Затруднительным в изучении этого языка на начальных этапах может стать использование не совсем привычной грамматики.
Например при вызове встроенных событий, если вместо onMouseOver написать ONMOUSEOVER или onmouseover, интерпретатор не сможет прочесть выполнить команду.