Еще один нюанс JavaScript, о котором все знают, но не все задумываются

176eaf53d8b94a9795f6c81ccff6e8d5.png В последнее время вышло много статей о Javascript. Как холиварных, рассказывающих о том, какой он плохой, или какой он хороший, так и полезных рассказывющих о некоторых странных особенностях и разжевывающих «почему так», как например вот эта.
И я решил сделать свой микровклад в эту тему.

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

let a = {
  'myKey': 'myValue'
}
let key = 'constructor'; // comes from outside source
let b = a[key] || 'defaultValue';
expect(b).to.be.equal('defaultValue'); // fails


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

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

Библиотека hash-map решает эту задачу методом сокрытия всех нижележащих полей пустыми значениями:

  const result = {};
  for (var prop of Object.getOwnPropertyNames(Object.prototype)) {
    result[prop] = undefined;
  }
  return result;


Способы использования можно посмотреть в readme.

Yet another JS library


Статья получилась короткая, да и нечего особо рассказать об этой микропроблеме. Поэтому я до кучи решил упомянуть об еще одной библиотечке — typescript-reexport-generator. В процессе разработки на typescript я прибегал к разным способам экспортировать-импортировать код между файлами, пришел к тому, что наиболее удобным является следующее. Все .ts файлы в папке экспортируют код следующим образом:

// file1
export function myFunction(){}

// file2
export class myClass{}


Далее в папку кладется файл index.ts со следующим содержимым:

export * from './file1';
export * from './file2';


Теперь можно импортировать можно вот так:

//было
import { myFunction } from './folder/file1';
import { myClass } from './folder/file2';
//стало
import { myFunction, myClass } from './folder';


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

import { myClass } from '.';
export function myFunction(){ // doSmth with class }


Есть еще один мини-выигрыш: навигация в VSCode (ctrl + mouse click) наилучшим образом работает с таким экспортированием. Навигация от использования до имплементации в 1 клик. С default экспортом навигация осуществлялась в два клика, что несколько удручало, поэтому я от такого довольно быстро полностью отказался.

И для того, чтобы не писать эти реэкспорты руками, я написал простенький генератор, который создает эти файлы index.ts из таски с gulp.watch. Если вы используете такой же способ импортов-экспортов, библиотека может оказаться полезной.

Недостаток библиотеки, а куда же без них, это то, что VSCode не следит за изменениями файлов, поэтому только что созданный файл с экспортами не сразу позволяет импортироваться снаружи. Приходится руками зайти в index, чтобы студия «увидела», что там появилась новая строчка. Другой недостаток, который уже зависит от меня — gulp.watch не сообщает что именно изменилось, соответственно генератору приходится просматривать (и парсить) все файлы в проекте. В будущем возможно создам следующую версию библиотеки где это будет решено. Полным будет только первый проход, а далее будут парситься только те файлы, которые были изменены.

© Habrahabr.ru