Подтвердить что ты не робот

Частные переменные в унаследованных прототипах

Я думаю, что неправильно понял, как работает прототипное наследование Javascript. В частности, внутренние переменные прототипов, по-видимому, разделяются между несколькими различными под-объектами. Проще всего проиллюстрировать код:

var A = function()
{
  var internal = 0;
  this.increment = function()
  {
    return ++internal;
  };
};

var B = function() {};
// inherit from A
B.prototype = new A;

x = new B;
y = new B;

$('#hello').text(x.increment() + " - " + y.increment());​

Это выводит 1 - 2 (проверьте его на JSBin), в то время как я полностью ожидал, что результат будет 1 - 1, так как я хотел два отдельных объекта.

Как я могу убедиться, что объект A не является общим объектом между несколькими экземплярами B?

Обновление: В этой статье освещаются некоторые проблемы:

Проблема заключается в том, что область применения каждого подхода для создания частной переменной, которая отлично работает, также является закрытием в действии, результатом чего является изменение частной переменной для одного экземпляра объекта, она изменяется для всех, То есть его больше похоже на частное статическое свойство, чем фактическая личная переменная.

Итак, если вы хотите иметь что-то личное, больше как непубличную константу, любой из вышеперечисленных подходов хорош, но не для реальных частных переменных. Частные переменные работают очень хорошо с объектами singleton в JavaScript.

Решение. В соответствии с ответом BGerrissen изменение объявления B и уход прототипа работают по назначению:

var B = function() { A.apply(this, arguments); };
4b9b3361

Ответ 1

Частные члены сложно использовать прототипное наследование. Во-первых, они не могут быть унаследованы. Вы должны создавать частные члены в каждом отдельном конструкторе. Вы можете сделать это, применив суперструктор в подклассе или создав декоратор.

Пример декоратора:

function internalDecorator(obj){
    var internal = 0;
    obj.increment = function(){
        return ++internal;
    }
} 

var A = function(){
    internalDecorator(this);
}
A.prototype = {public:function(){/*etc*/}}

var B = function(){
    internalDecorator(this);
}
B.prototype = new A(); // inherits 'public' but ALSO redundant private member code.

var a = new B(); // has it own private members
var b = new B(); // has it own private members

Это просто вариация вызова супер-конструктора, вы также можете добиться того же путем вызова фактического супер-конструктора с помощью .apply()

var B = function(){
    A.apply(this, arguments);
}

Теперь, применяя наследование через B.prototype = new A(), вы вызываете ненужный код конструктора из A. Способ избежать этого - использовать метод Douglas Crockfords beget:

Object.beget = function(obj){
    var fn = function(){}
    fn.prototype = obj;
    return new fn(); // now only its prototype is cloned.
}

Что вы используете следующим образом:

B.prototype = Object.beget(A.prototype);

Конечно, вы можете вообще отказаться от наследования и эффективно использовать декораторы, по крайней мере, там, где необходимы частные члены.

Ответ 2

Вам нужно забыть идею классов. В JavaScript не существует такого понятия, как "экземпляр B". Существует только "некоторый объект, который вы получили, вызвав функцию конструктора B". Объект имеет свойства. Некоторые из них являются его собственными свойствами, другие - поиском цепи прототипов.

Когда вы говорите new A, вы создаете один объект. Затем вы назначаете его как прототип для B, а это означает, что каждый вызов new B создает новый объект с таким же прямым прототипом и, следовательно, с той же переменной счетчика.

В ответе Тима Дауна показаны две альтернативы. Его incrementPublic использует наследование, но делает переменную счетчика общедоступной (т.е. Дает инкапсуляцию). Принимая во внимание, что incrementInternal делает счетчик закрытым, но успешно, перемещая код в B (т.е. Дает наследование).

Что вы хотите, это комбинация из трех вещей:

  • наследуемое поведение - поэтому он должен быть определен в и не требует кода в B, кроме установки прототипа
  • личные данные, хранящиеся в локальных переменных замыкания
  • данные для каждого экземпляра, хранящиеся в this.

Проблема заключается в противоречии между двумя последними. Я бы также сказал, что наследование в JS имеет ограниченную ценность. Лучше рассматривать его как функциональный язык:

