MDN wants to learn about developers like you: https://www.surveygizmo.com/s3/5171903/MDN-Learn-Section-Survey-Recruiter-Pathway

Esta traducción está incompleta. Por favor, ayuda a traducir este artículo del inglés.

Con la mayoría de los detalles internos de OOJS (JavaScript Orientado a Objetos) explicados, este artículo muestra cómo crear clases "hijo" (constructores) que heredan características de sus clases "padre". Además, presentamos algunos consejos sobre cuándo y dónde puedes usar OOJS y cómo se crean las clases con la sintaxis moderna de ECMAScript.

Pre-requisitos: Conocimientos básicos de informática, una comprensión básica de HTML y CSS, familiaridad con los principios básicos de JavaScript (ver Primeros pasos y Construyendo bloques) y conceptos básicos de OOJS (ver Introduccion a objetos).
Objetivo: Entender cómo es posible implementar la herencia en JavaScript.

Herencia prototípica

Hasta ahora hemos visto algo de herencia en acción — hemos visto cómo funcionan las cadenas de prototipos, y cómo los miembros son heredados subiendo una cadena. Pero principalmente esto ha involucrado funciones integradas del navegador. ¿Cómo creamos un objeto en JavaScript que hereda de otro objeto?

Exploremos cómo hacer esto con un ejemplo concreto.

Primeros pasos

Primero que nada, hazte una copia local de nuestro archivo  oojs-class-inheritance-start.html (míralo corriendo en vivo también). Dentro de aquí encontrarás el mismo ejemplo de constructor de Persona() que hemos estado usando a través del módulo, con una ligera diferencia — hemos definido solo las propiedades dentro del constructor:

function Persona(nombrePila, apellido, edad, genero, intereses) {
  this.nombre = {
    nombrePila,
    apellido
  };
  this.edad = edad;
  this.genero = genero;
  this.intereses = intereses;
};

Todos los métodos están definidos en el prototipo del constructor. Por ejemplo:

Persona.prototype.saludo = function() {
  alert('¡Hola! soy ' + this.nombre.nombrePila + '.');
};

Nota: En el código fuente, también verá los métodos bio() y despedida() definidos. Más tarde verá cómo estos pueden ser heredados por otros constructores.

Digamos que quisieramos crear una clase de Profesor, como la que describimos en nuestra definición inicial orientada a objetos, que hereda todos los miembros de Persona, pero también incluye:

  1. Una nueva propiedad, materia — esto contendrá la materia que el profesor enseña.
  2. Un método actualizado de saludo(), que suena un poco más formal que el método estándar de saludo() — más adecuado para un profesor que se dirige a algunos estudiantes en la escuela.

Definiendo un constructor Profesor()

Lo primero que tenemos que hacer es crear el constructor Profesor()  — añadimos lo siguiente tras el código existente:

function Profesor(nombrePila, apellido, edad, genero, intereses, materia) {
  Person.call(this, nombrePila, apellido, edad, genero, intereses);

  this.materia = materia;
}

 

Esto es similar al constructor de Persona en muchos aspectos, pero hay algo extraño aquí que no hemos visto antes: la función call (). Esta función básicamente le permite llamar a una función definida en otro lugar, pero en el contexto actual.
El primer parámetro especifica el valor de this que desea utilizar al ejecutar la función, y los otros parámetros son aquellos que deben pasarse a la función cuando se invoca.

Queremos que el constructor Profesor() tome los mismos parámetros que el constructor Persona() del que está heredando, por lo que los especificamos todos como parámetros en la invocación call().

La última línea dentro del constructor simplemente define la nueva propiedad subject que los profesores tendrán y que las personas genéricas no tienen.

Como nota, podríamos haber simplemente hecho esto:

 

function Profesor(nombrePila, apellido, edad, genero, intereses, materia) {
  this.nombre = {
    nombrePila,
    apellido
  };
  this.edad = edad;
  this.genero = genero;
  this.intereses = intereses;
  this.materia = materia;
}

Pero esto es solo definir las propiedades de nuevo, no heredarlas de Persona(), por lo que anula el punto de lo que estamos tratando de hacer. También lleva más líneas de código.

Heredando de un constructor sin parámetros

Nótese que si el constructor del cual se está heredando no toma los valores de sus propiedades de parámetros, no se necesita especificarlos como argumentos adicionales en call(). Por ejemplo, si se tuviera algo muy simple como esto:

function Brick() {
  this.width = 10;
  this.height = 20;
}

Se podrían heredar las propiedades width y height haciendo esto (así como los otros pasos descritos a continuación, por supuesto):

