Номенклатура JavaScript (в контексте Node.js и Web API)
I. Предыстория
Я много лет использую UltraEdit как редактор на самые разные случаи жизни. Одна из основных причин — быстрая работа с гигабайтными файлами без загрузки их в память. Для программирования на JavaScript он тоже достаточно удобен, вот только с одним существенным недостатком: автодополнение в нём основывается на достаточно бедном, жёстко заданном списке ключевых слов и глобальных переменных, вдобавок отстающем от развития языка. Как-то я задался вопросом, можно ли пополнить этот список полным перечнем всех готовых свойств и методов, какие только можно ввести в контексте Node.js и Web API (браузера). Где бы такой список можно раздобыть? Мне приходили в голову такие варианты:
Готовый перечень, кем-то составляемый и обновляемый для всеобщего пользования, вроде библиотеки globals, но полнее.
Парсинг документации (спецификация ECMAScript, сайты MDN и Node.js и т.п.), вручную или программно.
- Получение списка метапрограммированием.
Основным ответом на мои вопросы было предложение сменить редактор и не мучиться. Но так как удобных редакторов для больших файлов не так уж и много, а мой минимализм затруднял использование нескольких под разные нужды, да и программирование не было моим основным занятием, я не сдавался. В конце концов, это стало для меня не только практической задачкой, но и принципиальным интересом: как же так, вроде бы такая простая нужда, а лёгких решений нет.
Поскольку готовых списков я не нашёл, а парсить документацию — путь долгий и ненадёжный, я решил попробовать третий способ.
II. Код
Воспользовавшись несколькими советами, я написал небольшой скрипт, который выводит основную часть языковой номенклатуры в разных контекстах несколькими способами.
Вот что у меня получилось.
Скрипт можно запустить в Node.js или в браузере (через консоль или вставку в страницу). В первом случае результат будет выведен в файлы, во втором — в прибавленные к текущему документу текстовые поля (можно открыть about:blank
вместе с консолью).
Постараюсь прокомментировать код.
Сперва мы создаём основные переменные-контейнеры. В первых двух мы будем накапливать нашу номенклатуру: в
nomenclatureTerms
будет храниться простой список всех лексем, вnomenclatureChains
— те же лексемы, но с полными цепочками, начиная от корневых объектов. Вglobs
мы будем хранить наши отправные точки для разматывания клубка и построения дерева — глобальные (корневые) объекты. Чтобы избежать бесконечной рекурсии из-за циклических ссылок, все обработанные объекты мы будем складывать вprocessedObjects
для последующей проверки.На втором этапе мы заполняем
globs
.Сначала скрипт пытается определить, в каком контексте он выполняется. Если это браузер, нам достаточно объекта
window
.Если это Node.js, всё немного сложнее. Сначала мы добавляем два основных глобальных объекта, а также
require
, поскольку иначе на эту функцию мы не выйдем. Потом мы добавляем объекты всех стандартных библиотек: основную часть — отталкиваясь от недокументированного спискаrequire('repl')._builtinLibs
, посоветованного одним из разработчиков Node.js, а затем несколько недостающих модулей. В завершение, поскольку несколько внутримодульных переменных (__dirname
и__filename
) не привязаны ни какому глобальному объекту, мы сразу же добавим их в наши номенклатурные контейнеры.Далее следует основная работа: при помощи рекурсивной функции
processKeys
мы обходим все глобальные объекты и все объекты, хранящиеся в их свойствах, до последней возможной глубины. Затем выводим результаты в зависимости от контекста и завершаем их итоговым выводом в консоль размеров наших номенклатур (скрипт работает ощутимое время, так что этот вывод может служить сигналом завершения работы — хотя в Chrome может потребоваться дополнительное время на обновление страницы даже после этого сигнала).Функция
processKeys
является основным двигателем процесса.Сперва мы проверяем, с корневым ли объектом мы имеем дело. Если да, мы сразу заносим его имя в номенклатуру. Если объект расположен в дочернем свойстве объекта, это занесение уже совершилось на предыдущем этапе рекурсии, поэтому мы его пропускаем.
Затем мы заносим объект в список обработанных объектов, чтобы не попасть в дурную бесконечность.
После этого начинаем обходить все свойства объекта. Каждое из них мы заносим в
nomenclatureTerms
(типSet
автоматически отбрасывает повторения), затем формируем цепочку из имени родительского объекта и текущего свойства и заносим её вnomenclatureChains
; эта же цепочка станет именем объекта для следующего рекурсивного вызова, поэтому она будет постоянно расти с продвижением вглубь (я выбрал нотацию с квадратными скобками на все случаи для унификации сортировки: если использовать точку для обычных идентификаторов и скобки для сложных строковых, это ломает порядок при выводе списка;JSON.stringify
употребляется для перестраховки — для экранирования возможных кавычек в составе имён свойств).На следующем этапе мы проверяем, что хранится в свойстве: если это объект, мы делаем новый рекурсивный вызов, если этого объекта ещё нет в списке обработанных. Проверка на объектность двойная, поскольку
instanceof Object
возвращаетfalse
дляObject.prototype
и для объектов, созданных при помощиObject.create(null)
.Такое повсеместное прохождение по свойствам часто вызывает ошибки, поэтому нам придётся добавить обработчик, чтобы процесс не прерывался (вывод сообщений об ошибках оставлен ради любопытства). Также в консоль, помимо нашего желания, будет выведено несколько предупреждений о попытках запросить свойства, получившие статус
deprecated
.- Функция
output
отвечает за вывод результатов в зависимости от контекста выполнения. Сначала она формирует список, отсортированный в более привычном словарном порядке (правда, параметрcaseFirst
в Firefox не работает). Затем проверяет контекст выполнения: в браузере списки выводятся в два текстовых поля, встраиваемые в текущую страницу (вверх списка добавляется для удобства имя файла, с которым список можно сохранить при помощи редактора); в Node.js создаются два файла в текущем каталоге.
Следует учитывать, что к браузерному списку добавляются имена функций нашего скрипта, а к списку Node.js — разные переменные окружения; также в перечень включаются разные недокументированные свойства внутреннего употребления, индексы массивов и т.п. С другой стороны, в наш список не попадают многие строковые элементы номенклатуры (например, названия событий или стандартные строковые параметры функций).
III. Результаты
После прогона скрипта на последней стабильной версии Node.js и на ночных сборках двух браузеров я получил следующие списки:
`
Node 6.6.0
Terms: 1 813
Chains: 7 282
Google Chrome Canary 55.0.2867.0
Terms: 3 339
Chains: 14 435
Firefox Nightly 52.0a1 (2016–09–21)
Terms: 5 040
Chains: 14 417
`
Возможно, у результатов программы могут быть разные применения. Например, сравнение номенклатуры разных браузеров или разных версий одного браузера (во время тестирования я замечал, что ночные сборки соседних дней могут давать результаты, различающиеся десятками позиций — что-то вводится, что-то уходит в историю). Если автоматизировать процесс, можно, например, создать историю API Node.js на протяжении многих версий. А можно собрать разнообразную языковую статистику: глубина вложения свойств, длина идентификаторов, принципы их создания и т.д.
Наверняка код можно оптимизировать по скорости, по удобству использования, по полноте результатов или их читабельности. Также я мог допустить какие-то глупые ошибки из-за незнания тонкостей языка или контекстов использования. Буду благодарен за поправки и добавления. Спасибо за внимание.
Комментарии (2)
22 сентября 2016 в 07:23 (комментарий был изменён)
0↑
↓
Как-то я задался вопросом, можно ли пополнить этот список полным перечнем всех готовых свойств и методов, какие только можно ввести в контексте Node.js и Web API (браузера). Где бы такой список можно раздобыть?
Есть же для Typescript .d.ts-файлы, где описывают сигнатуры для всяких библиотек, в том числе — стандартных. Многие редакторы/IDE их и используют.
Вот тут список файлов с сигнатурами стандартных библиотек ES и работы с DOM: https://github.com/Microsoft/TypeScript/tree/master/lib
Для ноды можно взять эти файлы из репозитория с кучей сигнатур для кучи библиотек: https://github.com/DefinitelyTyped/DefinitelyTyped
Вот папка с сигнатурами ноды: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/node
В общем, странно что вы ничего не слышали про это, раз пишите на JS.
22 сентября 2016 в 07:26
0↑
↓
Есть готовый перечень вообще-то.