Вся красота и гибкость джаваскрипта раскрывается как раз на подобных задачах: реализация функционала, который не заложен в языковые конструкции, но широко применяется в других языках программирования.
Синглтон по определению — объект, который может присутствовать в системе только в единственном числе.
Самое простое, что можно сделать — создать объект, и назвать его условно синглтоном. То есть непрограммными средствами решаем задачу.
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.
Имеем на выходе пуленепробиваемый конструктор синглтона, который можно где угодно хранить и как угодно вызывать. Такую красоту нельзя не прихватить в тулбокс.