function BlueGlassBrick() {
  Brick.call(this);

  this.opacity = 0.5;
  this.color = 'blue';
}

Nótese que solo especificamos this dentro de call() — no se requieren otros parámetros ya que no estamos heredando ninguna propiedad del padre que sea establecida por parámetros.

Estableciendo el prototipo de Teacher() y su referencia al constructor

Todo va bien hasta ahora, pero tenemos un problema. Definimos un nuevo constructor, y tiene una propiedad prototype, la cual por defecto solo contiene una referencia a la función constructor en sí misma. No contiene los métodos de la propiedad prototype del constructor Persona. Para ver esto, introduzca Object.getOwnPropertyNames(Profesor.prototype) ya sea en el campo de texto o en la consola de Javascript . Introdúzcalo nuevamente, reemplazando Profesor con Persona. El nuevo constructor tampoco hereda esos métodos. Para ver esto, compare los resultados de Persona.prototype.saludo and Profesor.prototype.saludo. Necesitamos obtener Profesor() para obtener los métodos definidos en el prototipo de Persona(). ¿Cómo lo hacemos?

  1. Añade la siguiente línea debajo de tu adición anterior:
    Teacher.prototype = Object.create(Person.prototype);
    Here our friend create() comes to the rescue again. In this case we are using it to create a new object and make it the value of Teacher.prototype. The new object has Person.prototype as its prototype and will therefore inherit, if and when needed, all the methods available on Person.prototype.
  2. We need to do one more thing before we move on. After adding the last line, Teacher.prototype's constructor property is now equal to Person(), because we just set Teacher.prototype to reference an object that inherits its properties from Person.prototype! Try saving your code, loading the page in a browser, and entering Teacher.prototype.constructor into the console to verify.
  3. This can become a problem, so we need to set this right. You can do so by going back to your source code and adding the following line at the bottom:
    Teacher.prototype.constructor = Teacher;
  4. Now if you save and refresh, entering Teacher.prototype.constructor should return Teacher(), as desired, plus we are now inheriting from Person()!

Giving Teacher() a new greeting() function

To finish off our code, we need to define a new greeting() function on the Teacher() constructor.

The easiest way to do this is to define it on Teacher()'s prototype — add the following at the bottom of your code:

Teacher.prototype.greeting = function() {
  var prefix;

  if (this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
    prefix = 'Mr.';
  } else if (this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
    prefix = 'Mrs.';
  } else {
    prefix = 'Mx.';
  }

  alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
};

This alerts the teacher's greeting, which also uses an appropriate name prefix for their gender, worked out using a conditional statement.

Trying the example out

Now that you've entered all the code, try creating an object instance from Teacher() by putting the following at the bottom of your JavaScript (or something similar of your choosing):

var teacher1 = new Teacher('Dave', 'Griffiths', 31, 'male', ['football', 'cookery'], 'mathematics');

Now save and refresh, and try accessing the properties and methods of your new teacher1 object, for example:

teacher1.name.first;
teacher1.interests[0];
teacher1.bio();
teacher1.subject;
teacher1.greeting();
teacher1.farewell();

These should all work just fine. The queries on lines 1, 2, 3, and 6 access members inherited from the generic Person() constructor (class). The query on line 4 accesses a member that is available only on the more specialized Teacher() constructor (class). The query on line 5 would have accessed a member inherited from Person(), except for the fact that Teacher() has its own member with the same name, so the query accesses that member.

Note: If you have trouble getting this to work, compare your code to our finished version (see it running live also).

The technique we covered here is not the only way to create inheriting classes in JavaScript, but it works OK, and it gives you a good idea about how to implement inheritance in JavaScript.

You might also be interested in checking out some of the new ECMAScript features that allow us to do inheritance more cleanly in JavaScript (see Classes). We didn't cover those here, as they are not yet supported very widely across browsers. All the other code constructs we discussed in this set of articles are supported as far back as IE9 or earlier, and there are ways to achieve earlier support than that.

A common way is to use a JavaScript library — most of the popular options have an easy set of functionality available for doing inheritance more easily and quickly. CoffeeScript for example provides class, extends, etc.

A further exercise

In our OOP theory section, we also included a Student class as a concept, which inherits all the features of Person, and also has a different greeting() method from Person that is much more informal than the Teacher's greeting. Have a look at what the student's greeting looks like in that section, and try implementing your own Student() constructor that inherits all the features of Person(), and implements the different greeting() function.

