Установка функции прототипа javascript в объявлении класса объекта
Обычно я видел функции прототипа, объявленные вне определения класса, например:
function Container(param) {
this.member = param;
}
Container.prototype.stamp = function (string) {
return this.member + string;
}
var container1 = new Container('A');
alert(container1.member);
alert(container1.stamp('X'));
Этот код создает два предупреждения со значениями "A" и "AX".
Я хотел бы определить функцию прототипа INSIDE определения класса. Что-то не так с этим?
function Container(param) {
this.member = param;
if (!Container.prototype.stamp) {
Container.prototype.stamp = function() {
return this.member + string;
}
}
}
Я пытался это сделать, чтобы получить доступ к частной переменной в классе. Но я обнаружил, что если моя прототипная функция ссылается на частный var, значение private var всегда является значением, которое использовалось, когда функция прототипа была ИНИЦИАЛЬНО создана, а не значение в экземпляре объекта:
Container = function(param) {
this.member = param;
var privateVar = param;
if (!Container.prototype.stamp) {
Container.prototype.stamp = function(string) {
return privateVar + this.member + string;
}
}
}
var container1 = new Container('A');
var container2 = new Container('B');
alert(container1.stamp('X'));
alert(container2.stamp('X'));
Этот код создает два предупреждения со значениями "AAX" и "ABX". Я надеялся, что выход будет "AAX" и "BBX". Мне любопытно, почему это не работает, и если есть другой шаблон, который я мог бы использовать вместо этого.
РЕДАКТИРОВАТЬ: Обратите внимание, что я полностью понимаю, что для этого простого примера было бы лучше всего использовать закрытие, подобное this.stamp = function() {}
, и вообще не использовать прототип. Вот как я это сделаю. Но я экспериментировал с использованием прототипа, чтобы узнать больше об этом и хотел бы знать несколько вещей:
- Когда имеет смысл использовать функции прототипа вместо закрытия? Мне нужно было использовать их для расширения существующих объектов, например
Date
. Я читал, что закрытия быстрее.
- Если мне по какой-то причине нужно использовать функцию прототипа, "ОК", чтобы определить ее внутри класса, как в моем примере, или он должен быть определен за пределами?
- Я хотел бы понять, почему значение privateVar для каждого экземпляра недоступно для функции прототипа, только значение первого экземпляра.
Ответы
Ответ 1
Когда имеет смысл использовать функции прототипа вместо закрытия?
Ну, это самый легкий способ, скажем, у вас есть метод в prototype
определенного конструктора, и вы создаете 1000 экземпляров объектов, все эти объекты будут иметь ваш метод в цепочке прототипов, и все они будут ссылаться только на один объект функции.
Если вы инициализируете этот метод внутри конструктора, например. (this.method = function () {};
), все ваши экземпляры объектов 1000 будут иметь объект функции как собственное свойство.
Если по какой-то причине мне нужно использовать функцию прототипа, "ОК", чтобы определить ее НАРУШИТЬ класс, как в моем примере, или он должен быть определен снаружи?
Определение элементов прототипа конструктора внутри себя, не имеет большого смысла, я объясню вам больше об этом и почему ваш код не работает.
Я хотел бы понять, почему значение privateVar для каждого экземпляра недоступно для функции прототипа, только значение первого экземпляра.
Посмотрите на свой код:
var Container = function(param) {
this.member = param;
var privateVar = param;
if (!Container.prototype.stamp) { // <-- executed on the first call only
Container.prototype.stamp = function(string) {
return privateVar + this.member + string;
}
}
}
Ключевым моментом в отношении поведения вашего кода является то, что функция Container.prototype.stamp
создается при первом вызове метода.
В настоящий момент вы создаете объект функции, он сохраняет текущую охватывающую область во внутреннем свойстве, называемом [[Scope]]
.
Эта область позже дополняется при вызове функции идентификаторами (переменными), объявленными в ней, с помощью var
или FunctionDeclaration.
Список свойств [[Scope]]
формирует цепочку областей видимости, и когда вы обращаетесь к идентификатору (например, к переменной privateVar
), эти объекты проверяются.
И так как ваша функция была создана при первом вызове метода (new Container('A')
), privateVar
привязан к Области этого первого вызова функции, и он будет привязан к нему независимо от того, как вы вызываете этот метод.
Посмотрите на этот ответ, первая часть посвящена выражению with
, но во второй части я расскажу о том, как цепочка областей действия работает для функций.
Ответ 2
Извините за восстановление старого вопроса, но я хотел добавить что-то, что я недавно обнаружил где-то еще здесь, на SO (поиск ссылки, отредактируйте/добавьте ее, как только я ее найду): нашел его.
Мне лично нравится эта методология, потому что я могу визуально группировать все определения моего прототипа и "экземпляра" вместе с определением функции, избегая при этом их оценки более одного раза. Это также дает возможность делать закрытие с помощью ваших прототипов, которые могут быть полезны для создания переменных 'private', разделяемых различными методами прототипов.
var MyObject = (function () {
// Note that this variable can be closured with the 'instance' and prototype methods below
var outerScope = function(){};
// This function will ultimately be the "constructor" for your object
function MyObject() {
var privateVariable = 1; // both of these private vars are really closures specific to each instance
var privateFunction = function(){};
this.PublicProtectedFunction = function(){ };
}
// "Static" like properties/functions, not specific to each instance but not a prototype either
MyObject.Count = 0;
// Prototype declarations
MyObject.prototype.someFunction = function () { };
MyObject.prototype.someValue = 1;
return MyObject;
})();
// note we do automatic evalution of this function, which means the 'instance' and prototype definitions
// will only be evaluated/defined once. Now, everytime we do the following, we get a new instance
// as defined by the 'function MyObject' definition inside
var test = new MyObject();
Ответ 3
Вам нужно поставить функцию на каждый конкретный экземпляр вместо прототипа, например:
Container = function(param) {
this.member = param;
var privateVar = param;
this.stamp = function(string) {
return privateVar + this.member + string;
}
}
Ответ 4
Чтобы получить нужное поведение, вам необходимо назначить каждому отдельному объекту отдельные функции stamp()
с уникальными закрытиями:
Container = function(param) {
this.member = param;
var privateVar = param;
this.stamp = function(string) {
return privateVar + this.member + string;
}
}
Когда вы создаете одиночную функцию в прототипе, каждый объект использует ту же функцию, при этом функция закрывается в первом контейнере privateVar
.
Назначая this.stamp = ...
каждый раз, когда конструктор вызывается, каждый объект получает свою собственную функцию stamp()
. Это необходимо, так как каждый stamp()
должен закрыть другую переменную privateVar
.
Ответ 5
Это потому, что privateVar
не является частным членом объекта, а частью закрытия штампа. Вы можете получить эффект, всегда создавая функцию:
Container = function(param) {
this.member = param;
var privateVar = param;
this.stamp = function(string) {
return privateVar + this.member + string;
}
}
Значение privateVar
устанавливается, когда функция построена, поэтому вам нужно создавать ее каждый раз.
EDIT: изменено не для установки прототипа.