JavaScript:instanceof运算符仍然让人困惑

5
这篇文章如下定义了 instanceof:

instanceof 运算符用于检测构造函数的原型属性是否出现在某个实例对象的原型链上。

这是一个很好的解释,但当我看到《JavaScript高级程序设计》中的代码时,就有些不理解了:

function TextCell(text) {
  this.text = text.split("\n");
}

TextCell.prototype.minWidth = function() {
  return this.text.reduce(function(width, line) {
    return Math.max(width, line.length);
  }, 0);
}

TextCell.prototype.minHeight = function() {
  return this.text.length;
}

TextCell.prototype.draw = function(width, height) {
  var result = [];
  for (var i = 0; i < height; i++) {
    var line = this.text[i] || "";
    result.push(line + repeat(" ", width - line.length));
  }
  return result;
}

function RTextCell(text) {
  TextCell.call(this, text);
}

RTextCell.prototype = Object.create(TextCell.prototype);

RTextCell.prototype.draw = function(width, height) {
  var result = [];
  for (var i = 0; i < height; i++) {
    var line = this.text[i] || "";
    result.push(repeat(" ", width - line.length) + line);
  }
  return result;
};

让我们创建一个RTextCell的实例,并执行以下操作

var rt = new RTextCell("ABC");
console.log(rt instanceof RTextCell); // true
console.log(rt instanceof TextCell); // true

我知道为什么第二个console.log的输出是"true"——因为构造函数TextCell是原型链的一部分。

然而,第一个console.log让我感到困惑。

如果你看看代码(从底部的第10行开始),RTextCell的原型被更新为一个新对象,其原型被设置为TextCell.prototype。

RTextCell.prototype = Object.create(TextCell.prototype);

查看下面的快照,对象"rt"的原型链中没有提到构造函数"RTextCell"。因此,根据我在文章开头提到的定义,输出不应该是false吗?为什么会返回true?

我还阅读了这个,但对于理解这个具体问题没有帮助。

请参见以下快照,依次显示了"rt"、"RTextCell"、"TextCell"的快照。

snapshot of "rt" snapshot of RTextCell snapshot of TextCell


这是一个非常结构良好的问题。你展示了所有以前的调查,希望你得到非常好的回答。 - Erick Petrucelli
哦!我应该补充一下。我在上面的快照中检查了这个原型链树,分别在Chrome 43.0.2357.65和Firefox 33.1.1上进行了检查。 - Harish
3个回答

4

您确实更改了 RTextCell.prototype,但是在构造任何 RTextCell 实例之前进行了更改。考虑这个完全不同的示例,其中 RTextCell.prototype 在使用原始原型创建实例后被修改:

var rt = new RTextCell();
RTextCell.prototype = somethingTotallyDifferent;
rt instanceof RTextCell; // false!

当创建 rt 时,rt.__proto__ === RTextCell.prototype 是正确的。一旦 RTextCell.prototype 改变,这个语句就不再成立了。
你没有在测试对象的原型链中是否存在来自 RTextCell 的原始 prototype 属性。相反,你测试的是 RTextCell.prototype 的值,即 现在 是否存在于对象的原型链中。对于 RTextCell 实例来说,这将总是成立的,因为使用 RTextCell 构造函数创建的实例总是在其原型链中获取 RTextCell.prototype 的当前值,并且在构造实例后永远不会更改 RTextCell.prototype

除了@Dark Falcon的答案之外,您关于将原型设置为“somethingTotallyDifferent”的示例对我很有帮助。这很有道理。谢谢。 - Harish

1
obj instanceof RTextCell

正在测试RTextCell.prototype是否存在于obj的原型链中。由于obj是使用new RTextCell创建的,因此它确实存在。这里的关键是RTextCell.prototype的原型是TextCell.prototype,似乎让您感到困惑。

RTextCell.prototype = Object.create(TextCell.prototype);

并不会删除RTextCell.prototype,它仍然是一个对象,但它的原型恰好是TextCell.prototype。


1

确切的措辞很重要。你谈论的构造函数在原型链中,但原始引用并没有:

instanceof运算符测试一个对象是否在其原型链中具有构造函数的原型属性

因此,表达式rt instanceof RTextCell实际上是在测试类似于这样的东西(记住__proto__不是标准的):

var p = rt.__proto__;
while(p)
{
  if(p == RTextCell.prototype)
    return true;
  p = p.__proto__;
}
return false;

因此,即使在上面的对象树中没有直接引用RTextCell函数,RTextCell.prototype对象也会被引用。

或者用一个表达式:RTextCell.prototype.isPrototypeOf(rt) :-) - Bergi
@Dark Falcon:之前我对instanceof运算符和isPrototypeOf()函数感到困惑,当我在MDN网站上看到“constructor”一词的定义时,我完全误解了。你的回答让我停下来,意识到我错过了什么。“措辞”。现在它有意义了。我还刚刚阅读了这篇文章,介绍了instanceof和isPrototypeOf()之间的区别。谢谢伙计。 - Harish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接