Синглтон на javascript
Вся красота и гибкость джаваскрипта раскрывается как раз на подобных задачах: реализация функционала, который не заложен в языковые конструкции, но широко применяется в других языках программирования.
Синглтон по определению — объект, который может присутствовать в системе только в единственном числе.
Самое простое, что можно сделать — создать объект, и назвать его условно синглтоном. То есть непрограммными средствами решаем задачу.
window.singletone = {
// singletone body
};
Такой подход порождает больше вопросов и проблем, чем решает: нет никакой инкапсуляции данных, ссылку на объект необходимо явно прокидывать во все области видимости, где подразумевается его использование. Да и сама форма записи, в случае большого количества методов, сложна для чтения.
Вопрос с инкапусяцией можно решить с помощью замыкания:
var singletone = (function() {
var private;
function private_method () {}
return {
public : function () {
return private_method(private);
}
}
}());
Уже немного лучше. Данные инкапсулированы, но строить хоть какую систему наследования с подобным подходом сложно. Для упрощения процесса наследования, будем работать с функцией – конструктором. Эта функция должна возвращать ссылку на один и тот же объект. Самое простое, что можно сделать во вновь сложившейся ситуации, это сконструировать объект и всегда возвращать ссылку на него
var Singletone;
Singletone = (function () {
var instance;
instance = {};
return function () {
return instance;
}
}());
Этот код больше похож на правду: во-первых мы инкапсулировали данные, во-вторых программно ограничили количество инстансов синглтона до одного. Возможность использования конструктора как с new так и без. Код вполне рабочий, и подходящий для использования в продакшене. Но в нем еще достаточно минусов: объект конструируется в любом случае, даже если конструктор не был ни разу вызван, невозможность использования цепочки прототипов, некорректный конструктор объекта, возвращаемого Singletone.
var singletone,
another_singletone;
singletone = new Singletone();
another_singletone = Singletone();
console.log(singletone === another_singletone); // true
console.log(another_singletone.constructor === Singletone); // false
console.log(another_singletone.__proto__ === Singletone.prototype); // false
Конечно мы можем вручную установить свойство constructor, __proto__. Правда такой код потеряет в кроссбраузерности и изящности (все равно продолжит работать, что намного важнее красоты). Я же в своих изысканиях ищу максимально изящный вариант.
Судя по предъявляемым требованиям, конструктор синглтона должен создавать и запоминать объект при первом вызове, и возвращать этот объект при последующих. Вспомним, что объект функции это объект, и будем хранить в нем инстанс сконструированного объекта.
function Singletone () {
if (Singletone.instance) {
return Singletone.instance
}
Singletone.instance = this;
}
Вот она, красота, javascript. Если конструктор явно не возвращает объект, то возвращается конструируемый объект. При первом вызове new Singletone в свойство Singletone.instance запишется конструируемый объект, а при последующих будет возвращаться ссылка на него.
Этот код хорош по нескольким причинам: объект не создается, если он не был сконструирован (в отличии от предыдущих вариантов), объект можно расширять с помощью Singletone.prototype, так-же проверять instanceOf Singletone. С другой стороны, эту функцию необходимо всегда вызывать с ключевым словом new. Не то, чтобы я был противником использования new или сокрытия этого механизма от программиста, использующего мой синглтон, но создание возможности вызова конструктора как с new, так и без, позволит избежать ряда вопросов про использование синглтона. Так же в этом варианте функция выступает в качестве хранилища инстанса объекта. Такой подход имеет право на жизнь, но он слишком прямолинеен и показывает детали реализации наружу, делая код чуть уязвимее.
Возможно я бы не придирался так к коду (кстати, в вики пример реализации синглтона именно в виде, представленном выше), если не бы не знал лучшей реализации.
Мы же пойдем дальше, и создадим реализацию, которая:
- инкапсулирует данные
- не конструирует объект, если не конструктор не был вызван
- позволяет использовать все инструменты прототипного наследования (constructor, Singletone.prototype, instanceOf)
- может вызываться как с new, так и без
var Singletone = (function () {
var instance;
return function Construct_singletone () {
if (instance) {
return instance;
}
if (this && this.constructor === Construct_singletone) {
instance = this;
} else {
return new Construct_singletone();
}
}
}());
Разберем что происходит в возвращаемой функции.
Если есть instance, то возвращаем его. Если функция используется с new или без, все равно мы вернем ссылку на объект, сконструированный этой функцией.
Если функция вызвана с new, то this ссылается на объект, конструируемый этой функцией. This так же может быть определен, если функция вызвана в нестрогом режиме (this === window), или если функция вызвана как метод объекта (this === этот_объект). Второй случай аннигилируется проверкой конструктора у this.
Если this не определен (вызов в строгом), или конструктор у this не текущая функция, возвращаем результат текущей функции с new.
Имеем на выходе пуленепробиваемый конструктор синглтона, который можно где угодно хранить и как угодно вызывать. Такую красоту нельзя не прихватить в тулбокс.
Было приятно увидеть знакомого автора в в топе выдачи, спасибо за статью!
п.с. и спасибо за MMA/2014 :)
Рад быть полезным.
Благодарю, статья мне помогла разобраться с этим синглтоном:)
Что вы скажите о таком решении?
https://code.google.com/p/jslibs/wiki/JavascriptTips#Singleton_pattern
Главный минус на мой взгляд в том, что решение не будет работать в строгом режиме (речь идет именно о строгом режиме в том файле, где объявляется MySingletonClass)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/callee
В остальном подход мало чем отличается от описанного в статье подхода, когда в свойстве конструктора сохраняется ссылка на инстанс синглтона.
function S () {
if (!S.self) {
S.self = this;
}
return self;
}
$a = new S();
$b = new S();
console.log($a == $b) // true
Среди примеров в статье этот тоже рассмотрен
Здравствуйте, Дмитрий. Уже несколько дней пытаюсь понять один момент из кода.
В примерах вроде
var singletone = (function() {
var private;
function private_method () {}
return {
public : function () {
return private_method(private);
}
}
}());
зачем скобки вокруг функции?
a = function у нас ведь уже является функциональным выражением, т.е.
a = function() {}(); – корректно отрабатывает.
В чём здесь секрет?
Здравствуйте.
Технически скобки вокруг функции можно было бы и опустить. Я оставляю их ради двух вещей:
Кстати, если есть больше вопросов по javascript, буду рад ответить на них на форуме http://forum.jscourse.com.
А, вон оно что. Честно говоря, я подозревал что-то подобное. Спасибо за ответ )
че очки не носишь?
Здравствуйте,
Большое спасибо за статью!
Я новичок в JS. Подскажите, пожалуйста, как в такой реализации синглтона объявлять и использовать публичные и приватные методы/атрибуты?
Спасибо!
Вот пример где есть методы (inc, dec) и они изменяют значение (total) в самом синглтоне. Обрати внимание // initialization stage комментарий. Это место где будут создаваться свойства при первом конструировании экземпляра синглтона.