【问题标题】:JS Inheritance and mutating prototypeJS 继承和变异原型
【发布时间】:2015-06-17 14:07:30
【问题描述】:

AFAIK,JS 通过将原型链分配给新创建的对象来提供继承。所以,下面的代码对我来说似乎是正确的方法:

function Animal(name){
    this.name = name;
}
Animal.prototype.getName = function(){return this.name;};
function Cat(){
    Animal.apply(this, arguments);
}

Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
Cat.prototype.sayHi = function(){return 'Hi, my name is '+this.name+', and I am a "'+this.constructor.name+'" instance';};

这真的正确吗?而且,我读过,变异对象的原型是一个缓慢的不鼓励操作,这会影响以后对它的所有调用。但是在这里我们只是改变了Animal.prototypeCat.prototype。那么,这很糟糕吗?如果是我们如何处理呢?或者我误解了关于原型变异警告的一些内容?如果是这样,它的实际含义是什么?

【问题讨论】:

  • 前段时间问了一个关于JS继承的问题:stackoverflow.com/questions/11661078/…。这不是同一个问题,但您可能会在回复中找到有用的信息。
  • 如果你最终改变了所有 Animal 试图将方法添加到 Cat 的原型中,那么改变原型将是一个问题。在这里你正确地使用了 Object.create() 所以你让所有的动物都一样。
  • 请注意,函数对象的 name 属性是对 ES5 的扩展(参见 ECMAScript ed 6 draft),不应依赖它(尽管它似乎被大多数浏览器的最新版本)。
  • “我已经读过,改变对象的原型是一个缓慢的不鼓励操作,它会影响以后对它的所有调用。”那是在什么背景下的?是的,如果你改变原型,它会影响共享原型的所有其他对象,但如果你不知道为什么要这样做,并且不是出于特定(有益的)目的而这样做,那只会是一件坏事.
  • @JoshBeam 片段的上下文,被你引用:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…

标签: javascript inheritance prototype


【解决方案1】:

这真的正确吗?

因为它不包含任何可能被视为不良做法的内容,是的。无法确定结果是否正确。

而且,我读过,改变对象的原型是一个缓慢的不鼓励操作,

