Обычно, я видел опытные функции, объявленные вне определения класса, как это:
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".
Я хотел бы определить опытную функцию В определении класса. Есть ли что-то не так с выполнением чего-то вроде этого?
function Container(param) {
this.member = param;
if (!Container.prototype.stamp) {
Container.prototype.stamp = function() {
return this.member + string;
}
}
}
Я пробовал это так, чтобы я мог получить доступ к частной переменной в классе. Но я обнаружил что, если мои опытные ссылки на функцию частный var, значение частного 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
. Я считал, что закрытия быстрее.Когда имеет смысл использовать функции-прототипы вместо закрытий?
Ну, это самый легкий способ, допустим, у вас есть метод в прототипе
некоторого конструктора, и вы создаете 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]]
образует цепочку scope, и когда вы обращаетесь к идентификатору (например, к вашей переменной privateVar
), эти объекты рассматриваются.
А поскольку ваша функция была создана при первом вызове метода (new Container('A')
), privateVar
привязана к области видимости этого первого вызова функции, и она останется привязанной к ней, как бы вы ни вызывали метод.
Посмотрите этот ответ, первая часть посвящена оператору with
, а во второй части я рассказываю о том, как работает цепочка scope для функций.
Чтобы получить желаемое поведение, вам нужно назначить каждый отдельный объект отдельно stamp ()
функции с уникальными замыканиями:
Container = function(param) {
this.member = param;
var privateVar = param;
this.stamp = function(string) {
return privateVar + this.member + string;
}
}
Когда вы создаете одну функцию на прототипе, каждый объект использует одну и ту же функцию, при этом функция закрывается поверх самой первой privateVar
контейнера.
Назначая this.stamp = ...
каждый раз, когда вызывается конструктор, каждый объект получит свою собственную функцию stamp ()
. Это необходимо, поскольку каждая штамп ()
должна закрывать другую переменную privateVar
.
Вам нужно поместить функцию в каждый конкретный экземпляр вместо прототипа, например:
Container = function(param) {
this.member = param;
var privateVar = param;
this.stamp = function(string) {
return privateVar + this.member + string;
}
}
Это потому, что privateVar
не является частным членом объекта, а является частью закрытия штампа. Вы можете получить эффект, всегда создавая функцию:
Container = function(param) {
this.member = param;
var privateVar = param;
this.stamp = function(string) {
return privateVar + this.member + string;
}
}
Значение privateVar
устанавливается при построении функции, поэтому вам нужно создавать его каждый раз.
РЕДАКТИРОВАТЬ: изменено, чтобы не устанавливать прототип.