【问题标题】:Puzzle about inheritance in JavaScript关于 JavaScript 继承的谜题
【发布时间】:2016-04-01 10:29:59
【问题描述】:

我知道 JavaScript 中继承的概念是通过原型链,但我不确定我是否理解正确。当一个属性被读取时,引擎会先搜索实例自身的属性,如果没有找到会搜索实例的[[Prototype]]属性,该属性是对创建实例的函数原型的引用,搜索将继续,直到到达 Object.prototype。对于以下代码:

var person1 = {
    name: "Qiushi",
    sayName: function() {
        console.log(this.name);
    }
};

person1.sayName();  // Qiushi

var person2 = Object.create(person1);
person2.name = "Alex";
console.log(person2.hasOwnProperty("sayName")); // false
person2.sayName();  // Alex

当person2继承自person1时,person2可以使用person1中定义的方法。但是 sayName 方法不是 person1 原型的属性,而是它自己的属性。我的问题是,由于方法搜索仅遵循原型链,person2 将如何使用不在此链中的方法?

-------------------最终编辑------------- ------------

如果你对这个问题有同样的担忧,请阅读我和金博的对话。

【问题讨论】:

  • 在链中,只是不拥有(未在person2原型上定义)。这就是为什么我们首先使用hasOwnProperty()
  • 这里有一点需要考虑:JavaScript 中没有继承。没有任何东西可以从任何地方继承任何东西。只有一个对象的单链表,称为原型链。按顺序在该列表中的对象中搜索具有给定名称的属性。一个对象定义了某个属性(然后hasOwnProperty() 对那个特定对象为真),或者它在链中的一个父对象具有(然后hasOwnProperty() 对那个特定父对象为真,但对初始对象为假),或没有,则该属性未定义。
  • @Tomalak:“JavaScript 中没有继承”是不真实的。仅仅因为继承机制很容易解释,这并不意味着它不是继承。 Java 和 C++ 的继承机制也很容易解释。
  • @T.J.Crowder 我知道,我们同意这一点。评论的目的是打破继承属性以某种方式成为原型链末端对象的实际部分的常见思维模式。如果我替换对象原型链的一个元素,该对象立即看起来具有不同的属性。原型继承模仿了继承的效果,但所讨论的对象并没有真正获得它们响应的属性(与 Java/C++ 中的工作方式相反)。
  • @Tomalak:这不是模仿,只是一种不同的机制。同意它的不同之处,以及如何让人们感到惊讶。 (有趣的是,您还经常看到 OP 考虑单独对象的 Java 问题,一个用于基类部分,另一个用于派生部分,并且以 that 的方式感到困惑。:-))

标签: javascript inheritance prototype


【解决方案1】:

作为其他对象原型的对象没有什么特别之处,它们只是对象。

当引擎去读取一个属性时,首先它会查看对象本身是否有它,如果有,就使用它。如果没有,它会查看对象的原型,然后查看 原型等。

在您的代码中,person1person2 的原型,因此在person2 上查找sayName 时,由于引擎在person2 上找不到它,它会在person2 上查找它s 原型,person1,并找到它。

这是您在代码中设置的简单 ASCII 艺术图:

+-------------------+ 人1-----------+-->| (对象) | | +-------------------+ | | __proto__ |-->(未显示,Object.prototype) | |名称:《秋实》 | | | sayName: 函数 | | +-------------------+ | +--------------+ | 人2---->| (对象) | | +--------------+ | | __proto__ |--+ |名称:“亚历克斯” | +---------------+

这是一个更完整的版本:

+------------+ 对象--->| (函数) | +->(未显示,Function.prototype) +------------+ | | __proto__ |--+ +--------------------+ |原型 |---------------------------------+-->| (对象) | +------------+ | +--------------------+ +-------------------+ | | __proto__: null | 人1-----------+-->| (对象) | | | toString: 函数 | | +-------------------+ | | valueOf: 函数 | | | __proto__ |--+ | ... | | |名称:《秋实》 | +--------------------+ | | sayName: 函数 | | +-------------------+ | +--------------+ | 人2---->| (对象) | | +--------------+ | | __proto__ |--+ |名称:“亚历克斯” | +---------------+

【讨论】:

  • 图表很好。
  • 所以这里指出的原型只是当前对象创建的另一个对象,而不是当前对象的[[prototype]]属性,对吗?
  • @photosynthesis:[[prototype]] 属性(上面显示为__proto__)是一个引用对象原型的属性,“只是另一个基于它的对象当前对象已创建”。 :-) 原型对象并不特殊,它们只是普通对象,碰巧有另一个对象的 [[prototype]] 属性引用它们。
【解决方案2】:

Object.create 返回一个新对象,其中提供的参数新对象的原型对象。

所以你现在得到了这个:

{人2} - 名称:'亚历克斯' - {原型} - 名称:'Quishi' -sayName:函数

因此,如果您在 person2 上查找属性 name,它将在 person2 本身上找到该属性,而不是在原型上查找。如果你查找sayName,它不会在 person2 上找到它,所以它会在原型上查找并在那里找到它。

this 关键字将在函数中引用取决于函数从何处调用。这就是this 的工作原理。当您在对象上调用方法时,该对象将是该方法内的this。因此,即使该方法在原型上,如果您调用 person2.sayName();this 仍将引用 person2。如果您要更改它(例如通过.call),您可以更改输出。

控制台.log(person2.name); // -> 亚历克斯 person2.sayName(); // -> 亚历克斯 person2.sayName.call(person2.__proto__); // -> Quishi,即使调用了 person2 person1.sayName(); // -> 奎师 person1.sayName.call(person2); // -> Alex,即使调用 person1

我指出这一点是因为在此示例中误解 this 的可能性与误解原型的可能性一样多。

【讨论】:

  • 猜我搞砸了原型和对象的原型属性。更具体地说,当在自己的属性中没有找到某个属性时,搜索引擎会去它的原型,也就是当前实例所基于的对象,而不是去当前实例的原型属性。
  • @photosynthesis - 你刚才说的没有多大意义。首先,了解Object.create 只是一种在指定其原型的同时创建新对象的方法(最初在对象上不能公开访问的东西)。它对原型是什么 的基本性质或如何处理它们没有影响。在这种情况下,我们只是指定我们希望我们的person2 对象的原型是对象person1。因此,当引擎在person2 上找不到属性并检查person2.__proto__ 时,它找到的是对person1 的引用。
  • 所以从对象字面量创建的每个对象都有其 [[Prototype]] 属性指向 Object.prototype。那么 person2 的 [[Prototype]] 属性指向的是什么,person1 还是 person1.prototype?
  • @photosynthesis - 事实上,Object.create 最初甚至不是 JS 的一部分。事实上,这只是 Douglas Crockford 的用户制作功能,他在其中找到了一种制作新对象并手动确定其原型的好方法……规范制定者非常喜欢他们添加它的实际规格。您将它视为原型继承本身的基本部分......它不是。它只是一个处理原型和对象的函数。
  • @photosynthesis - [[Prototype]] 没有指向 Object.prototype ......它们只是同一事物的两个术语。对象的原型在传统上是不可访问的。因此它被称为不可访问属性 [[Prototype]] 以区分我们实际上并不是在谈论您可以使用点运算符访问的属性。但是对于函数,你可以去.prototype 访问它,所以有些人会这样谈论对象,即使它实际上不起作用。两者都有原型......它只是不能在对象上公开访问。新的__proto__ 改变了这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-17
  • 1970-01-01
  • 2012-10-20
  • 1970-01-01
  • 2011-11-21
相关资源
最近更新 更多