// higher-order function, returns another function with counter state
var makeCounter = function() {
  var c = 0;
  return function() { return ++c; };
};

// make an object with an 'increment' method:
var incrementable = {
  increment: makeCounter()
};

Лично я стараюсь избегать конструкторских функций и наследования прототипов большую часть времени. Они гораздо менее полезны в JS, чем предполагают люди из OO.

Обновление. Я бы с осторожностью относился к заявлению, которое вы указали в своем обновлении:

Частные переменные работают действительно хорошо с одноэлементными объектами в JavaScript.

Это не так. Объект - это просто "словарь" свойств, любой из которых может быть функциями. Поэтому забудьте объекты и подумайте о функциях. Вы можете создать несколько экземпляров функции в соответствии с некоторым шаблоном, написав функцию, которая возвращает функцию. Пример makeCounter - это просто простой пример этого. makeCounter не является "одноэлементным объектом" и не должен ограничиваться использованием в одноэлементных объектах.

Ответ 3

Точка прототипа заключается в том, что она разделяется между несколькими объектами (т.е. теми, которые создаются одной и той же конструкторской функцией). Если вам нужны переменные, которые не разделяются между объектами, которые используют прототип, вам нужно сохранить эти переменные в функции конструктора для каждого объекта. Просто используйте прототип для совместного использования методов.

В вашем примере вы не можете делать то, что хотите, используя прототипы. Вот что вы могли бы сделать вместо этого. См. Ответ Дэниела Эрвикера для более подробного объяснения того, что здесь нет смысла реплицировать здесь.

var A = function() {};

A.prototype.incrementPublic = function()
{
    return ++this.publicProperty;
};

var B = function()
{
    this.publicProperty = 0;
    var internal = 0;
    this.incrementInternal = function()
    {
      return ++internal;
    };
};

B.prototype = new A();

var x = new B(), y = new B();
console.log(x.incrementPublic(), y.incrementPublic()); // 1, 1
console.log(x.incrementInternal(), y.incrementInternal()); // 1, 1

Ответ 4

Я просто нашел другое сложное решение, не экспортируя никакие методы/переменные в общедоступные объекты.

function A(inherit) {
    var privates = { //setup private vars, they could be also changed, added in method or children
        a: 1,
        b: 2,
        c: 3
    };
    //setup public methods which uses privates
    this.aPlus = bindPlus("aPlus", this, privates); //pass method name as string!
    this.aGet = bindPlus("aGet", this, privates);
    if (inherit) {
        return privates;
    }
}
A.prototype.aPlus = function () {
    var args = getArgs(arguments),
        //self is "this" here 
        self = args.shift(),
        privates = args.shift(),
        //function real arguments
        n = args.shift();
    return privates.a += n;
};

A.prototype.aGet = function (n) {
    var args = getArgs(arguments),
        self = args.shift(),
        privates = args.shift();
    console.log(this, self, privates);
    return privates.a;
};

//utilites
function getArgs(arg) {
    return Array.prototype.slice.call(arg);
}

function bindPlus(funct, self, privates) {
    return function () {
        return Object.getPrototypeOf(self)[funct].bind(this, self, privates).apply(null, arguments);
    };
}

//inherited 
function B(inherit) {
    var privates = Object.getPrototypeOf(this).constructor.call(this, true);
    privates.d = 4;
    this.dGet = bindPlus("dGet", this, privates);
    if (inherit) {
        return privates;
    }
}

B.prototype = Object.create(A.prototype);
B.constructor = B;

B.prototype.aGet = function () {
    var args = getArgs(arguments),
        self = args.shift(),
        privates = args.shift();
    console.warn("B.aGet", this, privates);
    return privates.a;
};

B.prototype.dGet = function () {
    var args = getArgs(arguments),
        self = args.shift(),
        privates = args.shift();
    console.warn("B.dGet", this, privates);
    return privates.d;
};


// tests
var b = new B();
var a = new A();

//should be 223
console.log("223 ?",b.aPlus(222));

//should be 42
console.log("41",a.aPlus(222));

Больше тестов и примеров здесь: http://jsfiddle.net/oceog/TJH9Q/