我想我误解了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
的多个实例之间的共享对象?
更新:This article突出显示了一些问题:
问题在于每个方法用于创建私有变量的范围,其工作正常,也是闭包,在行动中,如果您更改一个对象实例的私有变量,它将被更改为所有。即它更像是私有静态属性,而不是实际的私有变量。
所以,如果你想拥有私有的东西,更像是非公共常量,上述任何一种方法都是好的,但不适用于实际的私有变量。私有变量只适用于JavaScript中的单例对象。
解决方案:根据BGerrissen的回答,更改B
的声明并保留原型按预期工作:
var B = function() { A.apply(this, arguments); };
答案 0 :(得分: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 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);
当然,您可以完全放弃继承并充分利用装饰器,至少需要私人成员。
答案 1 :(得分:16)
你需要忘记课程的想法。在JavaScript中并不存在像'B实例'这样的东西。只有'通过调用构造函数B'获得了一些对象。对象具有属性。有些是其“自有”属性,其他属性则通过搜索原型链来包含。
当您说new A
时,您正在创建一个对象。然后将其指定为B的原型,这意味着每次调用new B
都会生成一个具有相同直接原型的新对象,因此具有相同的计数器变量。
在Tim Down的回答中,显示了两种选择。他的incrementPublic
使用继承,但使计数器变量公开(即放弃封装)。而incrementInternal
使计数器变为私有,但通过将代码移动到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
不是“单例对象”,不必限制在单例对象中使用。
答案 2 :(得分:5)
原型的要点是它在多个对象(即由相同构造函数创建的对象)之间共享。如果需要在共享原型的对象之间不共享的变量,则需要将这些变量保留在每个对象的构造函数中。只需使用原型来共享方法。
在您的示例中,您无法使用原型完全执行您想要的操作。这是你可以做的事情。请参阅Daniel Earwicker的答案,以获得更多解释,我现在无法复制这一点。
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
答案 3 :(得分: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/