Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Processing.js

Глава 17 из книги "Архитектура приложений с открытым исходным кодом", том 2.

Оригинал: Processing.js
Автор: Mike Kamermans
Перевод: А.Панин

Язык Java использует строгую типизацию; JavaScript не использует ее.

В языке Java числовые значения 2 и 2.0 отличаются, поэтому при их использовании в математических операциях будут получены различные результаты. Например, при использовании кода i = 1/2 значение переменной i будет равно 0, так как эти значения рассматриваются как целочисленные, но при этом при использовании кода i = 1/2.0, i = 1.0/2 и даже i = 1./2. значение переменной i будет равно 0.5, так как числа рассматриваются как дробные десятичные с ненулевой целой частью и нулевой дробной частью. Даже в том случае, если результирующий тип данных является числовым с плавающей точкой, а в арифметических операциях используются только целочисленные значения, результат также будет целочисленным. Это обстоятельство позволяет вам записывать достаточно сложные математические выражения при использовании языка программирования Java, а, следовательно, и при использовании языка программирования Processing, но они будут генерировать теоретически значительно отличающиеся результаты при переходе к использованию библиотеки Processing.js, так как в рамках языка JavaScript вводится только понятие "чисел". При разговоре о JavaScript следует упомянуть о том, что значения 2 и 2.0 рассматриваются как одно и то же число, что может привести к очень занимательным ошибкам при выполнении скетча с использованием библиотеки Processing.js.

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

Java является основанным на классах и их экземплярах объектно-ориентированным языком программирования с четким разделением между пространствами переменных и методов. JavaScript не является таковым.

JavaScript использует прототипы объектов и соответствующую им модель наследования. Это значит, что все объекты представлены в формате пар ключ/значение, где каждый ключ представлен строкой, а значения являются примитивами, массивами, объектами или функциями. При взгляде со стороны наследования следует отметить, что прототипы могут дополнять другие прототипы, но не существует реальной концепции "суперкласса" и "подкласса". Для добавления возможности исполнения "корректного" объектно-ориентированного кода в Java-стиле нам пришлось реализовать классическую модель наследования для языка JavaScript в рамках библиотеки Processing.js без значительного замедления ее работы (нам кажется, что в этом плане мы добились успеха). Нам также пришлось предложить способ предотвращения коллизий между именами переменных и именами функций. Из-за представления объектов в JavaScript в формате ключ/значение, описание переменной с именем line с последующим описанием функции, подобным line(x1,y1,x2,y2) позволит вам работать с объектом, использующим только то, что было объявлено в последнюю очередь. Сначала JavaScript устанавливает значение object.line = "some value", после чего повторно устанавливает значение переменной line object.line = function(x1,y1,x2,y2)(...), заменяя то значение line, которое вы ожидали увидеть.

Создание механизма раздельного управления переменными и методами/функциями значительно замедлило бы библиотеку, поэтому в документации точно так же описано то, что использование функций и переменных с одинаковыми именами является плохой идеей. В том случае, если бы все разрабатывали "корректный" код, это не стало бы большой проблемой, так как обычно переменные и функции называются в зависимости от того, для чего они предназначены или что они делают, но в реальности все не так. Иногда ваш код не будет работать и это произойдет из-за нашего решения, заключающегося в том, что в случае конфликта имен предпочтительным вариантом является отказ от выполнения кода вместо медленной работы в любом случае. Второй причиной, обуславливающей отсутствие реализации разделения между переменными и функциями является то, что такой подход может привести к неработоспособности кода на языке JavaScript, используемого в скетчах системы Processing. Замыкания и цепочка областей видимости языка JavaScript основываются на характере объектов, представленных парами ключ/значение, поэтому вмешательство в процесс их обработки путем разработки собственных методов управления также могло значительно повлиять на производительность, в особенности в областях компиляции при выполнении и компрессии, использующих замыкания функций.

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

