【问题标题】:Javascript prototype subtleties: why 'new' mattersJavascript 原型的微妙之处:为什么“新”很重要
【发布时间】:2013-08-12 02:25:49
【问题描述】:

在我了解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.report 更改为Foo.prototype.report),那么'b' 再次起作用,但浏览器说'f' 没有report() 方法。但是,如果我将report() 添加到Foo.prototype 而不是仅仅将其添加到Foo,那么一切都会完美运行。当然,如果我设置Bar.prototype = Object.create(Foo.prototype),一切正常。但是滥用部分工作和失败的方式让我完全困惑。有人可以帮助我准确了解我做这些事情时发生了什么吗?具体来说,什么时候发生:

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

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

【问题讨论】:

  • 您在 constructor Foo 上设置属性,而不是在 instances 上设置属性,这正是设置 prototype 所做的。当您将Bar.prototype 设置为Foo 时,将引用Foo 构造函数的report 属性
  • @Ian 所以Foo.report = ...Foo.report.constructor = ... 一样吗?
  • 我不这么认为。也许我不应该说“构造函数”。我所说的“构造函数”的意思只是它可以用作通过使用new 创建实例(至少是您设置它的方式以及您通常期望的方式)。 Foo 只是一个对象(一个函数),它具有属性。但是当您使用new Foo() 时,这些属性不会复制到实例中。复制的属性来自Foo.prototype。我所说的“复制”实际上是指“参考”。你实际上是在做Bar.prototype = { report: func... };
  • 是的,我想是的。我对措辞有点困惑,但我想你明白了。即使它被声明为一个函数,Foo 只是一个可以调用的对象......它具有属性。默认情况下,Foo 没有属性(如空对象)。然后,设置report 属性。因此,如果您查看我的上一条评论(不确定您是否看到了编辑),您会看到它基本上在做什么(如最后一句话)。请记住,它不适用于您的f 对象,因为您没有在Foo 原型 上设置report,因此Foo 实例不会“继承”它属性
  • 函数 properties 不被实例继承。函数prototype 属性。您的最后评论似乎自相矛盾,但也许我误读了。我不确定它是否会有所帮助,但请看一下 jsfiddle.net/7y9Fg 。您应该看到Foo instances 没有report 属性(因为report 不在Foo.prototype 中)。但是Bar instances 可以

标签: javascript prototype


【解决方案1】:

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

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

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

原型继承的两个方面

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

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

第一个模式是原型继承的常见或真实模式。 Self 和 Lua 等语言采用原型继承的原型模式。

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

我在Why Prototypal Inheritance Matters 上的文章中深入讨论了原型继承,建议您仔细阅读。

了解原型继承

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

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

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

鲍勃是个男孩。

在经典继承中,您可以将上面的内容写成:

class Boy {
    // body
}

Boy bob = new Boy;

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

从原型构造对象

现在考虑我们有一个名为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());

但是,在从rectangle 继承的每个对象上继续定义widthheight 是很痛苦的。因此我们创建了一个构造函数:

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());

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

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

你的问题

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

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

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

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

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

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

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

这里prototypeBarFoo。因此,从Bar 创建的对象将继承自Bar.prototypeFoo。因此,这些对象从Foo 继承了report 函数。

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

Why Prototypal Inheritance Matters

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 嘿,误用意味着我不知道我在做什么!我担心我仍然没有。
    【解决方案3】:

    您在理解原型方面没有任何问题。您无法理解 f = Foof = 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's this 的新对象运行Foo() 函数。您可以访问 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

    【讨论】:

    • 如果你用第一人称自言自语,人们会认为你疯了。并不是说这与我有任何关系,但您的回答并没有具体回答您自己的问题。对于第三者,您的回答与您的问题完全无关。您的回答反映了您所理解的内容,而不是问题的要求。这是与 StackOverflow 上的问答相关的经典 xy problem。请考虑让您的答案更具体。
    • 嗯,很好的观点。我想真正的问题是我一开始就问错了问题。发布此答案后,我更改了标题。我不知道它是否真的是 XY,因为我问的不是一个尝试的解决方案;我只是以为我遇到了原型问题。事实上,它与原型相关的,只是不是我一开始想的那样。也许我应该完全删除这个问题?
    • @AaditMShah 弊大于利?嗯,解释它是如何工作的对你有害吗?我没有提出任何建议,我解释了
    • @Ian 您的 cmets 和我们随后的聊天帮助我制定了我发布的答案。非常感谢。
    • @AaditMShah In the end when you construct an object from a function using new in JavaScript the newly created object inherits from the prototype of the function. Not the function itself. - 这就像我试图强调的主要事情,但我想我很难解释或理解 OP。无论如何,您对原型继承的解释总是很棒
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-11
    • 2018-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多