4

问题:

如何从该类的实例生成该类的新实例?

我发现了什么:

考虑下一个类及其实例:

// Create a new class
var Foo = function Foo () {
  console.log('New instance of FOO!');
};

Foo.prototype.generate = function(first_argument) {
  this.arr = [1,2,3,4,5];
};

var foo = new Foo();

foo.generate();
console.log(foo.arr); // [1,2,3,4,5]

请注意,这foo是 fo 的实例Foo,我将在示例中使用此实例。

对象.create

使用 Object.create 我可以生成 foo 实例的新副本,但它们将共享状态数据:

var bar = Object.create(foo);
console.log(bar.arr); // [1,2,3,4,5]

bar.arr.push(6);
console.log(foo.arr); // [1,2,3,4,5,6]
console.log(bar.arr); // [1,2,3,4,5,6]

如果某些逻辑在构造函数中,则不会调用它。

原型继承

这类似于对象创建,但它调用构造函数。它仍然有相同的状态数据问题:

var Bar = function () {
  foo.constructor.call(this);
};

Bar.prototype = foo;

var bar = new Bar();
console.log(bar.arr); // [1,2,3,4,5]

bar.arr.push(6);
console.log(foo.arr); // [1,2,3,4,5,6]
console.log(bar.arr); // [1,2,3,4,5,6]

火狐原型

这是我真正想做的一个例子,但它只适用于 Firefox:

// Create Bar class from foo
var Bar = foo.constructor;
Bar.prototype = foo.__proto__; // Firefox only

在这里,我有类 Bar,它是类 Foo 的副本,我可以生成新实例。

var bar = new Bar();

console.log(bar.arr); // undefined

bar.generate();
console.log(bar.arr); // [1,2,3,4,5]

有没有其他方法可以实现适用于所有浏览器的相同目标?

4

5 回答 5

5

1) proto由于明显的原因必须避免,你有 Object.getPrototypeOf 以标准方式读取对象的原型。
2)您没有让构造函数完成所有工作,而是有一种带有生成的init方法。因此,在构造函数的末尾调用 this.generate 会更有意义。这就是原型继承不起作用的原因:构造函数不起作用。同样的想法,你不能责怪 Object.create 因为它既不是构造函数,也不是在你的例子中生成,所以你基本上构建了一个具有相同原型的对象,仅此而已。

如果您确实在构造函数中添加了对 this.generate 的调用,也许最简单的方法是使用 foo 的构造函数属性来构建一个新对象:

var bar = new foo.constructor();

这会给你一个全新的项目。

所以回答你的问题:是的,还有另一种方法,使用对象实例的构造函数属性来构建一个新项目。

但是没有继承方案能够“猜测”,应该用这样那样的方法执行完整的初始化。初始化是构造函数的工作。例如,我们可以想象您this.arr = [];在构造函数中构建了一个新数组,并且 generate() 将 push() 该数组中的项目,这将是一种有效的方式。但是为了优化和清晰,您必须避免通过方法添加属性:在构造函数中设置所有实例属性,并且可能在原型上设置共享属性,并且仅在方法中使用这些属性。

于 2013-07-11T11:02:15.623 回答
2

为什么不按照传统意义来做呢?在可以复制或从它们创建实例的 C++ 类中,只需实现一个复制构造函数。您可以像使用 C++ 一样在 JavaScript 中执行此操作,只需让构造函数检查参数:

function Foo(from) {
    if (from && from.prototype && from.prototype.constructor === Foo) {
        this.arr = [].concat(from.arr);
    }
}

如果你真的想要,然后在 Foo 上创建一个复制方法:

Foo.prototype.createCopy = function() {
    return new Foo(this);
};

或者只使用构造函数:

var bar = new Foo(foo);

这将创建一个浅层的 arr 克隆。要执行深度克隆,您需要编写深度克隆方法或使用 JavaScript 框架中的一种方法,例如 YUI。

如果你想要一个新实例:

Foo.prototype.createNew = function() {
    return new Foo();
};

var bar = foo.createNew();

var fooCopy = foo.createCopy();

如果该类很可能用作另一个类的基类,则使用:

Foo.prototype.createNew = function() {
    return new (this.prototype.constructor)();
};

Foo.prototype.createCopy = function() {
    return new (this.prototype.constructor)(this);
};

如果您担心修改 Foo 类,您始终可以创建自己的实现来发出复制和创建函数。

(function() {
    var Foo = function() {},
        MyNamespace = {
            Foo: Foo
        };

    Foo.prototype = {
        constructor: window.Foo,

        createCopy: function() { /* ... */ },
        createNew: function() { /* ... */ }
    };

    for (var n in window.Foo.prototype) {
        Foo.prototype[n] = window.Foo.prototype[n];
    }

    // Export it.
    window.MyNamespace = MyNamespace;
})();

如果您只想将这些方法添加到本机 Foo 类,通常是这样做的:

