3

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

测试用例:

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

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

有什么方法可以更优雅地解决这个问题吗?

4

2 回答 2

1

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

原型上没有任何东西被复制——原型继承的整个概念是属性引用原型对象的共享属性。因此,如果您希望每个实例的属性都是独立的,则必须将其显式分配给对象并隐藏原型属性;就像您在_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
});

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

于 2013-04-13T14:18:53.077 回答
0

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

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

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 它是一个对象:并且您在o1和之间共享了该对象o2。这就是为什么实例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不应该有这样的属性。我仍然不清楚为什么你不能修改构造函数,但重点是:当你创建对象的实例时,你必须“设置”它。通常,调用构造函数,但任何函数都可以。当您想要支持继承并调用“超级”构造函数来初始化您的对象时,也会这样做。

于 2013-04-13T12:48:46.240 回答