Методы определения объектов Javascript, плюсы и минусы

Каковы основные способы определения объектов многократного использования в Javascript? Я говорю, что можно использовать повторно, чтобы исключить методы singleton, такие как объявление переменной с литературой объектного литерала напрямую. Я где-то видел, что Крокфорд определяет четыре таких способа в своих книгах, но я бы предпочел не покупать книгу для этой короткой информации.

Вот как я знаком с:

  • Используя this, и построим с помощью new (я думаю, это называется классическим?)

    function Foo() {
        var private = 3;
        this.add = function(bar) { return private + bar; }
    }
    
    var myFoo = new Foo();
    
  • Использование прототипов, похожих на

    function Foo() {
        var private = 3;
    }
    Foo.prototype.add = function(bar) { /* can't access private, correct? */ }
    
  • Возврат литерала, не используя this или new

    function Foo() {
        var private = 3;
        var add = function(bar) { return private + bar; }
        return {
            add: add
        };
    }
    
    var myFoo = Foo();
    

Я могу относиться к относительно небольшим вариациям на них, которые, вероятно, не имеют никакого значения каким-либо значительным образом. Какие стили мне не хватает? Что еще более важно, каковы плюсы и минусы каждого из них? Есть ли рекомендация придерживаться, или это вопрос предпочтения и священная война?

Ответы

Ответ 1

Используйте прототип. Возвращение определенных объектов из конструкторов делает их неконструкторами, а назначение методов this делает наследование менее удобным.

Возврат литерала объекта

Плюсы:

  • Если человек забывает new, он все равно получает объект.

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

Минусы:

  • Это не настоящий конструктор. Добавление чего-то к его прототипу не приведет к изменению возвращаемых объектов, new или no new. new Foo() instanceof Foo также приведет к false.

Использование prototype

Плюсы:

  • Вы оставляете конструктор незагроможденным.

  • Это стандартный способ делать вещи в JavaScript, и все встроенные конструкторы помещают свои методы в свои прототипы.

  • Наследование становится проще и правильнее; вы можете (и должны) использовать Object.create(ParentConstructor.prototype) вместо new ParentConstructor(), а затем вызвать ParentConstructor из Constructor. Если вы хотите переопределить метод, вы можете сделать это на прототипе.

  • Вы можете "изменить" объекты после того, как они уже были созданы.

  • Вы можете расширить прототипы конструкторов, к которым у вас нет доступа.

Минусы:

  • Это может быть слишком многословным, и если вы хотите изменить имя функции, вы должны также изменить все функции, добавленные в прототип. (Либо это, либо определить прототип как один большой объектный литерал с совместимым дескриптором свойств для Constructor.)

  • Они не разделяют область, специфичную для экземпляра, поэтому у вас действительно нет частных переменных.

Назначение this.* в конструкторе

Плюсы:

  • Вы можете использовать закрытие и, следовательно, частные переменные-члены.

Минусы:

  • Нет утиной печати; вы не можете вызывать метод прямо с прототипа с любым старым объектом. Например, Array.prototype.slice.call(collectionLikeObject).

Ответ 2

Это в основном вопрос предпочтения. Нет никакого способа сделать суп из куриного лапши, а равномерные предметы - одинаковые.

Я не использую ни одного из этих 3, хотя они все работают в своих целях. Я использую настраиваемую функцию Object: deploy и использую ее следующим образом.

var a = { hey: 'hello' },
    b = {}.deploy(a);

console.log(b.hey); // 'hello'

Использование prototype является лучшим для большинства людей из-за автоматического обтекания.

function A() {};
A.prototype.hello = "Hey";

var a = new A();
console.log(a.hello); // 'Hey'

A.prototype.hello = "Hello";

console.log(a.hello); // 'Hello'

И вопреки распространенному мнению, вы можете использовать частные переменные в prototype.

function Hello() {};

(function () {
    var greeting = "Hello";

    Hello.prototype.greet = function () { return greeting };
}).apply(this);

Но даже если это возможно, обычно лучше делать.

function Hello() {};
Hello.prototype.greeting = "Hello";
Hello.prototype.greet = function () { return this.greeting };

Ответ 3

Ну, второй подход (прототип) больше похож на стандартные классы на других языках, таких как Python, поскольку у вас есть общий объект "prototype", который все экземпляры используют. Для сравнения первого и второго подходов:

  • В подходе 1 каждый раз, когда вы вызываете "new Foo()", вы создаете совершенно новый объект и вставляете все методы. Это не очень эффективное время или пространство, так как каждый экземпляр Foo будет иметь собственную таблицу всех методов. Вы можете проверить это, создав два объекта Foo и запросив foo1.add == foo2.add (false). Подход 3 очень похож на этот; Я не уверен, что семантическая разница (если есть) между подходами 1 и 3.
  • В подходе 2 вы создали общий прототипный объект, содержащий все методы. Если вы спросите foo1.add == foo2.add, вы получите правду. Это больше пространства и времени. Он также позволяет добавлять дополнительные методы к прототипу после создания экземпляров, и они будут видеть новые методы.

Проблема с подходом 2, как вы говорите, заключается в том, что вы не можете получить доступ к закрытым членам. Но вы можете добавить нечастных членов к самому объекту и получить доступ к тем, которые используют методы прототипа:

function Foo() {
    this.private = 3;
}
Foo.prototype.add = function(bar) { return this.private + bar }

Предостережение заключается в том, что foo.private видимо внешне.

Ответ 4

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

Ответ 5

Не забывайте о наследовании. Когда-нибудь вам понадобится. А для организации наследования наилучшим подходом является комбинированная техника:

function Foo() {
    this.array = [1, 2, 3];
    this.add = function(bar) { return private + bar; }
}

Foo.prototype.add = function(bar) {  }

var myFoo = new Foo();

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