【问题标题】:Confused about JavaScript prototypal inheritance对 JavaScript 原型继承感到困惑
【发布时间】:2010-02-02 08:26:15
【问题描述】:

在《JavaScript 权威指南 5 版》一书中,第 9.2 节 Prototypes and Inheritance 中,我找到了以下文字:

在上一节中,我展示了 new 运算符创建一个新的、空的 对象,然后调用构造函数 函数作为该对象的方法。 这不是完整的故事, 然而。创建空后 对象,new 设置那个的原型 目的。对象的原型是 原型属性的值 它的构造函数。全部 函数具有原型属性 这是自动创建的,并且 函数执行时初始化 定义。 初始值 原型属性是一个对象 单一财产。该物业是 命名构造函数并返回 构造函数 原型是关联的。(你 可能会想起构造函数属性 从第 7 章开始;这就是为什么每个 对象具有构造函数属性。) 您添加到此的任何属性 原型对象似乎是 初始化对象的属性 构造函数。

现在,如果这是真的,那么原型继承怎么会存在呢?我的意思是,假设构造函数的原型对象最初具有构造函数属性。因为原型对象本身就是一个对象,所以我们经常使用prototype_object.constructor来确定它的构造函数。但是现在prototype_object 本身已经有一个constructor 属性,它指向原型关联的构造函数。在这种情况下,继承怎么可能存在?

【问题讨论】:

    标签: javascript inheritance prototype prototypal-inheritance


    【解决方案1】:

    .constructor 属性实际上并不重要,并且与从 JavaScript 中的其他对象继承关系不大。它只是一个对象构造函数的方便句柄。

    例如,如果您有某事物的一个实例,并且您想创建该事物的另一个实例,但您没有直接处理它的构造函数,您可以执行以下操作:

    const myCar = new Racecar();
    console.log(myCar.constructor); // [Function: Racecar]
    
    const car2 = new myCar.constructor();
    console.log(car2.constructor); // [Function: Racecar]
    

    了解.constructor 属性和对象的类不是同义词很重要。正如您可能已经猜到的那样,.constructor 属性是动态的,就像 JavaScript 中的大多数其他东西一样,所以它不应该用于类型检查等任何事情。

    了解.constructor 属性并不意味着某事物是其他事物的子类也很重要。事实上,在 JavaScript 中没有可靠的方法来确定某事物是否是其他事物的子类。因为它是一种动态语言,并且因为有很多方法可以从其他对象继承属性(包括在实例化后从其他对象复制属性),所以 JavaScript 中不像其他语言那样存在类型安全子类。

    了解某项是否兼容类型的最佳方法是对属性进行特性测试。也就是鸭式。

    instanceof 运算符忽略 .constructor 属性。相反,它会检查构造函数的.prototype 是否存在于对象的原型链中(通过身份检查)。

    使用手动构造函数,继承会混淆.constructor 属性连接(使其引用错误的构造函数)。您可以通过手动连接连接来修复它。例如,在 ES5 中这样做的规范方式是这样的:

    function Car () {}
    
    console.log(Car.prototype.constructor); // Car
    
    function Racecar () {}
    
    Racecar.prototype = Object.create(Car.prototype);
    // To preserve the same relationship we have with the Car
    // constructor, we'll need to reassign the .prototype.constructor:
    Racecar.prototype.constructor = Racecar;
    
    var myCar = new Racecar();
    console.log(myCar.constructor); // [Function: Racecar]
    

    ES6 类会自动为您执行此操作:

    // ES6
    class Car {}
    class Racecar extends Car {}
    
    const myCar = new Racecar();
    console.log(myCar.constructor); // [Function: Racecar]
    

    也就是说,我不是构造函数或 ES6 类的忠实粉丝,而且我通常很少使用 .constructor 属性。为什么?因为工厂函数更灵活、更强大,而且它们没有与构造函数和类继承相关的陷阱。见"Factory Functions vs Constructor Functions vs Classes"

    【讨论】:

      【解决方案2】:

      比方说,狗是哺乳动物。

      function Mammal() {
        this.milk = true;
      };
      
      

      function Dog() { this.bark = true; } Dog.prototype = new Mammal;

      所以 Dog 的原型指向 哺乳动物的对象。这个 Mammal 对象有一个对其构造函数的引用,所以当 Dog 是新的时,JavaScript 会看到 Dog 原型是一个 Mammal,所以调用 Mammal 的构造函数来生成一个有效的 Mammal 对象(另一个),然后使用 Dog 构造函数使其成为 Dog 对象。

      由此,Dog.prototype 的构造函数是 Mammal(添加了额外字段和函数的哺乳动物对象)但 Dog 的构造函数是 Dog。继承的存在是因为 Dog 的一个实例有一个 Mammal 作为原型;因此,狗是哺乳动物。当一个方法被调用,而 JS 在Dog.prototype 中找不到它时,JS 会去Mammal.prototype(这是一个添加了额外字段和函数的 Object)。

      希望这会有所帮助。

      【讨论】:

      • 不幸的是,根据您编写的代码,var d = new Dog(); if(d.constructor === Dog) console.log('yes!') else console.log('oh noes!') 将记录 oh noes!。这是因为你已经用Mammal 对象覆盖了Dog.prototype,所以d.constructor 将指向Mammal。使.constructor 不可靠的常见陷阱。
      • Dog.prototype = new Mammal; 是众所周知的反模式。
      【解决方案3】:

      不用担心构造函数属性——它无关紧要。 跳过这些句子,你可能会更好地理解它。

      如果你仍然不确定你的理解,谷歌 __proto__ - JS 对象的内部原型引用。它甚至暴露在 Firefox 和 Safari 上的脚本中。

      一个很好的参考是https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/The_Employee_Example/Object_Properties/Inheriting_Properties

      【讨论】:

        【解决方案4】:

        如果你有一个对象obj,它的原型是obj.prototype,引用obj的构造函数的构造函数属性是obj.prototype.constructor

        对于对象obj.prototype,情况也是如此。假设proto = obj.prototype,那么proto 的构造函数的引用将在proto.prototype.constructor 中找到。

        这个和obj.prototype.prototype.constructor一样,所以和obj.prototype.constructor没有冲突。

        【讨论】:

        • obj.prototype 不是obj 的原型。如果obj = new XYZ 那么XYZ.prototypeobj 的原型。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多