Note: If you have trouble getting this to work, have a look at our finished version (see it running live also).

Object member summary

To summarize, you've basically got three types of property/method to worry about:

  1. Those defined inside a constructor function that are given to object instances. These are fairly easy to spot — in your own custom code, they are the members defined inside a constructor using the this.x = x type lines; in built in browser code, they are the members only available to object instances (usually created by calling a constructor using the new keyword, e.g. var myInstance = new myConstructor()).
  2. Those defined directly on the constructor themselves, that are available only on the constructor. These are commonly only available on built-in browser objects, and are recognized by being chained directly onto a constructor, not an instance. For example, Object.keys().
  3. Those defined on a constructor's prototype, which are inherited by all instances and inheriting object classes. These include any member defined on a Constructor's prototype property, e.g. myConstructor.prototype.x().

If you are not sure which is which, don't worry about it just yet — you are still learning, and familiarity will come with practice.

ECMAScript 2015 Classes

ECMAScript 2015 introduces class syntax to JavaScript as a way to write reusable classes using easier, cleaner syntax, which is more similar to classes in C++ or Java. In this section we'll convert the Person and Teacher examples from prototypal inheritance to classes, to show you how it's done.

Note: This modern way of writing classes is supported in all modern browsers, but it is still worth knowing about how the underlying prototypal inheritance in case you work on a project that requires supporting a browser that doesn't support this syntax (most notably Internet Explorer).

Let's look at a rewritten version of the Person example, class-style:

class Person {
  constructor(first, last, age, gender, interests) {
    this.name = {
      first,
      last
    };
    this.age = age;
    this.gender = gender;
    this.interests = interests;
  }

  greeting() {
    console.log(`Hi! I'm ${this.name.first}`);
  };

  farewell() {
    console.log(`${this.name.first} has left the building. Bye for now!`);
  };
}

The class statement indicates that we are creating a new class. Inside this block, we define all the features of the class:

  • The constructor() method defines the constructor function that represents our Person class.
  • greeting() and farewell() are class methods. Any methods you want associated with the class are defined inside it, after the contructor. In this example, we've used template literals rather than string concatenation to make the code easier to read.

We can now instantiate object instances using the new operator, in just the same way as we did before:

let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);
han.greeting();
// Hi! I'm Han

let leia = new Person('Leia', 'Organa', 19, 'female' ['Government']]);
leia.farewell();
// Leia has left the building. Bye for now

Note: Under the hood, your classes are being converted into prototypal Inheritance models — this is just syntactic sugar. But I'm sure you'll agree that it's easier to write.

Inheritance with class syntax

Above we created a class to represent a person. They have a series of attributes that are common to all people; in this section we'll create our specialized Teacher class, making it inherit from Person using modern class syntax. This is called creating a subclass or subclassing.

To create a subclass we use the extends keyword to tell JavaScript the class we want to base our class on.

class Teacher extends Person {
  constructor(first, last, age, gender, interests, subject, grade) {
    this.name = {
      first,
      last
    };

  this.age = age;
  this.gender = gender;
  this.interests = interests;
  // subject and grade are specific to Teacher
  this.subject = subject;
  this.grade = grade;
  }
}

We can make the code more readable by defining the super() operator as the first item inside the constructor(). This will call the parent class' constructor, and inherit the members we specify as parameters of super(), as long as they are defined there:

class Teacher extends Person {
  constructor(first, last, age, gender, interests, subject, grade) {
    super(first, last, age, gender, interests);

    // subject and grade are specific to Teacher
    this.subject = subject;
    this.grade = grade;
  }
}

When we instantiate Teacher object instances, we can now call methods and properties defined on both Teacher and Person, as we'd expect:

let snape = new Teacher('Severus', 'Snape', 58, 'male', ['Potions'], 'Dark arts', 5);
snape.greeting(); // Hi! I'm Severus.
snape.farewell(); // Severus has left the building. Bye for now.
snape.age // 58
snape.subject; // Dark arts

Like we did with Teachers, we could create other subclasses of Person to make them more specialized without modifying the base class.

Note: You can find this example on GitHub as es2015-class-inheritance.html (see it live also).

Getters and Setters

There may be times when we want to change the values of an attribute in the classes we create or we don't know what the final value of an attribute will be. Using the Teacher example, we may not know what subject the teacher will teach before we create them, or their subject may change between terms.

We can handle such situations with getters and setters.

Let's enhance the Teacher class with getters and setters. The class starts the same as it was the last time we looked at it.

Getters and setters work in pairs. A getter returns the current value of the variable and its corresponding setter changes the value of the variable to the one it defines.