(function() {
    if (! Foo.prototype.createCopy) {
        Foo.prototype.createCopy = function() { /* ... */ };
    }

    if (! Foo.prototype.createNew) {
        Foo.prototype.createNew = function() { /* ... */ };
    }
})();

如果您在最早调用程序/脚本时执行此操作,您将在创建任何实例之前挂钩 Foo,从而使您能够创建副本。

于 2013-07-11T11:00:45.000 回答
2

根据您对克雷格回答的评论:从任何对象创建一个新实例都可以在一行中轻松完成:

var newFoo = new (Object.getPrototypeOf(foo).constructor);

但是,对于某些较旧的浏览器,Object.getPrototypeOf将无法正常工作。您可以通过将此位添加到脚本中轻松解决此问题:

if (!Object.getPrototypeOf)
{
    Object.getPrototypeOf = function(o)
    {
        if (o instanceof Object)
        {
            return (o.__proto__ || o.prototype);
        }
        throw new TypeError('Object.getPrototypeOf called on non-object');
    };
}

在大多数情况下,考虑所有因素:

var newFoo = new (foo.constructor);

工作得很好,除非一些小丑弄乱了实例,给它分配了一个constructor属性,或者改变了原型。

在我看来,您好像没有完全理解 JS 如何解析上下文,以及如何解析对对象属性的引用。在我的答案中查看图表

首先,将您的最后一个片段更改为:

Bar.prototype = Foo.prototype;//__proto__ is non-standard proto reference
Bar.prototype.constructor = Bar;

这应该适用于 X 浏览器。
至于为什么您的其他尝试失败,这很容易:当您编写 时bar.arr,JS 只是查找arr直接附加到实例的属性。如果没有找到,搜索将扩展到Bar.prototye. 如果失败,JS 会搜索 的原型Bar.prototype,即foo(例如传递给Object.create的实例)。
在那里,JS确实找到了它正在寻找的属性,因此bar.arr 引用 foo.arr。如果您更改对象(数组是对象),则使用哪个引用来访问该对象并不重要。只有一个实例,只要它至少被引用一次,它就会一直保存在内存中。

在全:

var Foo = function()
{
    console.log('the Foo constructor');
};
Foo.prototype.generate = function()
{
    this.arr = [1,2,3];
    return this;
};
var Bar = function()
{
    console.log('the Bar constructor');
};
Bar.prototype = new Foo;//make Bar inherit from Foo
Bar.prototype.constructor = Bar;//but make it use its own constructor

现在您可以根据需要使用Foo两者Bar

var foo = new Foo;
foo.generate();
foo.arr.push(4);
var bar = new Bar;
bar.generate();
console.log(foo.arr);//here [1,2,3,4]
console.log(bar.arr);//[1,2,3]

当然,如果您想Bar从 foo 的特定实例开始:

var foo = new Foo;
foo.generate();
Bar.prototype = foo;//
Bar.prototype.constructor = Bar;
var bar = new Bar;
console.log(bar.arr);//logs [1,2,3]
//BUT BE CAREFUL:
console.log(foo.arr === bar.arr);//true, bar only references the same property
bar.arr = bar.arr.slice(0);//copies the array
bar.arr.push(4);
console.log(foo.arr);//[1,2,3] still
console.log(bar.arr);//[1,2,3,4] changed

要撤消我在上面所做的更改,我只需要取消原型属性:

delete bar.arr;
console.log(bar.arr);//back to [1,2,3]
console.log(bar.arr === foo.arr);//true, I've deleted a bar property

而已

于 2013-07-11T11:01:00.507 回答
0

永远不要在原型中放任何数据,只放函数。

您可以通过以下方式实现开箱即用Object.create

function A() {

}

function B() {

}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
于 2013-07-11T10:59:06.060 回答
0

在您的第二个原型示例bar.prototype = foo中,您将必须创建一个代理函数,以便不调用构造函数方法,并且您真正从 foo 扩展:

var Foo = function() { 
    console.log('foo init') 
};
Foo.prototype.generate = function() { 
    this.arr = [1, 2, 3, 4]; 
}

var Bar = function() {
    // Call the "constructor" method (Foo) with the correnct context and arguments,
    // for this we can use Function.apply(context, array);
    // where array can be any array like object as well a an real array:
    Foo.apply(this, arguments); 
    console.log('bar init');
};

// Surrogate function:
var sur = function() {}; 
// Capture Foo's prototype:
sur.prototype = Foo.prototype;
// Apply the prototype from Foo to Bar's prototype chain, without initializing Foo:
Bar.prototype = new sur;
// Also remember to update the constructor:
Bar.prototype.constructor = Bar;

var f = new Foo(); // foo init;
var b = new Bar(); // foo init, bar init;

f.generate();
f.arr.push(-1);
b.generate();
b.arr.push(-2);

console.log(f.arr); // [1,2,3,4,-1]
console.log(b.arr); // [1,2,3,4,-2]
于 2013-07-11T10:45:12.977 回答