我不知道那是什么意思。但是,修改不属于您的对象并不是一种好的做法,因此不要乱用内置对象或宿主对象(有很多关于为什么不这样做的文章,例如 What's wrong with extending the DOMExtending builtin natives. Evil or not?)。

这会影响以后对它的所有调用。

修改原型可能会影响所有将其作为[[Prototype]] 的对象,这就是原型继承的意义所在。

但是这里我们只是改变了 Animal.prototype 和 Cat.prototype。那么,这很糟糕吗?

就其本身而言,不。如果它达到了您要求的结果,那很好。您正在定义构造函数、原型属性和继承方案,这取决于您。可能有更高效或更容易维护的方案,但这是另一个话题。

评论

复杂的继承方案在 javascript 中很少有用,只是没有太多需要它。大多数内置对象只有一层或两层继承(例如,函数实例继承自 Function.prototype 和 Object.prototype)。宿主对象(例如 DOM 元素)可能有更长的链,但这是为了方便而不是真正需要(至少有一个非常流行的浏览器直到最近才实现宿主对象的原型继承)。

【讨论】:

    【解决方案2】:

    你在正确的轨道上。你不想改变Animal 的原型,因为这破坏了继承的概念。正如 cmets 中提到的,使用 Object.create() 是从一个对象继承属性和方法到另一个对象的正确方法。一个简单的原型继承示例,使用您的示例,是通过这种方式实现的:

    function Animal(name) {
      this.name = name;
    }
    
    Animal.prototype = {
      getName: function() {
        return this.name;
      }
    };
    
    function Cat(name, color) {
      Animal.call(this, name);
      this.color = color;
    }
    
    Cat.prototype = Object.create(Animal.prototype);
    Cat.prototype.constructor = Cat;
    Cat.prototype.getColor = function() {
      return this.color;
    };
    
    var animal = new Animal('Bob');
    var cat = new Cat('Tabby', 'black');
    
    console.log(cat.getName());
    console.log(cat.getColor());
    console.log(animal.getName());
    console.log(animal.getColor());  // throws an error
    

    【讨论】:

    • 我认为您没有理解这个问题。 OP 似乎将“改变对象的内部 [[proto]] 属性”(这很糟糕)与“改变函数的 prototype 属性”(这很好)混为一谈。我会否决您的回答,以消除它收到的误导性赞成票,但我更喜欢建设性的批评。
    【解决方案3】:

    您好,JavaScript 中的继承相当复杂,您必须对原型对象有一个很好的理解。我建议您使用例如 TypeScript 的继承模式。你可以试试Playground

    看看这个:

    var extends = this.extends || function (class, parent) {
        for (var property in parent) {
            if (parent.hasOwnProperty(property)) {
                class[property] = parent[property];
            }
        }
        function newClass() { 
            this.constructor = class;
        }
        newClass.prototype = parent.prototype;
        class.prototype = new newClass();
    };
    
    var Animal = (function () {
        function Animal(name) {
            this.name = name;
        }
        return Animal;
    })();
    
    var Cat = (function (_super) {
        extends(Cat, _super);
        function Cat(name) {
            _super.call(this, name);
        }
        Cat.prototype.sayHi = function () {
            return "Hello my name is:" + this.name;
        };
        return Cat;
    })(Animal);
    

    【讨论】:

    • 我看不出这个答案如何解决 OP 的问题。
    【解决方案4】:

    您似乎将函数的 prototype 属性与所有对象(包括函数)的内部 [[proto]] 属性混合在一起。它们是两个不同的东西。

    不鼓励改变对象的内部[[proto]] 属性。这可以通过setPrototypeOf 或通过继承自Object.prototype__proto__ 访问器来完成:

    var a = {}; // `a` inherits directly from `Object.prototype`
    var b = {}; // `b` inherits directly from `Object.prototype`
    
    a.__proto__ = b;             // `a` now inherits directly from `b`
    
    // or
    
    Object.setPrototypeOf(a, b); // `a` now inherits directly from `b`
    

    这是不鼓励的,在对象创建后改变其内部[[proto]] 属性。但是,应该注意的是,在创建对象时,可以将任何对象分配为其内部 [[proto]] 属性。

    例如,许多 JavaScript 程序员希望创建从 Function.prototype 以外的某个对象继承的函数。这目前只能通过在创建函数后更改函数的内部 [[proto]] 属性来实现。但是,in ES6 you will be able to assign the internal [[proto]] property of an object when it is created(从而避免了改变对象内部[[proto]] 属性的问题)。

    例如,考虑这个糟糕的代码:

    var callbackFunctionPrototype = {
        describe: function () {
            alert("This is a callback.");
        }
    };
    
    var callback = function () {
        alert("Hello World!");
    };
    
    callback.__proto__ = callbackFunctionPrototype; // mutating [[proto]]
    
    callback.describe();
    

    在 ES6 中可以改写如下:

    var callbackFunctionPrototype = {
        describe: function () {
            alert("This is a callback.");
        }
    };
    
    // assigning [[proto]] at the time of creation, no mutation
    
    var callback = callbackFunctionPrototype <| function () {
        alert("Hello World!");
    };
    
    callback.describe();
    

    因此,改变对象的内部[[proto]] 属性(使用setPrototypeOf__proto__ 访问器)是不好的。但是,修改函数的prototype 属性是可以的。

    函数的prototype 属性(我们称之为函数F)只影响new F 创建的对象。例如:

    var myPrototype = {
        describe: function () {
            alert("My own prototype.");
        }
    };
    
    function F() {}
    
    var a = new F; // inherits from the default `F.prototype`
    
    alert(a.constructor === F); // true -  inherited from `F.prototype`
    
    F.prototype = myPrototype; // not mutating the `[[proto]]` of any object
    
    var b = new F; // inherits from the new `F.prototype` (i.e. `myPrototype`)
    
    b.describe();
    

    要详细了解 JavaScript 中的继承,请阅读以下问题的答案:

    JavaScript inheritance and the constructor property

    希望对您有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-08-06
      • 1970-01-01
      • 2022-10-13
      • 2014-09-21
      • 2013-05-20
      • 2013-07-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多