Pull to refresh

Нужны ли «приватные» свойства объектов в Javascript?

Reading time4 min
Views4.7K
В последнее время во многих статьях (на Хабре и не только) я часто вижу примеры эмуляции приватных свойств объектов в JS через замыкания. Авторы обычно объясняют это своим желанием использовать такой механизм ООП, как инкапсуляция, и тем самым гарантировать работу с объектом исключительно посредством его методов, не затрагивая напрямую свойства.

В этой статье я предлагаю объективно рассмотреть достоинства и недостатки такого подхода, чтобы каждый мог для себя решить, стоит ли его использовать или нет.


Итак, для начала, предмет обсуждения. Мы будем сравнивать создание объектов с помощью связки «конструктор — прототип» (т.е. все методы хранятся в прототипе, все свойства — создаются конструктором)
function make_obj(a, b) {
 this.prop1 = a;
 this.prop2 = b;
}
make_obj.prototype = {
 method1: function(){...},
 method2: function(){...},
 methodN: ...
}


* This source code was highlighted with Source Code Highlighter.

и с эмуляцией приватных свойств (все свойства объекта являются переменными функции-конструктора, все методы, работающие с этими свойствами создаются прямо в конструкторе, чтобы сработало замыкание).
function make_obj(a, b) {
 var prop1 = a;
 var prop2 = b;

 method1 = function(){...}
 method2 = function(){...}
 methodN = ...
}

* This source code was highlighted with Source Code Highlighter.


Далее в статье я буду называть их соответственно «приватный» и «прототипный», для краткости.

Основным достоинством (и главным основанием для применения) приватного способа является «жесткая» инкапсуляция объектов. Жесткая в том смысле, что ограничивает доступ к свойствам напрямую не только идеологически, на уровне рекомендаций, но и фактически.

Также к плюсам можно отнести экономию на слове this при доступе к свойствам в методах (property = 5 вместо this.property = 5, например).

Недостатки


Их можно условно разделить на несколько групп.

Идеология
  1. Часто можно услышать заявление, что если в языке программирования нет таких вещей, как абстракция, полиморфизмом, наследованием и инкапсуляция, то он и не ООП-язык вовсе. При этом многие забывают, что наличие этих концепций — всего лишь особенности реализации ООП в определенных языках, а вовсе не догма.
  2. Языки JavaScript, JScript и основанный на них стандарт ECMA-262 не дают никаких «родных», нативных средств для скрытия свойств или методов объекта от обращения извне. Все свойства объектов, создаваемых в рамках программы — доступны для чтения и изменения. Отсюда напрямую следует, что любая реализация «приватных» свойств — надстройка над языком, влекущая дополнительные накладные расходы на исполнения кода и на его поддержку. Подробнее об этом — в следующих пунктах.

Производительность
Приватные свойства во всех отношениях замедляют работу скрипта, в сравнении с обычными свойствами. Объекты с приватными свойствами (или методами) дольше создаются и занимают больше места в памяти (т.к. при каждом создании объекта заново происходит создание его методов и каждая копия метода занимает свое место в памяти). Я провел простой тест, создавая 1000 объектов, имеющих 15 методов, сначала «приватным» способом, а затем «прототипным». В первом случае IE6 потратил 250мс на выполнение задачи, IE7 — 110мс. Во втором случае и тот и другой потратили по 15мс.

Разница в 100-200мс может показаться несущественной, однако в момент отображение анимации на странице или просто во время какого-либо действия пользователя она уже воспринимается весьма негативно (т.к. «подвисает» вся страница, а если браузер не использует разделение ресурсов по вкладкам, то и весь браузер).

Кроме того, приложение имеет свойство со временем усложняться, объекты обрастают новыми методами. Решив расширить наши объекты еще 3-4 методами, при приватном способе мы получим еще + 25-50мс лишнего времени, которое будет потрачено на их создание.

В случае с прототипами мы можем добавить хоть 50 новых методов — на время создания объектов это не повлияет вообще (как и на расход памяти).

Функциональность
  1. Хотя с помощью «приватного» подхода можно эмулировать атрибут private для свойств и методов, задать им атрибут protected в его классическом виде (т.е. недоступность извне, но доступность для потомков) не получится.
    Это сильно ограничивает возможности наследования, т.к. дочерний объект не может ни получить доступ к свойствам родительского напрямую, ни переопределить его публичные методы (аналогично будет потеряна связь со свойствами).
  2. Невозможно получить доступ к свойству объекта с помощью строкового литерала, например вот так this[(a > 2? 'max': 'min')]
    Казалось бы мелочь, но неприятно.

Поддержка и изменение кода
  1. Объекты с приватными свойствами сложнее в отладке. Для того, чтобы просмотреть внутреннее состояние такого объекта, нужно либо перебирать все его геттеры, либо создавать в его конструкторе специальную дамп-функцию (причем ее придется именно копипастить, чтобы она имела доступ к свойствам).
    Надо ли говорить, что в случае с обычным объектов вопрос отображения дампа не стоит вообще.
  2. В языке типа PHP5 мы можем, в процессе разработки, запросто сменить private свойство на public, или public на protected — и уровень доступа к свойству или методу тут же изменится.
    При эмулировании приватных свойств в JS, нам придется везде в коде добавлять или убирать this. перед обращением к свойству.


Поймите меня правильно, я не против использования замыканий, аксессоров или инкапсуляции, я против использования для этого кривых, неполноценных способов, которые в любом случае не решают поставленную задачу на 100%, но лишь усложняют код и делают его тяжелее для интерпретатора.

Всех вышеперечисленных проблем можно избежать, используя для обозначения «приватных» свойств знак подчеркивания (или что-то типа того), если уж так хочется разграничить доступ к ним идеологически.
Также стоит задуматься о том, что когда кто-то (или вы сами) очень хотите получить доступ к свойству напрямую, то это скорее говорит о том, что либо его аксессоры не удобны для использования, либо о том, что они ему и вовсе не нужны (например, нет нужды валидировать входное значение при записи свойства или совершать какие-то другие дополнительные действия).
Tags:
Hubs:
+30
Comments193

Articles