2010-09-01 4 views
20

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

Update: This article подчеркивает некоторые из вопросов:

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

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

Решение: В соответствии с ответом BGerrissen, пеленальным декларацию о B и выходе прототипа работает как задумано:

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

ответ

17

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

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

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.Способ избежать этого является использование Douglas Crockfords зачать метод:

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

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

5

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

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

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 
+1

Но здесь все дело в том, что функциональность принадлежит в 'Ā', и распределяется между несколькими суб«классов»(' 'b', C' и 'D'). Нет ли возможности для нестатических частных переменных в суперклассах в Javascript? –

+1

Обычный способ обозначения «личных» намерений (иначе говоря, программисты что-то особенное происходит со свойством) - использовать символ подчеркивания как префикс имени свойства (т.е. obj._privateMember). Хотя это будет работать с наследованием для строк и чисел, оно начинает становиться более сложным при использовании объектов и массивов в качестве свойств. – BGerrissen

+0

Я не совсем понимаю, что вы просите. Вы хотите, чтобы переменная, определенная в функции 'A', была доступна для методов' B', 'C' и' D', но больше нигде? Постарайтесь не думать о вещах в JavaScript с точки зрения возможностей других языков (таких как Java или C#). JavaScript просто не имеет классов. Он имеет объекты, которые наследуют свойства от других объектов через цепочку прототипов. –

15

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

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

В ответе Тима Дауна показаны две альтернативы. Его incrementPublic использует наследование, но делает переменную счетчика общедоступной (т. Е. Отказывается от инкапсуляции). Принимая во внимание, что 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.

Update Я бы с осторожностью заявления вы цитируемый в вашем обновлении:

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

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

+1

+1. Хорошее объяснение. Я оставлю свой ответ так, как есть, и не пытаюсь переписать то, что вы здесь написали. Лично я считаю, что прототипы и конструкторы полезны, хотя бы для совместного использования методов между несколькими связанными объектами. –

+0

+1 для просветительской прототипической природы. – BGerrissen

0

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

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/

Смежные вопросы