【问题标题】:Weird moment of JS InheritanceJS继承的诡异时刻
【发布时间】:2014-02-11 22:42:32
【问题描述】:

这是我的超类:

function Element() {
   var name;
   this.setName(n) = func()...{};
   this.getName() = func()..{return name};
}

我的另一个子班:

Select = null;
...
Select =
   function (n) {
       if (typeof n !== "undefined")
          this.setName(n);
   ...
}
Select.prototype = new Element();
Select.prototype.constructor = Select;

那么,我在说什么“奇怪的时刻”?这里是:

var e1 = new Select("element1");
e1.getName();  // return "element1"
var e2 = new Select();  // WITHOUT NAME
e2.getName();  // return "element1"!!! should be ""!

这是一个相当可预测的行为,但如何解决这个问题? 当然,我可以在Element 中制作类似this.clear() 的东西,这将清除属性并将此方法放入Select 函数中,但也许有合适的解决方案?

【问题讨论】:

  • 不喜欢私有财产。
  • @wared yeeh 很棒的解决方案)
  • 您应该将此行 Element.call(this, n) 添加到 Select
  • 更多关于如何继承和使用构造函数可以在这里找到stackoverflow.com/a/16063711/1641941,比如如何重用父构造函数以及为什么你不应该创建一个Parent实例来设置Child的原型部分。
  • 顺便说一句,您可能会考虑不覆盖全局Element interface...

标签: javascript prototype prototypal-inheritance


【解决方案1】:

您应该将此行 Element.call(this, n) 添加到 Select 中。这很难解释,而且我很生气,因为我不喜欢 javascript 中的虚假私有属性,但是,我必须提供一些细节,以便您了解您当前在做什么,否则我将无法到达睡觉。

所以,new Element() 创建了一个新的环境,name 可以在不打扰任何人的情况下生活。此外,还创建了两个名为setNamegetName 的新函数,并绑定到Selectprototype 对象。

从现在开始,如果您创建Select 的新实例,则可以调用这些函数,因为它们在实例的原型中可用。这实际上发生在new Select("element1")。确实,n 是定义的,所以,setName 是从引用实例的this 调用的。

但最重要的是,调用setName 并将n 设置为"element1" 也会将name 设置为"element1"。然后,如果您创建 Select 的第二个实例,但未定义 n,则不会调用 setName,因此,name 仍然设置为 "element1"对于这两个实例

为什么对于这两种情况?因为两个实例共享相同的setName 方法,一个绑定到原型,并且这个方法引用了一个唯一的name 变量——记住Element 只被调用了一次。

最后,为什么Element.call(this, n) 这一新行会阻止name 被共享?因为每次调用Element都会创建一个新的上下文,即一个新的name、一个新的setName和一个新的getName,并将这两个方法绑定到新创建的实例上。

好吧,希望今天是你的早晨,如果你因我的错而陷入睡眠障碍,我会很抱歉......


正如BergiHMR 所提到的,这种创建原型的方式——Child.prototype = new Parent——已经过时了,会导致你走向死胡同。请记住,原型是一种用于创建实例的模板。了解这一点并考虑您的代码,我们至少可以做出两个观察:

  1. 如您所知,构造函数旨在初始化实例。在您的情况下,初始化过程(Element 中的内容)是不必要的,因为当前的目标是设置将在其上创建实例的模板。
  2. 假设namenew Element(name) 中是必需的,否则你的程序会崩溃。你会给原型起什么样的名字?这个问题显然没用,因为您不想给模板命名。

要绕过这些问题,您需要一个中间人,如下面的代码所示(复制自 Bergi 共享的 link)。如您所见,原型的创建被委托给一个空的“虚拟”构造函数。这样做可以解决上面提出的两个问题。

function Dummy () {}
Dummy.prototype = Element.prototype;
Select.prototype = new Dummy();
Select.prototype.constructor = Select;

或者,您可以使用内置的 Object.create()

Select.prototype = Object.create(Element.prototype);
Select.prototype.constructor = Select;

进一步阅读:

【讨论】:

【解决方案2】:

您应该使用Element 的原型,这样变量就不会在实例之间共享:

function Element() {}    
Element.prototype.setName = function(n) { this.name = n; };
Element.prototype.getName = function() { return this.name; };

在这种情况下,e2.getName() 将返回 undefined

【讨论】:

  • 使用这种方法会暴露名称变量。
  • @Péter 它并没有改变任何东西,因为即使是私有的,变量仍然可以通过setName 改变。这就是我觉得脾气暴躁的原因:D +1 Dan Lee。
  • 是的,但是我们没有看到 setName 方法的来源,我认为@wingsofovnia 是故意选择这个结构的。
  • @Péter OP 没有提到变量必须是私有的(我们只是在这里假设)。这只是另一个可行的解决方案。
  • @Péter 我不是在责怪你,我只是想借此机会详细说明我的观点。如果能得到 OP 的一些澄清,那就太好了。
猜你喜欢
  • 2015-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-20
  • 1970-01-01
  • 2013-02-04
相关资源
最近更新 更多