JavaScript избегает нового ключевого слова

Я читал эту страницу (в частности, раздел о фабриках).

В нем упоминается о том, что нельзя использовать ключевое слово new, чтобы случайно не забыть его. Он предлагает использовать фабрики.

Новый пример страницы:

function Bar() {
    var value = 1;
    return {
        method: function() {
            return value;
        }
    }
}
Bar.prototype = {
    foo: function() {}
};

new Bar(); 
Bar(); // These are the same.

Пример фабрики страницы:

function Foo() {
    var obj = {};
    obj.value = 'blub';

    var private = 2;
    obj.someMethod = function(value) {
        this.value = value;
    }

    obj.getPrivate = function() {
        return private;
    }
    return obj;
}

Заводские минусы:

  • Он использует больше памяти, поскольку созданные объекты не используют методы прототипа.
  • Чтобы наследоваться, фабрика должна скопировать все методы из другого объекта или поместить этот объект в прототип нового объекта.
  • Отбрасывание цепочки прототипов только из-за пропущенного нового ключевого слова противоречит духу языка.

Избегание new для предотвращения проблем в случае, если вы забудете, понятно. Но чего я не совсем понимаю, так это того, что они говорят, что фабричный пример занимает больше памяти, поскольку он не использует функции прототипа. Так почему бы не использовать что-то подобное вместо этого?

Мое решение:

var Foo = function () {
    var foo = function () {

    };
    foo.prototype = {
        bar: function () { }
    };
    return new foo();
};

Вопрос. Я упустил что-то, что делает это решение не лучшим? Убирает ли мое решение перечисленные минусы фабричного метода, почему бы и нет?


person Shelby115    schedule 17.07.2014    source источник
comment
Да, это создает новый конструктор каждый раз, когда вы вызываете фабричный метод.   -  person soktinpk    schedule 18.07.2014
comment
Я немного уточнил свой вопрос. Я знал, что каждый раз мой создавал новый объект, мне просто было любопытно, удаляются ли минусы фабричного метода, которые были перечислены на связанном сайте (который я сейчас включил в свой вопрос). Или добавил какие-либо плюсы/минусы в этом отношении.   -  person Shelby115    schedule 18.07.2014
comment
Не верьте им! Как показывают все эти фрагменты кода, есть вещи похуже, которые могут пойти не так. Есть лучшие способы противостоять забытым new.   -  person Bergi    schedule 18.07.2014


Ответы (1)


Хорошо, возьмем пример new:

function Bar() {
    var value = 1;
    // Whoops, sorry
    // As Bergi points out, if you have a return value then that overrides the normal object that is returned by new
    // Didn't even notice you have a return in here!
    /*
    return {
        method: function() {
            return value;
        }
    }
    */
    // This does the same thing (approximately) but now calling "(new Bar()) instanceof Bar" will be true
    this.method = function() {
        return value;
    };
}
Bar.prototype = {
    foo: function() {}
};

var b = new Bar();

В консоли Google Chrome у b есть свойство __proto__. По сути, когда вы вызываете b.foo(), сначала браузер ищет метод с именем foo. Если не находит, то ищет в b.__proto__ и b.__proto__.__proto__ и так далее.

Примечание: b.__proto__ === Bar.prototype

Это означает, что если вы будете вызывать new Bar() несколько раз, все они будут иметь один и тот же __proto__, что экономит память. (Это имеет побочный эффект: если вы мутируете Bar.prototype, это также изменит все экземпляры __proto__ Bar).

Давайте посмотрим на ваш заводской метод:

var Foo = function () {
    var foo = function () {

    };
    foo.prototype = {
        bar: function () { }
    };
    return new foo();
};

Это не экономит память, поскольку создает новый prototype и новый конструктор каждый раз, когда вы вызываете Foo(). Другими словами, если

var f1 = new Foo(), f2 = new Foo();

Следующие возвращают false: f1.constructor == f2.constructor и f1.__proto__ == f2.__proto__. Что это значит? Это означает, что f1 и f2 не используют один и тот же prototype, поэтому объекты должны каждый раз дублироваться. Возможно, вы могли бы сделать это:

var fooProto = {
  callFoo: function() { alert("test"); }
};
function Foo() {
    var foo = function() {};
    foo.prototype = fooProto;
    return new foo();
};

Это будет использовать тот же объем памяти, что и обычный конструктор.

Боковое редактирование: современные браузеры имеют встроенную функцию, которая делает что-то вроде Foo в последнем примере. Вы можете использовать Object.create(fooProto) (но только в новых браузерах).

Также обратите внимание, что __proto__ технически является скрытым свойством, доступным только для чтения (хотя некоторые браузеры позволяют вам писать в него). Он использовался только для того, чтобы показать, что происходит за кулисами, и его не следует использовать в реальном коде.

person soktinpk    schedule 17.07.2014
comment
1. Решит ли это перемещение foo и foo.prototype вне функции Foo? 2. Спасибо за объяснение о __proto__ было полезно. - person Shelby115; 18.07.2014
comment
b.__proto__ === Bar.prototype – нет? Конструктор return является объектом, прототип будет проигнорирован. - person Bergi; 18.07.2014
comment
Вы просто должны всегда использовать Object.create для ясности. И, при необходимости, подмените его в старых браузерах. - person Bergi; 18.07.2014
comment
@Bergi (комментарий 1) Я говорю о первом примере, где вы используете var b = new Bar();. Затем b.__proto__ === Bar.prototype - person soktinpk; 18.07.2014
comment
Да я тоже так делаю. function Bar(){ return {}; } Object.getPrototypeOf(new Bar) !== Bar.prototype. Попытайся! - person Bergi; 18.07.2014
comment
@ Shelby115: Да, это поможет. На самом деле даже не так уж редко встречается function makeFoo() { return new Foo(); }, который, в отличие от Foo, можно использовать без new. Обратите внимание, что имена конструкторов всегда должны быть написаны с заглавной буквы. - person Bergi; 18.07.2014