- PVSM.RU - https://www.pvsm.ru -
Я много лет использую UltraEdit как редактор на самые разные случаи жизни. Одна из основных причин — быстрая работа с гигабайтными файлами без загрузки их в память. Для программирования на JavaScript он тоже достаточно удобен, вот только с одним существенным недостатком: автодополнение в нём основывается на достаточно бедном, жёстко заданном списке ключевых слов и глобальных переменных, вдобавок отстающем от развития языка. Как-то я задался вопросом, можно ли пополнить этот список полным перечнем всех готовых свойств и методов, какие только можно ввести в контексте Node.js и Web API (браузера). Где бы такой список можно раздобыть? Мне приходили в голову такие варианты:
Готовый перечень, кем-то составляемый и обновляемый для всеобщего пользования, вроде библиотеки globals [1], но полнее.
Парсинг документации (спецификация ECMAScript, сайты MDN и Node.js и т.п.), вручную или программно.
Основным ответом на мои вопросы было предложение сменить редактор и не мучиться. Но так как удобных редакторов для больших файлов не так уж и много, а мой минимализм затруднял использование нескольких под разные нужды, да и программирование не было моим основным занятием, я не сдавался. В конце концов, это стало для меня не только практической задачкой, но и принципиальным интересом: как же так, вроде бы такая простая нужда, а лёгких решений нет.
Поскольку готовых списков я не нашёл, а парсить документацию — путь долгий и ненадёжный, я решил попробовать третий способ.
Воспользовавшись несколькими советами, я написал небольшой скрипт, который выводит основную часть языковой номенклатуры в разных контекстах несколькими способами.
Скрипт можно запустить в Node.js или в браузере (через консоль или вставку в страницу). В первом случае результат будет выведен в файлы, во втором — в прибавленные к текущему документу текстовые поля (можно открыть about:blank
вместе с консолью).
Постараюсь прокомментировать код.
1. Сперва мы создаём основные переменные-контейнеры. В первых двух мы будем накапливать нашу номенклатуру: в nomenclatureTerms
будет храниться простой список всех лексем, в nomenclatureChains
— те же лексемы, но с полными цепочками, начиная от корневых объектов. В globs
мы будем хранить наши отправные точки для разматывания клубка и построения дерева — глобальные (корневые) объекты. Чтобы избежать бесконечной рекурсии из-за циклических ссылок, все обработанные объекты мы будем складывать в processedObjects
для последующей проверки.
2. На втором этапе мы заполняем globs
.
Сначала скрипт пытается определить, в каком контексте он выполняется. Если это браузер, нам достаточно объекта window
.
Если это Node.js, всё немного сложнее. Сначала мы добавляем два основных глобальных объекта, а также require
, поскольку иначе на эту функцию мы не выйдем. Потом мы добавляем объекты всех стандартных библиотек: основную часть — отталкиваясь от недокументированного списка require('repl')._builtinLibs
, посоветованного одним из разработчиков Node.js, а затем несколько недостающих модулей. В завершение, поскольку несколько внутримодульных переменных (__dirname
и __filename
) не привязаны ни какому глобальному объекту, мы сразу же добавим их в наши номенклатурные контейнеры.
3. Далее следует основная работа: при помощи рекурсивной функции processKeys
мы обходим все глобальные объекты и все объекты, хранящиеся в их свойствах, до последней возможной глубины. Затем выводим результаты в зависимости от контекста и завершаем их итоговым выводом в консоль размеров наших номенклатур (скрипт работает ощутимое время, так что этот вывод может служить сигналом завершения работы — хотя в Chrome может потребоваться дополнительное время на обновление страницы даже после этого сигнала).
4. Функция processKeys
является основным двигателем процесса.
Сперва мы проверяем, с корневым ли объектом мы имеем дело. Если да, мы сразу заносим его имя в номенклатуру. Если объект расположен в дочернем свойстве объекта, это занесение уже совершилось на предыдущем этапе рекурсии, поэтому мы его пропускаем.
Затем мы заносим объект в список обработанных объектов, чтобы не попасть в дурную бесконечность.
После этого начинаем обходить все свойства объекта. Каждое из них мы заносим в nomenclatureTerms
(тип Set
автоматически отбрасывает повторения), затем формируем цепочку из имени родительского объекта и текущего свойства и заносим её в nomenclatureChains
; эта же цепочка станет именем объекта для следующего рекурсивного вызова, поэтому она будет постоянно расти с продвижением вглубь (я выбрал нотацию с квадратными скобками на все случаи для унификации сортировки: если использовать точку для обычных идентификаторов и скобки для сложных строковых, это ломает порядок при выводе списка; JSON.stringify
употребляется для перестраховки — для экранирования возможных кавычек в составе имён свойств).
На следующем этапе мы проверяем, что хранится в свойстве: если это объект, мы делаем новый рекурсивный вызов, если этого объекта ещё нет в списке обработанных. Проверка на объектность двойная, поскольку instanceof Object
возвращает false
для Object.prototype
и для объектов, созданных при помощи Object.create(null)
.
Такое повсеместное прохождение по свойствам часто вызывает ошибки, поэтому нам придётся добавить обработчик, чтобы процесс не прерывался (вывод сообщений об ошибках оставлен ради любопытства). Также в консоль, помимо нашего желания, будет выведено несколько предупреждений о попытках запросить свойства, получившие статус deprecated
.
5. Функция output
отвечает за вывод результатов в зависимости от контекста выполнения. Сначала она формирует список, отсортированный в более привычном словарном порядке (правда, параметр caseFirst
в Firefox не работает). Затем проверяет контекст выполнения: в браузере списки выводятся в два текстовых поля, встраиваемые в текущую страницу (вверх списка добавляется для удобства имя файла, с которым список можно сохранить при помощи редактора); в Node.js создаются два файла в текущем каталоге.
Следует учитывать, что к браузерному списку добавляются имена функций нашего скрипта, а к списку Node.js — разные переменные окружения; также в перечень включаются разные недокументированные свойства внутреннего употребления, индексы массивов и т.п. С другой стороны, в наш список не попадают многие строковые элементы номенклатуры (например, названия событий или стандартные строковые параметры функций).
После прогона скрипта на последней стабильной версии Node.js и на ночных сборках двух браузеров я получил следующие списки:
Node 6.6.0
» Terms: 1 813 [2]
» Chains: 7 282 [3]
Google Chrome Canary 55.0.2867.0
» Terms: 3 339 [4]
» Chains: 14 435 [5]
Firefox Nightly 52.0a1 (2016-09-21)
» Terms: 5 040 [6]
» Chains: 14 417 [7]
Возможно, у результатов программы могут быть разные применения. Например, сравнение номенклатуры разных браузеров или разных версий одного браузера (во время тестирования я замечал, что ночные сборки соседних дней могут давать результаты, различающиеся десятками позиций — что-то вводится, что-то уходит в историю). Если автоматизировать процесс, можно, например, создать историю API Node.js на протяжении многих версий. А можно собрать разнообразную языковую статистику: глубина вложения свойств, длина идентификаторов, принципы их создания и т.д.
Наверняка код можно оптимизировать по скорости, по удобству использования, по полноте результатов или их читабельности. Также я мог допустить какие-то глупые ошибки из-за незнания тонкостей языка или контекстов использования. Буду благодарен за поправки и добавления. Спасибо за внимание.
Автор: vmb
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/190938
Ссылки в тексте:
[1] globals: https://github.com/sindresorhus/globals/
[2] Terms: 1 813: https://gist.github.com/vsemozhetbyt/3115e443f8b3bac42a7092cd34f3e483
[3] Chains: 7 282: https://gist.github.com/vsemozhetbyt/1bf2ed5693d2777d171a16b2afad4fac
[4] Terms: 3 339: https://gist.github.com/vsemozhetbyt/d4d1bb025cc9165b335ab680d617c465
[5] Chains: 14 435: https://gist.github.com/vsemozhetbyt/636827943688f406a8eb1b0dff56b405
[6] Terms: 5 040: https://gist.github.com/vsemozhetbyt/b4c558759caa025b09d72d7960ca1643
[7] Chains: 14 417: https://gist.github.com/vsemozhetbyt/4913b2a1861df6e58faa5fc9823a14f6
[8] Источник: https://habrahabr.ru/post/310662/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.