Одной из наиболее мощных возможностей языка Java является возможность объявления функции, скажем add(int,int), после которой может быть объявлена другая функция с таким же именем, но отличающимся набором аргументов, т.е, add(int,int,int) или с другими типами аргументов, т.е., add(ComplexNumber,ComplexNumber). При вызове функции add с двумя или тремя целочисленными аргументами автоматически будет вызвана соответствующая функция, а при вызове функции add с аргументами, представленными числами с плавающей точкой или объектами Car, будет сгенерирована ошибка. С другой стороны, в языке JavaScript нет поддержки такой возможности. В JavaScript функция является свойством и вы можете разыменовать ее (в этом случае JavaScript передаст вам значение после приведения типов, которое в данном случае будет логическим значением true, если свойство указывает на описание функции или false в противном случае) или осуществить ее вызов, используя операторы исполнения (которые записываются с помощью скобок, без или с некоторым количеством аргументов в них). Если вы объявите функцию add(x,y), после чего осуществите ее вызов add(1,2,3,4,5,6), описанный код будет приемлем для JavaScript. В качестве значения аргумента x будет установлено число 1, а в качестве значения y - 2, причем все последующие аргументы будут просто проигнорированы. Для того, чтобы сделать механизм перегрузки методов работоспособным, мы заменяем функции с идентичными именами и разным количеством аргументов на пронумерованную функцию, следовательно функция function(a,b,c) в исходном коде будет преобразована в функцию function$3(a,b,c) в результирующем коде и функция function(a,b,c,d) превратится в функцию function$4(a,b,c,d), что позволит использовать корректные пути исполнения кода.

Мы также практически полностью решили проблему перегрузки функций с одинаковым количеством аргументов различных типов, так как типы аргументов могут дифференцироваться средствами языка JavaScript. JavaScript может сообщить тип свойств функций при использовании оператора typeof, который вернет строку number, string, object или function в зависимости от того, что представлено с помощью свойства. Описание var x = 3 с последующим описанием x = '6' приведет к тому, что typeof x вернет строку number после первого описания и строку string после повторного присваивания. Пока функции с одинаковым количеством аргументов отличаются их типами, мы осуществляем их переименование и выбор в зависимости от результата операции typeof. Этот подход не работает в том случае, если функции принимают аргументы типа object, поэтому для таких функций мы используем дополнительную проверку с помощью оператора instanceof (который возвращает имя функции, использованной для создания объекта) для поддержки работоспособности механизма перегрузки методов. Фактически, единственным местом, в котором мы не можем успешно провести транскомпиляцию перегружаемых функций, являются участки кода, на которых в функциях используется одинаковое количество аргументов разных числовых типов. Так как язык JavaScript имеет только один числовой тип, объявления таких функций, как add(int x, int y), add(float x, float y) и add(double x, double y), будут конфликтовать друг с другом. Во всех других случаях, однако, механизм перегрузки методов работает отлично.

Язык Java позволяет осуществлять импорт скомпилированного кода.

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

Это является проблемой, так как скомпилированный Java-код является байткодом, используемым виртуальной машиной Java. Данное обстоятельство доставило нам много головной боли: как реализовать импорт функций из библиотек без разработки декомпилятора байткода Java? После длившихся практически в течение года дискуссий мы пришли к по-видимому наиболее простому решению. Вместо того, чтобы пытаться также добавить поддержку библиотек языка программирования Processing, мы решили добавить поддержку ключевого слова import в скетчи и создать API библиотеки дополнительных функций Processing.js, предоставив таким образом разработчикам возможность создания версий их библиотек на языке JavaScript (в том случае, когда эта задача выполнима в условиях работы веб-приложений), таким образом, в том случае, если они разработают библиотеку дополнительных функций, используемую с помощью директивы import prosessing.video, язык программирования Processing будет использовать архив с расширением .jarchive, а библиотека Processing.js вместо этого будет использовать код из файла processing.video.js, поэтому в обоих случаях приложение будет "просто работать". Эти функции предназначены для включения в релиз Processing.js 1.4, а возможность импорта кода библиотек является последней важной функцией, которой не хватало в Processing.js (на данный момент мы поддерживаем ключевое слово import, но только таким образом, что оно удаляется из исходного кода перед его преобразованием) и будет последним важным шагом для достижения совместимости.


Далее: Для чего использовать язык JavaScript