Я думаю, что неправильно понял, как работает наследование прототипов 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?

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

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

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

Решение . Согласно ответу Б.Герриссена, изменение объявления B и оставление прототипа работает как задумано:

var B = function() { A.apply(this, arguments); };
20
Vegard Larsen 1 Сен 2010 в 14:35

4 ответа

Лучший ответ

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

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

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's own private members
var b = new B(); // has it's own private members

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

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

Теперь, применяя наследование через B.prototype = new A(), вы вызываете ненужный код конструктора из A. Чтобы избежать этого, используйте метод 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);

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

17
Oleg 16 Авг 2012 в 10:45

Я просто нашел другое хитрое решение, без экспорта каких-либо методов / переменных в публичные объекты.

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/

0
zb' 21 Июл 2014 в 03:37

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

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

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
5
Tim Down 1 Сен 2010 в 11:53

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

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

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

То, что вы хотите, это сочетание трех вещей:

  • наследуемое поведение - поэтому оно должно быть определено в A и не требовать кода в 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 не является «одноэлементным объектом» и не должен ограничиваться использованием в одноэлементных объектах.

16
Daniel Earwicker 1 Сен 2010 в 11:23