Методы определения объектов 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();
Поля полей в конструкторе полезны, чтобы не изменять их дочерними объектами.
Настройка методов для прототипа выполняется быстрее, чем каждый раз в конструкторе.