简体   繁体   中英

How does prototypal inheritance in JavaScript really works?

I'm still not fully understanding the inheritance dychotomy (prototypal vs. classical) in JavaScript.

If the class is just a syntactic sugar over prototypes, how I'm supposed to de-sugar it?

May you show me the different approaches in creating React elements with classes and prototypes (ie without class & React.createClass )?

So, is there a way to get stateful component using native Object.create ?

Like this:

const Hello = Object.create(React.Component.prototype, {
  componentDidMount: {
    value: function() {
      alert('Mounted');
    }
  },
  render: {
    value: function() {
      return <div>I love StackOverflow community! It is so helpful and friendly</div>;
    }
  }
});

ReactDOM.render(<Hello />, document.getElementById('root'));

Seems something like this won't work because of inner lib's restrictions. But why we can't use it in more natural to prototypal nature of JavaScript?

There's a note from the official docs: https://facebook.github.io/react/docs/composition-vs-inheritance.html#so-what-about-inheritance

[...] we haven't found any use cases where we would recommend creating component inheritance hierarchies

But isn't class mostly about inheritance?

I'm very confused and would like to hear your opinions of what I am doing and thinking wrong?

I've asked that question at Reactiflux and Brendan Hurley proposed this: https://codepen.io/niechea/pen/xdVEvv?editors=0010

function MyComponent(props) {
  this.props = props;
  this.state = {
    clickCount: 0,
  };
}

MyComponent.prototype = Object.create(React.Component.prototype);

MyComponent.prototype.clickHandler = function() {
  this.setState({
    clickCount: this.state.clickCount + 1,
  });
}

MyComponent.prototype.render = function() {
  return (
    <div>
      <p>Hello, {this.props.name}.</p>
      <p>You have clicked {this.state.clickCount} time(s)!</p>
      <button onClick={this.clickHandler.bind(this)}>Click me</button>
    </div>
  );
}

ReactDOM.render(<MyComponent name="Bogdan" />, document.getElementById('app'));

Is his solution truly prototypal?


Here are some references:


* The question is mostly about inheritance, not about React. React here is just a reference.

If the class is just a syntactic sugar over prototypes, how I'm supposed to de-sugar it?

For example this is a good article on the matter. So if you have create an entity Animal using class :

class AnimalES6 {
    constructor(name) {
        this.name = name;
    }

    doSomething() {
        console.log("I'm a " + this.name);
    }
}

var lionES6 = new AnimalES6("Lion");
lionES6.doSomething();

Prototypal version would look something like this:

var Animal = (function () {
    function Animal(name) {
        this.name = name;
    }
    // Methods
    Animal.prototype.doSomething = function () {
        console.log("I'm a " + this.name);
    };
    return Animal;
})();

var lion = new Animal("Lion");
lion.doSomething();

It's even more complicated with extend functionality (eg TypeScript simulation of inheritance):

var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};

May you show me the different approaches in creating React elements with classes and prototypes (ie without class & React.createClass)?

There are also SO questions already answered for this question, eg this one .

But still, in my opinion the real question is: Do you want to?

Since you linked Eric Elliot's article you probably noticed that there is a kind of dispute around EC6's class es in javascript world. Apart from the examples you posted, there are some aggregates of opinions from more developers, eg this github repo , here and more. There are also articles defending the purpose of class ...

Anyway, the creators of React seem to have embraced the "evilness" of class es and as you pointed out you would bump into problems when trying to use prototypal approach with React. So in my opinion: why bother with it? I too like prototypal nature of javascript more and I also like the ReactJS framework, but in my opinion it would be better to come up with some new framework which combines the best of both, something like "Protypal React" rather than trying to enforce prototyping on React when it wasn't intended for such use.

But isn't class mostly about inheritance?

That was probably answered in the comments but no. Classes have their advantages as has Composition design concept over inheritance. It depends on the needs but many frameworks/libraries build on object-oriented languages which use class embrace composition over inheritance, eg Unity.

Anyway, really nice question and I wanted to share my thoughts on the matter too. Hopefully it will help you form an opinion.

So, like others have said, the best way to think about classes in Javascript is as syntactical sugar over prototypal inheritance. It's best to avoid the association of classical inheritance and classes in other languages, particularly if you have been taught it in university/school.

