Установка функции-прототипа JavaScript в объявлении класса объекта - PullRequest
23 голосов
/ 07 мая 2010

Обычно я видел функции-прототипы, объявленные вне определения класса, например:

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;
        }
    }
}

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

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 каждого экземпляра недоступно для функции-прототипа, только значение первого экземпляра.

Ответы [ 5 ]

21 голосов
/ 07 мая 2010

Когда имеет смысл использовать функции-прототипы вместо замыканий?

Ну, это самый легкий способ, скажем, у вас есть метод в 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 привязана к Scope этого первого вызова функции, и она останется привязанной к ней независимо от того, как вы вызываете метод.

Посмотрите на этот ответ , первая часть посвящена выражению with, но во второй части я расскажу о том, как работает цепочка областей действия для функций.

12 голосов
/ 19 ноября 2010

Извините за воскрешение старого вопроса, но я хотел добавить кое-что, что недавно обнаружил где-то здесь, на SO (ищите ссылку, отредактируйте / добавьте ее, как только я ее найду) : нашел его .

Мне лично нравится приведенная ниже методология, потому что я могу визуально сгруппировать все свои определения прототипа и «экземпляра» вместе с определением функции, избегая при этом оценки их более одного раза.Это также дает возможность делать замыкания с вашими методами-прототипами, что может быть полезно для создания «закрытых» переменных, совместно используемых различными методами-прототипами.

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();
1 голос
/ 07 мая 2010

Чтобы получить желаемое поведение, вам нужно назначить каждому отдельному объекту отдельные 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.

1 голос
/ 07 мая 2010

Вам нужно поместить функцию в каждый конкретный экземпляр вместо прототипа, например:

Container = function(param) {
    this.member = param;
    var privateVar = param;

    this.stamp = function(string) {
        return privateVar + this.member + string;
    }
}
0 голосов
/ 07 мая 2010

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

Container = function(param) {
    this.member = param;
    var privateVar = param;
    this.stamp = function(string) {
      return privateVar + this.member + string;
    }
}

Значение privateVar устанавливается при построении функции, поэтому вам нужно создавать ее каждый раз.

РЕДАКТИРОВАТЬ: изменено, чтобы не устанавливать прототип.

...