Еще один нюанс JavaScript, о котором все знают, но не все задумываются
В последнее время вышло много статей о 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 не сообщает что именно изменилось, соответственно генератору приходится просматривать (и парсить) все файлы в проекте. В будущем возможно создам следующую версию библиотеки где это будет решено. Полным будет только первый проход, а далее будут парситься только те файлы, которые были изменены.