Эта статья является частью серии технологий веб-разработки от Microsoft. Спасибо за поддержку партнеров, которые делают возможным использование SitePoint.
Недавно я разработал Angular Cloud Data Connector , который позволяет разработчикам Angular использовать облачные данные, в частности мобильную службу Azure , с использованием веб-стандартов, таких как индексированная БД. Я пытался создать способ для разработчиков JavaScript для встраивания частных членов в объект. Моя техника для этого конкретного случая — использовать то, что я называю «пробелом». В этом уроке я хочу поделиться с вами, как использовать это для ваших собственных проектов и как производительность и память влияют на основные браузеры.
Но прежде чем углубляться в это, позвольте мне рассказать, почему вам могут понадобиться частные участники, а также альтернативный способ «симулировать» частных участников.
Не стесняйтесь пинговать меня в твиттере, если вы хотите обсудить эту статью.
Зачем использовать частные члены
Когда вы создаете объект с помощью JavaScript, вы можете определить значения членов. Если вы хотите контролировать доступ для чтения / записи на них, вам нужны средства доступа, которые можно определить следующим образом:
var entity = {}; entity._property = 'hello world'; Object.defineProperty(entity, 'property', { get: function () { return this._property; }, set: function (value) { this._property = value; }, enumerable: true, configurable: true });
Делая это, вы имеете полный контроль над операциями чтения и записи. Проблема в том, что член _property все еще доступен и может быть изменен напрямую.
Именно поэтому вам нужен более надежный способ определения закрытых членов, к которым могут обращаться только функции объекта.
Использование пробела
Решение состоит в том, чтобы использовать пространство закрытия. Это пространство памяти создается для вас браузером каждый раз, когда внутренняя функция имеет доступ к переменным из области видимости внешней функции. Иногда это может быть сложно, но для нашей темы это идеальное решение.
Итак, давайте изменим предыдущий код, чтобы использовать эту функцию:
var createProperty = function (obj, prop, currentValue) { Object.defineProperty(obj, prop, { get: function () { return currentValue; }, set: function (value) { currentValue = value; }, enumerable: true, configurable: true }); } var entity = {}; var myVar = 'hello world'; createProperty(entity, 'property', myVar);
В этом примере функция createProperty
имеет переменную createProperty
которую могут видеть функции get
и set
. Эта переменная будет сохранена в закрывающем пространстве функций get
и set
. Только эти две функции теперь могут видеть и обновлять переменную currentValue
! Миссия выполнена!
Единственное предупреждение, которое мы имеем здесь, это то, что исходное значение ( myVar
) все еще доступно. Итак, вот еще одна версия для еще более надежной защиты:
var createProperty = function (obj, prop) { var currentValue = obj[prop]; Object.defineProperty(obj, prop, { get: function () { return currentValue; }, set: function (value) { currentValue = value; }, enumerable: true, configurable: true }); } var entity = { property: 'hello world' }; createProperty(entity, 'property');
При использовании этого метода уничтожается даже исходное значение. Итак, миссия полностью выполнена!
Вопросы производительности
Давайте теперь посмотрим на производительность.
Очевидно, что закрывающие пространства или даже свойства медленнее и дороже, чем простая переменная. Вот почему эта статья больше фокусируется на разнице между обычным способом и техникой замкнутого пространства.
Чтобы подтвердить, что подход к закрытию пространства не слишком дорогой по сравнению со стандартным способом, я написал этот небольшой тест:
<!DOCTYPE html> <html xmlns='http://www.w3.org/1999/xhtml'> <head> <title>Benchmark</title> <style> html { font-family: 'Helvetica Neue', Helvetica; } </style> </head> <body> <div id='results'>Computing...</div> <script> var results = document.getElementById('results'); var sampleSize = 1000000; var opCounts = 1000000; var entities = []; setTimeout(function () { // Creating entities for (var index = 0; index < sampleSize; index++) { entities.push({ property: 'hello world (' + index + ')' }); } // Random reads var start = new Date().getTime(); for (index = 0; index < opCounts; index++) { var position = Math.floor(Math.random() * entities.length); var temp = entities[position].property; } var end = new Date().getTime(); results.innerHTML = '<strong>Results:</strong><br>Using member access: <strong>' + (end - start) + '</strong> ms'; }, 0); setTimeout(function () { // Closure space var createProperty = function (obj, prop, currentValue) { Object.defineProperty(obj, prop, { get: function () { return currentValue; }, set: function (value) { currentValue = value; }, enumerable: true, configurable: true }); } // Adding property and using closure space to save private value for (var index = 0; index < sampleSize; index++) { var entity = entities[index]; var currentValue = entity.property; createProperty(entity, 'property', currentValue); } // Random reads var start = new Date().getTime(); for (index = 0; index < opCounts; index++) { var position = Math.floor(Math.random() * entities.length); var temp = entities[position].property; } var end = new Date().getTime(); results.innerHTML += '<br>Using closure space: <strong>' + (end - start) + '</strong> ms'; }, 0); setTimeout(function () { // Using local member // Adding property and using local member to save private value for (var index = 0; index < sampleSize; index++) { var entity = entities[index]; entity._property = entity.property; Object.defineProperty(entity, 'property', { get: function () { return this._property; }, set: function (value) { this._property = value; }, enumerable: true, configurable: true }); } // Random reads var start = new Date().getTime(); for (index = 0; index < opCounts; index++) { var position = Math.floor(Math.random() * entities.length); var temp = entities[position].property; } var end = new Date().getTime(); results.innerHTML += '<br>Using local member: <strong>' + (end - start) + '</strong> ms'; }, 0); </script> </body> </html>
Я создаю один миллион объектов, все с членом свойства. Затем я делаю три теста:
-
Один миллион случайных доступов к собственности
-
Один миллион случайных обращений к версии «закрытого пространства»
-
Один миллион случайных обращений к обычной версии get / set
Вот таблица и диаграмма, детализирующая результат:
Мы видим, что версия для закрытого пространства всегда быстрее, чем обычная, и в зависимости от браузера это может быть действительно впечатляющей оптимизацией.
Производительность Chrome хуже, чем я ожидал. Там может быть ошибка, поэтому, чтобы быть уверенным, я связался с командой Google, чтобы выяснить, что происходит. Если вы хотите проверить, как это работает в Project Spartan — новом браузере Microsoft, который будет поставляться по умолчанию с Windows 10 — вы можете скачать его здесь .
Использование пробела или даже свойства может быть в десять раз медленнее, чем прямой доступ к члену. Так что будьте осторожны и используйте это с умом.
След памяти
Мы также должны проверить, не использует ли этот метод слишком много памяти. Для оценки памяти я написал эти три небольших фрагмента кода:
Код ссылки
var sampleSize = 1000000; var entities = []; // Creating entities for (var index = 0; index < sampleSize; index++) { entities.push({ property: 'hello world (' + index + ')' }); }
Обычный путь
var sampleSize = 1000000; var entities = []; // Adding property and using local member to save private value for (var index = 0; index < sampleSize; index++) { var entity = {}; entity._property = 'hello world (' + index + ')'; Object.defineProperty(entity, 'property', { get: function () { return this._property; }, set: function (value) { this._property = value; }, enumerable: true, configurable: true }); entities.push(entity); }
Закрытие Космическая версия
var sampleSize = 1000000; var entities = []; var createProperty = function (obj, prop, currentValue) { Object.defineProperty(obj, prop, { get: function () { return currentValue; }, set: function (value) { currentValue = value; }, enumerable: true, configurable: true }); } // Adding property and using closure space to save private value for (var index = 0; index &amp;lt; sampleSize; index++) { var entity = {}; var currentValue = 'hello world (' + index + ')'; createProperty(entity, 'property', currentValue); entities.push(entity); }
Затем я запустил все три примера кода и запустил встроенный профилировщик памяти (пример здесь с использованием инструментов F12):
Вот результаты, которые я получил на своем компьютере:
Сравнивая пространство закрытия и обычный способ, только Chrome имеет несколько лучшие результаты для версии пространства закрытия. IE11 и Firefox используют немного больше памяти, но браузеры похожи — пользователи, вероятно, не заметят разницы между современными браузерами.
Больше практического опыта с JavaScript
Это может вас удивить, но у Microsoft есть куча бесплатных уроков по многим темам с открытым исходным кодом JavaScript, и мы планируем создать гораздо больше с приходом Project Spartan . Проверьте мои собственные:
Или серия обучения нашей команды:
-
Практические советы по повышению производительности для ускорения работы HTML / JavaScript (серия из семи частей: от адаптивного дизайна до казуальных игр и оптимизации производительности)
-
Современная веб-платформа JumpStart (основы HTML, CSS и JS)
-
Разработка универсального приложения для Windows с использованием HTML и JavaScript JumpStart (используйте JS, который вы уже создали, для создания приложения)
И некоторые бесплатные инструменты: сообщество Visual Studio , пробная версия Azure и инструменты кросс-браузерного тестирования для Mac, Linux или Windows.
Вывод
Как видите, свойства пространства закрытия могут быть отличным способом создания действительно приватных данных. Возможно, вам придется столкнуться с небольшим увеличением потребления памяти, но, с моей точки зрения, это вполне разумно (и при такой цене вы можете значительно повысить производительность по сравнению с обычным способом).
И, кстати, если вы хотите попробовать это сами, пожалуйста, найдите весь код, используемый здесь . Здесь есть полезные инструкции по использованию мобильных служб Azure.
Эта статья является частью серии технологий веб-разработки от Microsoft. Мы рады поделиться с вами Project Spartan и его новым механизмом рендеринга . Получите бесплатные виртуальные машины или проведите удаленное тестирование на устройстве Mac, iOS, Android или Windows на сайте modern.IE .