The modified Teacher class looks like this:

class Teacher extends Person {
  constructor(first, last, age, gender, interests, subject, grade) {
    super(first, last, age, gender, interests);
    // subject and grade are specific to Teacher
    this._subject = subject;
    this.grade = grade;
  }

  get subject() {
    return this._subject;
  }

  set subject(newSubject) {
    this._subject = newSubject;
  }
}

In our class above we have a getter and setter for the subject property. We use _  to create a separate value in which to store our name property. Without using this convention, we would get errors every time we called get or set. At this point:

  • To show the current value of the _subject property of the snape object we can use the snape.subject getter method.
  • To assign a new value to the _subject property we can use the snape.subject="new value" setter method.

The example below shows the two features in action:

// Check the default value
console.log(snape.subject) // Returns "Dark arts"

// Change the value
snape.subject="Balloon animals" // Sets _subject to "Balloon animals"

// Check it again and see if it matches the new value
console.log(snape.subject) // Returns "Balloon animals"

Note: You can find this example on GitHub as es2015-getters-setters.html (see it live also).

When would you use inheritance in JavaScript?

Particularly after this last article, you might be thinking "woo, this is complicated". Well, you are right. Prototypes and inheritance represent some of the most complex aspects of JavaScript, but a lot of JavaScript's power and flexibility comes from its object structure and inheritance, and it is worth understanding how it works.

In a way, you use inheritance all the time. Whenever you use various features of a Web API , or methods/properties defined on a built-in browser object that you call on your strings, arrays, etc., you are implicitly using inheritance.

In terms of using inheritance in your own code, you probably won't use it often, especially to begin with, and in small projects. It is a waste of time to use objects and inheritance just for the sake of it when you don't need them. But as your code bases get larger, you are more likely to find a need for it. If you find yourself starting to create a number of objects that have similar features, then creating a generic object type to contain all the shared functionality and inheriting those features in more specialized object types can be convenient and useful.

Note: Because of the way JavaScript works, with the prototype chain, etc., the sharing of functionality between objects is often called delegation. Specialized objects delegate functionality to a generic object type.

When using inheritance, you are advised to not have too many levels of inheritance, and to keep careful track of where you define your methods and properties. It is possible to start writing code that temporarily modifies the prototypes of built-in browser objects, but you should not do this unless you have a really good reason. Too much inheritance can lead to endless confusion, and endless pain when you try to debug such code.

Ultimately, objects are just another form of code reuse, like functions or loops, with their own specific roles and advantages. If you find yourself creating a bunch of related variables and functions and want to track them all together and package them neatly, an object is a good idea. Objects are also very useful when you want to pass a collection of data from one place to another. Both of these things can be achieved without use of constructors or inheritance. If you only need a single instance of an object, then you are probably better off just using an object literal, and you certainly don't need inheritance.

Alternativas para extender la cadena del prototipos

En JavaScript, hay varias maneras diferentes de extender el prototipo de un objeto aparte de lo que hemos mostrado anteriormente. Para saber más sobre las otras formas, visite nuestro artículo Herencia y la cadena de prototipos.

Resumen

Este artículo ha cubierto el resto de la teoría y sintaxis central de OOJS que creemos que debería conocer ahora. En este punto debe entender los conceptos básicos de objetos JavaScript y POO, prototipos y herencia de prototipos, cómo crear clases (constructores) e instancias de objetos, añadir características a las clases y crear subclases que heredan de otras clases.

En el siguiente artículo veremos cómo trabajar con JavaScript Object Notation (JSON), un formato común de intercambio de datos escrito con objetos JavaScript.

Véase también

  • ObjectPlayground.com — A really useful interactive learning site for learning about objects.
  • Secrets of the JavaScript Ninja, Chapter 6 — A good book on advanced JavaScript concepts and techniques, by John Resig and Bear Bibeault. Chapter 6 covers aspects of prototypes and inheritance really well; you can probably track down a print or online copy fairly easily.
  • You Don't Know JS: this & Object Prototypes — Part of Kyle Simpson's excellent series of JavaScript manuals, Chapter 5 in particular looks at prototypes in much more detail than we do here. We've presented a simplified view in this series of articles aimed at beginners, whereas Kyle goes into great depth and provides a more complex but more accurate picture.

 

In this module

 

Etiquetas y colaboradores del documento

Colaboradores en esta página: ivanagui2, mdnwebdocs-bot, cvillafraz, Adrian-Cuellar, B1tF8er
Última actualización por: ivanagui2,