Prototypal inheritance can be considered to be more expressive than classical inheritance.

Using the 'class' keyword in JS is syntactically closer to classical inheritance.

For example, you have in your UI these components: Link, MediaLink, ImageLink & VideoLink. In classical inheritance, you might be tempted to have a Link class, a MediaLink class that extends Link, and VideoLink & ImageLink classes that extend MediaLink class where MediaLink is an abstract class (not to be instantiated). If you were to implement this hierarchy with stateful React components using prototypal inheritance, the states could theoretically be easily manipulated by calling super (or calling React.Component.apply(this, props); as in an answer above), however the core of React, ie 'render' would be a little abstract and difficult to extend. A link might return <a ...>link</a> , what would a MediaLink return? How would a VideoLink work with the return of its parent? Or do you begin to negate the parent rendering functions and replace them entirely? It gets awkward at this point and it begins to look a little like spaghetti.

Instead, you can compose components. In classical inheritance, you can think of your Component class as a final class that has inherited from an abstract class , React.Component. To implement the above, you add behaviour to your class that may be common to a lot of classes. For example, Components TextLink, VideoLink & ImageLink all have a boolean state 'clicked'. I think this article summarises these ideas pretty well.

Another way of composing components is by wrapping components with other components and passing state via props to their children. A really crude example might be: Link, SwitchedLink. The Link class has a prop 'active' which determines whether or not it is active. The Switched link will render either <Link /> or <Link active /> as a single child depending on its own state. The child is stateless. The parent has state. These are common patterns with React and thus why there is no need for a classical multiple level inheritance structure.

I hope that addresses your question.

Sorry i can't write any comment. I think you must initialize super class constructor below.

function MyComponent(props) {
  React.Component.prototype.apply(this, props);
  this.props = props;
  this.state = {
    clickCount: 0,
  };
}

In React, there are two types of component; the functional component and the class-based component. Here is a link to the react docs explaining the difference.

An interesting thing to note is that a Component in React is essentially a function that returns some amount of JSX. So you can write a function and have it return JSX and that would be a component but without key internal details like the lifecycle methods such as componentWillMount , this.props and this.state that the Class Component would provide to you out of the box.

So you can create a functional component (ie a function that returns JSX) and have prototypes in it like so

const Card = (props) => {
this.sayHello = function () {
  console.log('hello hello hello');
};

return (  
  <View style={styles.containerStyle}>
    {props.children}
    { this.sayHello() }
  </View>
 );
};

and this would pass for a Component created in React without the normal terminology you are used to.

Hope this is helpful

I think its important to break things down in easy to understand ways. You are asking about prototypal inheritance in JavaScript and then how it applies regarding React. First of all, I have to say that prototype is not easy to explain, nor is it well documented, but lets focus on one thing at a time. So yes, JavaScript does not have a concept of object inheritance, but instead prototypal inheritance. Lets create a function on cars:

function Car() {

}

We can then make a Toyota that will inherit from Car . So lets see what a constructor function looks like when not using classes.

function Car(options) {
  this.title = options.title;
}

So every single Car has to have options passed into it and this options has a title .

Now we can create a new car and pass it an options object of title like so:

const car = new Car({ title: 'Focus' });
car;

Now lets add a method on to this Car class, so we are adding this method to the prototype object of the constructor like so:

Car.prototype.drive = function() {
  return 'vroom!';
}

Now when we call car.drive(); we get a result of vroom! .

So this is basic object creation with JavaScript. We create the constructor object, then use the new keyword on it and then we can add methods to the object, but we add it on the prototype property of the constructor:

constructorFunction.prototype.methodWeAdd = function() {
  return something;
};

Now I will add an object that will inherit from Car to set up the prototypal link.

So I will make a Toyota and I want it to inherit from this Car object like so:

function Toyota(options) {

}

So I passed it an options object as an argument which contains the color of the car.

function Toyota(options) {
  this.color = options.color;
}

const toyota = new Toyota({ color: 'grey', title: 'Prius' });
console.log(toyota);

So I want the Toyota to inherit all the properties and methods of a Car because toyota is a Car . So how do I create a link between the two? How do I delegate toyota to be able to call all the methods that Car has?

