Несколько лет назад разработчики JavaScript часто использовали вариации шаблонов модулей. IIFE (немедленно вызываемое выражение функции) было распространенным способом структурирования модульных частей любого приложения Angular JS / backbone / jQuery:
В настоящее время IIFE все еще заметен — вы можете найти его в неминифицированном производственном коде (например, в скриптах CDN: jQuery, AngularJS, React), TypeScript посттранспилированный код (классы, модули и пространства имен транспилируются к IIFE) и устаревший код, но он редко используется непосредственно разработчиками в новых приложениях.
В этой статье я хочу сосредоточиться на управлении конфиденциальностью в среде ES 2015+. У нас есть как минимум 5 вариантов:
- Используя область модуля ES
- Сохраняя все личные данные внутри конструктора класса
- Используя символы
- С помощью слабых карт
- Используя соглашение об именах
Используя область модуля ES
Константы и методы, которые не экспортируются, являются закрытыми. Это самый простой подход, но он имеет серьезные ограничения.
Во-первых, такой закрытый метод не знает о том, что он находится в контексте класса.
Если вы хотите, чтобы ключевое слово this было внутри сортировать по алфавиту, вам нужно изменить его. в обычную функцию, чтобы сделать указатель «this» динамическим, потому что «this» внутри стрелочной функции нельзя изменить с помощью .bind(), call() или apply().
После этого вам нужно будет привязать эту функцию к определенному классу.
Другая проблема заключается в том, что нет возможности использовать этот подход для создания частного состояния (членов) для каждого экземпляра класса — массив names будет общим для всех экземпляров класса. В примере кода это не проблема, потому что SomeService не использует массив names в качестве состояния экземпляра, но проблема серьезная.
Стоит также отметить, что сортировать по алфавиту является «приватным» для модуля, а не для класса.
Если вы создадите второй класс в том же файле, он будет иметь доступ в сортировать по алфавиту, поэтому он больше не будет закрытым методом SomeService.
Сохраняя все личные данные внутри конструктора (класса)
Этот метод также использовался в ES5, и он заключается в сохранении всего, что должно быть закрытым внутри конструктора. Это самый безопасный метод, когда речь идет о конфиденциальности, но, к сожалению, он имеет несколько серьезных недостатков:
- Вы должны переместить каждую функцию, которой нужны закрытые данные, в конструктор (методы прототипа не могут получить доступ к закрытым данным).
- Пустая трата памяти - каждая функция, помещенная в конструктор, будет создана для каждого экземпляра класса, а не совместно использоваться через прототип
Используя символы
Кажется, что это самый умный подход, но на самом деле это не так. Он устраняет некоторые ограничения по сравнению с первым подходом (возможно наличие частного состояния для каждого экземпляра), но за простоту приходится платить — плохая безопасность.
Чтобы получить личные данные, вам даже не нужно экспортировать/импортировать символ, вы можете просто получить личные данные с помощью Reflect API или статического метода Object:
// НЕДОСТАТКИ:
console.log(p[_lastName]); // Малыш
console.log(Reflect.ownKeys(p)); // ["firstName", Symbol(lastName)]
console.log(p[Reflect.ownKeys(p)[1]]); // Малыш
console.log(p[Object.getOwnPropertySymbols(p)[0]]); // Малыш
С помощью слабых карт
WeakMaps хороши для защиты личных данных, потому что у них очень ограниченный API. Не существует метода перечисления ключей или значений WeakMap и… Вы вынуждены использовать Object в качестве ключа в WeakMap:
Благодаря этому мы можем привязать контейнер конфиденциальности к экземпляру класса. Чтобы получить приватные данные из конкретного экземпляра, вам придется экспортировать && импортировать «_Registry» (контейнер приватности) и явно вызывать метод get для конкретного экземпляра класса === неудобно и легко обнаружить при просмотре кода:
// Другой файл (после экспорта _Registry)
import { _Registry } from ‘./Registry’;
const reg = new Registry('Janusz', 'Tracz');
console.log(_Registry.lastName.get(reg)); // трассировка
Используя соглашение об именах
Вы, наверное, часто видели это в коде JavaScript. Идея проста — поставить «_» в начале имени каждого закрытого члена/метода. На самом деле, я не большой поклонник этого, потому что это может привести к конфликту имен:
Тем не менее, крупнейшие игроки используют эту технику, например. : Facebook в коде Relay, Google в коде Angular. Этот метод требует командной дисциплины и некоторых знаний в области программирования. Если вы не можете себе этого позволить в своей команде, но вам нравится такой подход — вы можете попробовать babel plugin:
Как вы можете видеть справа, Babel использует WeakMaps.
Другие варианты?
Вы слышали о TypeScript? Это надмножество JavaScript, которое может помочь вам с обеспечением конфиденциальности (в него встроены все необходимые модификаторы, и вы можете настроить его на блокировку транспиляции кода при нарушении некоторых правил).
Будущее
Есть предложения JavaScript по маркировке закрытых членов/методов знаком «#»:
Насколько я знаю, знак # был выбран из-за возможных конфликтов имен/соображений производительности — все указывает на то, что в JavaScript не будет модификаторов private/public/protected и т.д. В текущем предложении частные члены не могут быть достигнуты с помощью синтаксиса квадратных скобок:
// ДЛЯ ПУБЛИЧНЫХ ЧЛЕНОВ ДОСТУПНЫ ДВА СИНТАКСИСА:
a.test(), a[‘test’]()
// ДЛЯ ЧАСТНЫХ ЧЛЕНОВ НЕ БУДЕТ синтаксиса variable[‘name’]
Это предложение сейчас находится на стадии 3, поэтому мы не увидим хэшей в дополнениях 2018 года.
Вот и все!
Надеюсь, вам понравилось, любые предложения, комментарии, мысли приветствуются :)
Если вы хотите узнать больше, пожалуйста, посетите: