В недавнем проекте я обсуждал с @johnshew способ, которым разработчики JavaScript могут встраивать приватные элементы в объект. Моя техника для этого конкретного случая — использовать то, что я называю «пробелом».
Но прежде чем углубиться в это, позвольте мне представить вам, почему вам может понадобиться частный член, а также другой способ «симулировать» частного участника.
Не стесняйтесь пинговать меня в твиттере, если вы хотите обсудить эту статью: @deltakosh
Зачем использовать частные члены
Когда вы создаете объект с помощью 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 функция имеет CurrentValue переменную, получить и установить функции могут видеть. Эта переменная будет сохранена в закрытом пространстве функций 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></title> </head> <style> html { font-family: "Helvetica Neue", Helvetica; } </style> <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>
Я создаю 1 миллион объектов, все из которых принадлежат участнику. Затем я делаю три теста:
- Сделайте 1 миллион случайных доступов к собственности
- Сделайте 1 миллион случайных обращений к версии «закрытого пространства»
- Сделайте 1 миллион случайных обращений к обычной версии get / set
Вот таблица и диаграмма с результатом:
Мы можем заметить, что версия для закрытого пространства всегда быстрее, чем обычная версия, и в зависимости от браузера это может быть действительно впечатляющей оптимизацией .
Производительность Chrome кажется действительно странной. Там может быть ошибка. Чтобы быть уверенным, я связался с командой Google, чтобы выяснить, что здесь происходит
Однако если мы посмотрим внимательнее, то обнаружим, что использование пробела или даже свойства может быть в десять раз медленнее, чем прямой доступ к члену . Так что будьте осторожны и используйте это с умом.
След памяти
Мы также должны проверить, не использует ли этот метод слишком много памяти. Для оценки памяти я написал эти три небольших фрагмента кода:
Код ссылки
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 < sampleSize; index++) { var entity = {}; var currentValue = "hello world (" + index + ")"; createProperty(entity, "property", currentValue); entities.push(entity); }
Затем я запустил все эти три кода и запустил встроенный профилировщик памяти (пример здесь с использованием инструментов F12):
Вот результаты, которые я получил на своем компьютере:
Между закрывающим пространством и обычным способом только Chrome имеет лучшие результаты для закрывающего пространства. IE и Firefox используют немного больше памяти.
Вывод
Как видите, свойства пространства закрытия могут быть отличным способом создания действительно приватных данных . Возможно, вам придется иметь дело с небольшим увеличением потребления памяти, но, с моей точки зрения, это вполне разумно (и при такой цене вы можете значительно повысить производительность по сравнению с обычным способом).
И, кстати, если вы хотите попробовать это самостоятельно, пожалуйста, найдите весь код, используемый здесь .