1

在我了解Object.create().

var Foo = function() {
    this.report('Foo');
};

Foo.report = function(i) {  // You read that right; it's not 'Foo.prototype.report'
    alert('report: ' + i);
};

var Bar = function() {
    this.report('Bar');
};

Bar.prototype = Foo;  // Right; it's not 'new Foo()'

var b = new Bar();
b.report('b');

奇怪的是,这确实有效,或者至少它按我预期的方式工作:它警告“Bar”,然后是“b”。但是如果我尝试Foo()直接使用,它会失败:

var f = new Foo();
f.report('f');

浏览器告诉我该对象没有方法report()。为什么它适用于'b'而不适用于'f'?此外,如果我设置Bar.prototype = new Foo()(不更改Foo.reportFoo.prototype.report),那么“b”再次起作用,但浏览器说“f”没有report()方法。但是,如果我添加report()Foo.prototype而不是仅仅添加到Foo,那么一切都会完美运行。当然,如果我设置Bar.prototype = Object.create(Foo.prototype),一切都会正常工作。但是滥用部分工作和失败的方式让我完全困惑。有人可以帮助我准确了解我做这些事情时发生了什么吗?具体来说,什么时候发生:

  • 我将一个函数添加到Foo,而不是将其添加到Foo.prototype
  • 我设置Bar.prototypeFoo,而不是设置为new Foo()orObject.create(...)

Resig 在幻灯片 76中接近这一点,但他没有解释。我知道这不是使用原型的方式,但我觉得理解这种行为将对 js 原型设计有所启发。我以为我明白了!

4

3 回答 3

2

我理解你的问题。您提到的幻灯片 - John Resig 的第 76 张幻灯片,已在以下答案中进行了解释:

https://stackoverflow.com/a/17768570/783743

您的问题是人们第一次学习 JavaScript 时遇到的经典问题;这个问题的出现不是因为你不理解原型继承,而是因为原型继承在 JavaScript 中的描述方式。

原型继承的两个方面

原型继承可以通过两种方式之一来实现。因此原型继承类似于硬币。它有两个面孔:

  1. 原型继承的原型模式
  2. 原型继承的构造函数模式

第一种模式是原型继承的常见或真实模式。像 Self 和 Lua 这样的语言采用原型继承的原型模式。

第二种模式旨在使原型继承看起来像经典继承。它仅在 JavaScript 中使用,它隐藏了原型继承的工作方式,使其难以理解。

我在关于原型继承为何重要的文章中深入讨论了原型继承,我建议您仔细阅读。

了解原型继承

原型继承是关于从其他对象继承的对象。原型继承中没有类。只有对象。例如,考虑:

var boy = {};
var bob = Object.create(boy);

在上面的示例中,对象bob继承自 object boy。用简单的英语:

鲍勃是个男孩。

在经典继承中,您可以将上述内容编写为:

class Boy {
    // body
}

Boy bob = new Boy;

如您所见,原型继承非常灵活。在原型继承中,您需要的只是对象。对象既是类又是实例。表现为类的对象称为原型。因此boy是 的原型bob

从原型构造对象

现在考虑我们有一个名为 的对象rectangle

var rectangle = {
    width: 10,
    height: 5,
    area: function () {
        return this.width * this.height;
    }
};

我们可以使用rectangle如下实例:

console.log(rectangle.area());

另一方面,我们也可以rectangle用作原型:

var rect2 = Object.create(rectangle);
rect2.width = 15;
rect2.height = 6;
console.log(rect2.area());

然而widthheight在每个继承自rectangle. 因此我们创建了一个构造函数:

rectangle.create = function (width, height) {
    var rect = Object.create(this);
    rect.height = height;
    rect.width = width;
    return rect;
};

现在您可以创建rectangle如下实例:

var rect2 = rectangle.create(15, 6);
console.log(rect2.area());

这是原型继承的原型模式,因为在这种方法中,重点是原型而不是构造函数create

构造函数模式

同样的事情可以在 JavaScript 中使用构造函数模式来完成,如下所示:

function Rectangle(width, height) {
    this.height = height;
    this.width = width;
}

var rectangle = Rectangle.prototype;

rectangle.width = 10;
rectangle.height = 5;

rectangle.area = function () {
    return this.width * this.height;
};

var rect2 = new Rectangle(15, 6);
console.log(rect2.area());

构造函数模式的问题在于,重点是构造函数而不是原型。因此,人们认为这rect2是一个Rectangle错误的例子。在 JavaScript 中,对象继承自其他对象而不是构造函数。

该对象是创建时的rect2一个实例。它不是.Rectangle.prototype rect2Rectangle

你的问题

在您的问题中,您定义Foo如下:

var Foo = function() {
    this.report('Foo');
};

Foo.report = function(i) {
    alert('report: ' + i);
};

Foo因此,当您使用new(即)创建对象时,new Foo该对象继承自Foo.prototype. 它不继承自Foo. 因此,该对象没有调用任何属性report,因此会引发错误。

在您的问题中,您还定义Bar

var Bar = function() {
    this.report('Bar');
};

Bar.prototype = Foo;  // Right; it's not 'new Foo()'

这里prototype是。Bar_ Foo因此,从创建的对象Bar将继承自Bar.prototypeor Foo。因此,这些对象reportFoo.

我知道。这很令人困惑,但这就是在 JavaScript 中实现原型继承的方式。不要忘记阅读我的博文:

为什么原型继承很重要

于 2013-08-10T18:11:12.420 回答
1

您正在设置Bar.prototype给定对象(即 function Foo),因此该对象是 的任何实例的原型Bar,并且您正在向该原型添加方法,因此这些方法可用于Bar.

这当然是不典型的——通常我们不使用函数(尤其是构造函数)作为原型——但是函数(包括构造函数)是对象,因此有资格成为原型,所以从语言的角度来看,它绝对没有错。我不会称其为“滥用”,除非您的意思是这不是您打算做的。

于 2013-08-10T05:17:19.677 回答
0

你理解原型没有任何问题。您在理解 和 之间的区别时遇到f = Foo问题f = new Foo()

当您设置 时f = Foo,您将 'f' 设置为等于名为 的对象Foo。当然,如果你已经添加了一个report()方法Foo,那么你可以f.report()毫无问题地调用。但请注意,您无法访问Foo's原型。如果你设置Foo.prototype.announce = function() {...};,你将无法调用f.announce()——没有f.announce()调用。

当您设置 时f = new Foo(),您正在创建一个新对象,并使用该Foo()函数来构造它,也就是说,Foo()将新对象作为Foo's this. 您可以访问Foo's原型,但无法访问Foo's属性。你可以打电话f.announce(),但没有f.report()

同样,当您设置 时Bar.prototype = Foo,您将设置Bar.prototype为等于名为 的对象Foo。因为你设置b = new Bar()了,'b'可以访问Bar's原型,所以你可以调用b.report(),因为Bar's原型(被调用的对象Foo)里面有一个report()函数。Foo's但是由于上述原因,您无法访问原型中的任何内容。

所以现在应该很明显,当你设置Bar.prototype = new Foo():Bar.prototype可以访问Foo's原型时会发生什么(但不是Foo函数对象的任何方法,原因如上所述)。所以你可以在Bar's原型或Foo's原型中调用任何方法。

当然,Object.create()现在是创建对象的首选方法,而不是new.

于 2013-08-10T15:43:11.270 回答