Установка функции прототипа 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: изменено не для установки прототипа.