简体   繁体   中英

JS Inheritance and mutating prototype

AFAIK, JS provides inheriatnce by means of assigning a prototype chain to a newly created object. So, the code below seems to be the correct way to me:

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';};

Is this actually correct? And, I've read, that mutating object's prototype is a slow discouraged operation, that affects all the later calls to it. But here we've just mutated Animal.prototype and Cat.prototype . So, is that bad? If it is how do we deal with it? Or I've misunderstood something about that prototype mutating warning? If so, what does it actually mean?

You're on the right track. You don't want to mutate Animal 's prototype because that breaks the concept of inheritance. As was mentioned in the comments, using Object.create() is the correct way to inherit properties and methods from one object to another. A simple example of prototypical inheritance, using your example, is achieved this way:

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

Is this actually correct?

In that it doesn't contain anything that might be considered poor practice, yes. Whether it's correct in terms of the outcome being what you expected can't be determined.

And, I've read, that mutating object's prototype is a slow discouraged operation,

I have no idea what that means. However, it's not considered good practice to modify objects you don't own, so don't go messing with built–in or host objects (there are many articles on why not, eg What's wrong with extending the DOM and Extending builtin natives. Evil or not? ).

that affects all the later calls to it.

Modifying a prototype may affect all objects that have it as their [[Prototype]] , that's the point of prototype inheritance.

But here we've just mutated Animal.prototype and Cat.prototype. So, is that bad?

Of itself, no. If it achieves the outcome you require, then fine. You're defining the constructor, prototype properties and inheritance scheme, so it's up to you. There might be more efficient or more easily maintained schemes, but that's another topic.

Comment

Complex inheritance schemes are rarely useful in javascript, there just isn't much call for it. Most built–in objects have only one or two levels of inheritance (eg function instances inherit from Function.prototype and Object.prototype). Host objects (eg DOM elements) may have longer chains, but that is for convenience and not really necessary (at least one very popular browser didn't implement prototype inheritance for host objects until recently).

Hi inheritance in JavaScript is rather complex and you will have to have a good understanding of the prototype object. Ill suggest you use the inheritance pattern of for instance TypeScript. You can give it a try on the Playground

Take a look at this:

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);

You seem to be mixing the prototype property of functions with the internal [[proto]] property of all objects (including functions). They are two distinct things.

Mutating the internal [[proto]] property of an object is discouraged. This can be done either via setPrototypeOf or via the __proto__ accessor, which is inherited from Object.prototype :

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`

This is what is discouraged, mutating the internal [[proto]] property of an object after it has been created. However, it should be noted that while the object is being created it's alright to assign any object as its internal [[proto]] property.

For example, many JavaScript programmers wish to create functions which inherit from some object other than Function.prototype . This is currently only possible by mutating the internal [[proto]] property of a function after it has been created. However, in ES6 you will be able to assign the internal [[proto]] property of an object when it is created (thus avoiding the problem of mutating the internal [[proto]] property of the object).

For example, consider this bad code:

var callbackFunctionPrototype = {
    describe: function () {
        alert("This is a callback.");
    }
};

var callback = function () {
    alert("Hello World!");
};

callback.__proto__ = callbackFunctionPrototype; // mutating [[proto]]

callback.describe();

It can be rewritten in ES6 as follows:

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();

So, mutating the internal [[proto]] property of an object (using either setPrototypeOf or the __proto__ accessor) is bad. However, modifying the prototype property of a function is fine.

The prototype property of a function (let's call the function F ) only affects the objects created by new F . For example:

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();

To know more about inheritance in JavaScript read the answer to the following question:

JavaScript inheritance and the constructor property

Hope that helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM