原型中的对象作为参考继承

时间:2013-04-13 11:41:00

标签: javascript oop prototype

我想使用原型继承新的对象实例。

测试用例:

var MyObj = function() {}
MyObj.prototype.objName = {} 
// I want this to be a different object for each instance of MyObj

var o1 = new MyObj (),
    o2 = new MyObj ();

o1.objName['a'] = 1;
o2.objName['a'] = 2;

alert(o1.objName['a']) // 2
alert(o1.objName === o2.objName) // true

这意味着原型中的对象不会作为其副本继承,而是作为其引用继承。

我知道通常你可以这样做。

var MyObj = function() {
    this.objName = {}
}

var o1 = new MyObj(),
    o2 = new MyObj();

alert(o1.objName === o2.objName) // false

这样可以正常工作,但就我而言,这不是一个选择。我真的需要在MyObj函数之外定义objName。

我设法用这个

“解决”了这个问题
MyObj.prototype.objName = function() {
    if ( this._objName === undefined ) {
        this._objName = {};
    }
    return this._objName;
}

var o1 = new MyObj(),
    o2 = new MyObj();

o1.objName()['a'] = 1;
o2.objName()['a'] = 2;

alert(o1.objName()['a']) // 1

但这不是很漂亮,而且这段代码的性能要差得多。

有没有办法更优雅地解决这个问题?

2 个答案:

答案 0 :(得分:1)

  

这意味着原型中的对象不会作为其副本继承,而是作为其引用继承。

原型上没有任何内容被复制 - 原型继承的整个概念是属性引用原型对象的共享属性。因此,如果您希望每个实例都有一个属性,则必须将其显式分配给对象,并将 shadow 分配给prototype属性;就像您在代码中使用_objName属性一样。

  

但这不是很漂亮,而且这段代码的性能要差得多。

如果你想要它漂亮,将其移动到构造函数(或使构造函数查找类似于init方法的内容,如果存在则调用,然后你可以在原型上创建该init方法。

为了提高性能,可以将getter函数更改为

MyObj.prototype.getObj = function() {
    var obj = {};
    this.getObj = function(){ return obj; }; // overwrite itself
    return obj;
};

虽然它仍然具有函数调用开销。为了更加优雅,您可以使用getter属性(旧浏览器不支持)在第一次访问时删除自身:

Object.defineProperty(MyObj.prototype, "objName", {
    get: function() {
        var obj = {};
        Object.defineProperty(this, "objName", {
            value: obj,
            writable: true //?
        });
        return obj;
    },
    enumerable: true,
    configurable: true
});

现在你可以省略函数调用括号。

答案 1 :(得分:0)

  

这意味着原型中的对象不会作为其副本继承,而是作为其引用继承。

要清楚。首先,在JavaScript中,所有对象都通过引用传递,而不是通过值传递。只有原语按值传递。 其次,你实际上并没有“复制”或“传递”任何东西。当您设置prototype时,您正在创建原型链。这意味着在你的情况下:

var MyObj = function() {};
MyObj.prototype.objName = {} ;

var o1 = new MyObj ();
var o2 = new MyObj ();

o1o2都没有任何名为objName的属性,您可以使用以下方法对其进行测试:

console.log(Object.keys(o1)); // []

当JS看到像o1.objName这样的代码时,首先检查对象是否具有此属性,如果有,则使用它。如果没有,开始查看原型链,从o1的原型开始,即MyObj.prototype:它找到了属性objName,并返回它。如果没有找到它,那么JS将继续检查MyObj.prototype的原型,依此类推。所以,重点是:MyObj.prototype 它是一个对象:您在o1o2之间共享了该对象。这就是为什么objName的实例是相同的。这与以下内容完全相同:

function objName(obj) {
    return "objName" in obj ? obj.objName : O.objName;
}

var O = { objName: [] };
var foo = {};
var bar = {};

objName(foo).push(0);
objName(bar).push(1);

因此,您无法在prototype中放入任何不希望在使用该原型创建的对象之间共享的对象。我会说这样的共享状态也是一种应该避免的不良做法,这就是为什么一般prototype不应该有这样的属性。 我仍然不清楚为什么你不能修改构造函数,但重点是:当你创建对象的实例时,你必须“设置”它。通常,调用构造函数,但任何函数都可以。当你想支持继承,并调用“超级”构造函数来初始化你的对象时,也会这样做。