Путь извращенца: Singleton на JavaScript

Понадобился нам в нашем js-сценарии, допустим, Singleton. Ну, мы берём и делаем, как все взрослые дяди:

/**
 * @class MyClass
 * @property {String} x
 * @property {String} y
 */
function MyClass() {
    this.x = "x";
    this.y = "y";
}
MyClass.prototype = {/* ... */};
 
/**
 * Get instance of singleton
 *
 * @static
 * @return {MyClass}
 */
MyClass.getInstance = function () {
    if (!this.instance) {
        this.instance = new this();
    }
    return this.instance;
};
 
var instance1 = MyClass.getInstance(),
    instance2 = MyClass.getInstance();
 
instance1 == instance2; // true

Но, ведь, JavaScript идеальный язык для того, чтобы сделать какое-нибудь извращение. Грех такой шанс упустить.

Что происходит при выполнении new MyClass()? Создаётся пустой объект, ему в прототипы назначается MyClass.prototype, в его контексте вызывается MyClass() и после всего этого он же возвращается в качестве результата выражения. Всем известные факты.

Однако, если конструктор возвращает через return любой объект, то именно он, а не this будет результатом new. Воспользуемся этим:

function MyClass() {
    var callee = arguments.callee;
    if (!callee.instance) {
        callee.instance = {
            'x': "x",
            'y': "y"
        };
    }
    return callee.instance;
}
 
var instance1 = new MyClass(),
    instance2 = new MyClass();
 
instance1 == instance2; // true

Теперь не нужно никаких getInstance(). Просто создаём объекты через new и каждый раз получаем одно и то же.

Заодно избавились от проблемы первого варианта: можно было создать объект напрямую в обход getInstance() (конструктор то в JavaScript не сделать private).

Особые параноики видят ещё одну проблему: ручная замена MyClass.instance. Но и здесь у извращенцев есть ответ:

var MyClass = (function () {
    var instance;
    return (function () {
        if (!instance) {
            instance = {
                'x': "x",
                'y': "y"
            };
        }
        return instance;
    });
}());

Как и всегда, JavaScript позволяет нам делать стандартные вещи с одной стороны намного проще, а с другой так, что никто из читающих этот код, нихрена не поймёт.

4 комментария »

  • это все прекрасно пока не возникнет желания посмотреть инстанс
    var myObj = new MyClass();
    console.log(myObj instanceof MyClass); // false

    CTAPbIu_MABP, 25.02.2013, 9:05

  • CTAPbIu_MABP, тогда чуть иначе:

    function MyClass() {
        var callee = arguments.callee;
        if (callee.instance) {
            return callee.instance;
        }
        this.x = "x";
        this.y = "y";
        callee.instance = this;
    }
    

    vasa_c, 25.02.2013, 11:20

  • Здравствуйте, arguments.callee — устаревшее свойство, используйте

    var MyClass = function() {
    if (MyClass.prototype._singletonInstance) {
    return MyClass.prototype._singletonInstance;
    }
    MyClass.prototype._singletonInstance = this;
    };

    Александр, 11.12.2013, 18:49

  • Да, Александр, спасибо.
    Но зачем прототип замусоривать?

    vasa_c, 11.12.2013, 21:57

Leave a comment