So whenever I call Toyota the constructor I want to ensure I run any initialization that occurs in the Car as well like so:

function Toyota(options) {
  Car.call(this.options);
  this.color = options.color;
}

I also want to make sure I can call the Car drive method from a Toyota object like so:

Toyota.prototype = Object.create(Car.prototype);
Toyota.prototype.constructor = Toyota;

Now I should be able to add a method to Toyota prototype like so:

Toyota.prototype.honk = function() {
  return 'beep beep!';
};

Now I should be able to call the following:

const toyota = new Toyota({ color: 'grey', title: 'Prius' });
console.log(toyota);
console.log(toyota.drive());
console.log(toyota.honk());

So toyota should be inheriting some level of setup that is coming from Car . So the above painful process I just put myself through, is to say to you, this is how you would de-sugar it in plain vanilla JavaScript. Not sure why you would want to, but here it is.

So the above is how prototypal inheritance is done in JavaScript.

The idea of classes is not just for syntactic sugar for the sake of it, it's to remove such a painstaking process from our workload so we can focus on other challenges in application development.

When you look at new developments such as with ES6, yes some of it falls under syntactic sugar, but the idea is to remove or resolve a painstaking process from an everyday task, such as creating classes or accessing properties/methods from an object such as with destructuring.

In practice, the JavaScript community has embraced the use of classes, you will see a ton of libraries utilizing classes, whereas template strings and destructuring was a nice addition, classes has dramatically changed the way we write JavaScript code.

A lot of this is due to how difficult it was to write prototypal inheritance whereas with classes its a lot easier using the class and extend keywords.

When React first came out, the idea of using prototypal inheritance all over the place to create a component was considered laughable just because there is so much setup you have to do (as you saw above) whenever you are creating a subclass that inherits from another object. Instead, to create a component, we would use the createClass helper. This is a part of React, but I want to show you an instance of where some existing library migrated towards classes.

So it used to be the case that with React, we had this helper called createClass like so:

React.createClass({

});

And you would pass in an object and it might have a method like so:

React.createClass({
   doSomething() {

   },

   doSomethingElse() {

    }
});

This was not the worst thing in the world, but it definitely made it really hard whenever you wanted to subclass a component or create a component with some about of behavior and reuse it somewhere else. These days React has migrated towards using the class syntax instead, for example:

class MyComponent extends Component {
    doSomething() {

    }

    doSomethingElse() {

    }
}

The benefit of this approach is if I wanted to make some generic, very reusable component to utilize somewhere else in my codebase, I can do so much more easily with a class-based approach as opposed to using createClass() syntax and definitely much easier than the prototypal inheritance syntax way.

You will also notice the inheritance chain is much more obvious. So this Component keyword is a generic component that is provided by the React library.

Anyone who glances at this class can see that its creating a component and borrows functionality from Component .

The benefit of this is that if you are a fan of Object Oriented Programming and work with React, the class syntax is generally easier to work with than the older createClass() syntax.

Prototype inheritance is when you put a method on the original constructor here

function Cat(name,breed){
   this.name = name;
   this.breed = breed;
}

It will be inherited by the rest of them

const susie = new Cat('Susie', 'Persian');

If I create a new array, const names = ['Gilbert','Mia'] I just crated a brand new array, but now names.join() is a method names.pop)() is a method.

在此输入图像描述

We have all these methods on top of names. Where did they com from? Well, we have the mother Array capital A

If you look inside of it, you'll know that Array has many prototype methods,

在此输入图像描述

which means that when you create Cat

const susie = new Cat('Susie', 'Persian');

from the mother array or from the mother Cat

function Cat(name,breed){
   this.name = name;
   this.breed = breed;
}

every single instance of that inherits those methods.

 function Cat(name,breed){ this.name = name; this.breed = breed; } Cat.prototype.meow = function() { Console.log(`Meow meow! My name is ${this.name}`); } const susie = new Cat('Susie', 'Persian'); const mimi = new Cat('Mimi', 'Persian'); 

Now you can see the prototype Cat.prototype.meow is now inherited by every instance cat.

const susie = new Cat('Susie', 'Persian');
const mimi = new Cat('Mimi', 'Persian');

susie.meow(); -> Meow meow! My name is Susie mimi.meow(); -> Meow meow! My name is Mimi

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