Как реализовать метод перезагрузки счетчика вызовов функции в декораторе JavaScript
Что проверяют
Понимаете ли вы, что декоратор в широком смысле это функция-обёртка, а состояние счётчика живёт в её замыкании. Следом часто спрашивают, как сделать то же для метода класса и где тогда хранить счётчик, если экземпляров несколько.
Как отвечать
Базовая версия: функция countCalls(fn) заводит let count = 0, возвращает обёртку и вешает на неё два метода.
- обёртка:
function (...args) { count++; return fn.apply(this, args); }. Обычная функция, а не стрелка, чтобы не потерять контекст при использовании как метода; - чтение:
wrapper.getCount = () => count. Сама переменная наружу не отдаётся, и записать туда произвольное значение снаружи нельзя; - сброс:
wrapper.reset = () => { count = 0; }. Стрелки здесь работают, потому что им нужно замыкание, а неthis; - метаданные: при желании сохраните имя через
Object.defineProperty(wrapper, 'name', { value: fn.name }), чтобы стек вызовов и отладка оставались читаемыми.
Если интервьюер говорит о синтаксисе @countCalls, покажите разницу. Стандартные декораторы, которые TypeScript поддерживает с пятой версии, получают (value, context) и для метода возвращают функцию-замену. Режим experimentalDecorators работает со старой сигнатурой (target, key, descriptor), и перепутать их значит написать неработающий код.
Для метода класса важно, где лежит счётчик. Замыкание декоратора одно на класс, поэтому все экземпляры получат общий счёт. Для счёта по экземплярам храните его в WeakMap, где ключом служит this: запись исчезнет вместе с объектом.
Где спотыкаются
Делают счётчик публичным свойством wrapper.count и сбрасывают присваиванием. Это работает, но состояние открыто для записи кем угодно, и смысл инкапсуляции через замыкание пропадает.
Не прокидывают результат. Обёртка без return превращает любую функцию в возвращающую undefined, и это видно на первом же тесте.
Уверенно называют утечкой памяти само замыкание. Утечка появляется, когда обёртка держит ссылки на тяжёлые объекты или кеш без ограничения, а один числовой счётчик памяти не тянет.