Todo lo que necesita saber para comprender el prototipo de JavaScript

La mayoría de las veces, el prototipo de JavaScript confunde a las personas que acaban de empezar a aprender JavaScript, especialmente si tienen experiencia en C ++ o Java.

En JavaScript, la herencia funciona de manera un poco diferente en comparación con C ++ o Java. La herencia de JavaScript se conoce más ampliamente como "herencia prototípica".

Las cosas se vuelven más difíciles de entender cuando también se encuentra classen JavaScript. La nueva classsintaxis se parece a C ++ o Java, pero en realidad funciona de manera diferente.

En este artículo, intentaremos comprender la "herencia de prototipos" en JavaScript. También examinamos la nueva classsintaxis basada e intentamos comprender qué es realmente. Entonces empecemos.

Primero, comenzaremos con la función y el prototipo de JavaScript de la vieja escuela.

Comprender la necesidad de un prototipo

Si alguna vez ha trabajado con matrices, objetos o cadenas de JavaScript, habrá notado que hay un par de métodos que están disponibles de forma predeterminada.

Por ejemplo:

var arr = [1,2,3,4];arr.reverse(); // returns [4,3,2,1]
var obj = {id: 1, value: "Some value"};obj.hasOwnProperty('id'); // returns true
var str = "Hello World";str.indexOf('W'); // returns 6

¿Alguna vez te has preguntado de dónde vienen estos métodos? No ha definido estos métodos por su cuenta.

¿Puedes definir tus propios métodos como este? Podrías decir que puedes de esta manera:

var arr = [1,2,3,4];arr.test = function() { return 'Hi';}arr.test(); // will return 'Hi'

Esto funcionará, pero solo para esta variable llamada arr. Digamos que tenemos otra variable llamada y arr2luego arr2.test()arrojará un error “TypeError: arr2.test no es una función”.

Entonces, ¿cómo esos métodos están disponibles para todas y cada una de las instancias de matriz / cadena / objeto? ¿Puedes crear tus propios métodos con el mismo comportamiento? La respuesta es sí. Debes hacerlo de la manera correcta. Para ayudar con esto, viene el prototipo de JavaScript.

Primero veamos de dónde provienen estas funciones. Considere el fragmento de código a continuación:

var arr1 = [1,2,3,4];var arr2 = Array(1,2,3,4);

Hemos creado dos matrices de dos formas diferentes: arr1con matrices literales y arr2con Arrayfunción de constructor. Ambos son equivalentes entre sí con algunas diferencias que no importan para este artículo.

Ahora, llegando a la función de constructor Array, es una función de constructor predefinida en JavaScript. Si abre las herramientas de desarrollador de Chrome y va a la consola y escribe console.log(Array.prototype)y presiona enter, verá algo como a continuación:

Allí verá todos los métodos sobre los que nos estábamos preguntando. Así que ahora sabemos de dónde vienen esas funciones. Siéntete libre de probar con String.prototypey Object.prototype.

Creemos nuestra propia función constructora simple:

var foo = function(name) { this.myName = name; this.tellMyName = function() { console.log(this.myName); }}
var fooObj1 = new foo('James');fooObj1.tellMyName(); // will print Jamesvar fooObj2 = new foo('Mike');fooObj2.tellMyName(); // will print Mike

¿Puede identificar un problema fundamental con el código anterior? El problema es que estamos desperdiciando memoria con el enfoque anterior. Tenga en cuenta que el método tellMyNamees el mismo para todas y cada una de las instancias de foo. Cada vez que creamos una instancia del foométodo tellMyNametermina ocupando espacio en la memoria del sistema. Si tellMyNamees igual para todas las instancias es mejor guardarlo en un solo lugar y hacer que todas nuestras instancias se refieran desde ese lugar. Veamos cómo hacer esto.

var foo = function(name) { this.myName = name;}
foo.prototype.tellMyName = function() { console.log(this.myName);}
var fooObj1 = new foo('James');fooObj1.tellMyName(); // will print Jamesvar fooObj2 = new foo('Mike');fooObj2.tellMyName(); // will print Mike

Comprobemos la diferencia con el enfoque anterior y el enfoque anterior. Con el enfoque anterior, si console.dir()ve las instancias, verá algo como esto:

Tenga en cuenta que como propiedad de las instancias solo tenemos myname. tellMyNamese define en __proto__. Llegaré a esto __proto__después de algún tiempo. Lo más importante es que tenga en cuenta que la comparación tellMyNamede ambas instancias da como resultado verdadero. La comparación de funciones en JavaScript se evalúa como verdadera solo si sus referencias son las mismas. Esto prueba que tellMyNameno consume memoria extra para varias instancias.

Veamos lo mismo con el enfoque anterior:

Tenga en cuenta que este tiempo tellMyNamese define como una propiedad de las instancias. Ya no está debajo de eso __proto__. Además, tenga en cuenta que esta vez la comparación de funciones se evalúa como falsa. Esto se debe a que están en dos ubicaciones de memoria diferentes y sus referencias son diferentes.

Espero que a estas alturas comprenda la necesidad de prototype.

Ahora veamos más detalles sobre el prototipo.

Todas y cada una de las funciones de JavaScript tendrán una prototypepropiedad del tipo de objeto. Puede definir sus propias propiedades en prototype. Cuando utilice la función como función constructora, todas sus instancias heredarán propiedades del prototypeobjeto.

Ahora vayamos a esa __proto__propiedad que vio arriba. El __proto__es simplemente una referencia al objeto prototipo de la que la instancia ha heredado. ¿Suena complicado? En realidad, no es tan complicado. Visualicemos esto con un ejemplo.

Considere el siguiente código. Ya sabemos que la creación de una matriz con literales de matriz heredará propiedades de Array.prototype.

var arr = [1, 2, 3, 4];

Lo que acabo de decir arriba es " El __proto__es simplemente una referencia al objeto prototipo del cual la instancia ha heredado ". Entonces arr.__proto__debería ser lo mismo con Array.prototype. Verifiquemos esto.

Ahora no deberíamos acceder al objeto prototipo con __proto__. Según MDN, __proto__se desaconseja el uso y es posible que no sea compatible con todos los navegadores. La forma correcta de hacer esto:

var arr = [1, 2, 3, 4];var prototypeOfArr = Object.getPrototypeOf(arr);prototypeOfArr === Array.prototype;prototypeOfArr === arr.__proto__;

The last line of the above code snippet shows that __proto__ and Object.getPrototypeOf return the same thing.

Now it’s time for a break. Grab a coffee or whatever you like and try out the examples above on your own. Once you are ready, come back to this article and we will then continue.

Prototype chaining & Inheritance

In Fig: 2 above, did you notice that there is another __proto__ inside the first __proto__ object? If not then scroll up a bit to Fig: 2. Have a look and come back here. We will now discuss what that is actually. That is known as prototype chaining.

In JavaScript, we achieve Inheritance with the help of prototype chaining.

Consider this example: We all understand the term “Vehicle”. A bus could be called as a vehicle. A car could be called a vehicle. A motorbike could be called a vehicle. Bus, car, and motorbike have some common properties that's why they are called vehicle. For example, they can move from one place to another. They have wheels. They have horns, etc.

Again bus, car, and motorbike can be of different types for example Mercedes, BMW, Honda, etc.

In the above illustration, Bus inherits some property from vehicle, and Mercedes Benz Bus inherits some property from bus. Similar is the case for Car and MotorBike.

Let's establish this relationship in JavaScript.

First, let's assume a few points for the sake of simplicity:

  1. All buses have 6 wheels
  2. Accelerating and Braking procedures are different across buses, cars, and motorbikes, but the same across all buses, all cars, and all motorbikes.
  3. All vehicles can blow the horn.
function Vehicle(vehicleType) { //Vehicle Constructor this.vehicleType = vehicleType;}
Vehicle.prototype.blowHorn = function () { console.log('Honk! Honk! Honk!'); // All Vehicle can blow Horn}
function Bus(make) { // Bus Constructor Vehicle.call(this, "Bus"); this.make = make}
Bus.prototype = Object.create(Vehicle.prototype); // Make Bus constructor inherit properties from Vehicle Prototype Object
Bus.prototype.noOfWheels = 6; // Let's assume all buses have 6 wheels
Bus.prototype.accelerator = function() { console.log('Accelerating Bus'); //Bus accelerator}
Bus.prototype.brake = function() { console.log('Braking Bus'); // Bus brake}
function Car(make) { Vehicle.call(this, "Car"); this.make = make;}
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.noOfWheels = 4;
Car.prototype.accelerator = function() { console.log('Accelerating Car');}
Car.prototype.brake = function() { console.log('Braking Car');}
function MotorBike(make) { Vehicle.call(this, "MotorBike"); this.make = make;}
MotorBike.prototype = Object.create(Vehicle.prototype);
MotorBike.prototype.noOfWheels = 2;
MotorBike.prototype.accelerator = function() { console.log('Accelerating MotorBike');}
MotorBike.prototype.brake = function() { console.log('Braking MotorBike');}
var myBus = new Bus('Mercedes');var myCar = new Car('BMW');var myMotorBike = new MotorBike('Honda');

Allow me to explain the above code snippet.

We have a Vehicle constructor which expects a vehicle type. As all vehicles can blow their horns, we have a blowHorn property in Vehicle's prototype.

As Bus is a vehicle it will inherit properties from Vehicle object.

We have assumed all buses will have 6 wheels and have the same accelerating and braking procedures. So we have noOfWheels, accelerator and brake property defined in Bus’s prototype.

Similar logic applies for Car and MotorBike.

Let’s go to Chrome Developer Tools -> Console and execute our code.

After execution, we will have 3 objects myBus, myCar, and myMotorBike.

Type console.dir(mybus) in the console and hit enter. Use the triangle icon to expand it and you will see something like below:

Under myBus we have properties make and vehicleType. Notice the value of __proto__ is prototype of Bus. All the properties of its prototype are available here: accelerator, brake, noOfWheels.

Now have a look that the first __proto__ object. This object has another __proto__ object as its property.

Under which we have blowHorn and constructor property.

Bus.prototype = Object.create(Vehicle.prototype);

Remember the line above? Object.create(Vehicle.prototype) will create an empty object whose prototype is Vehicle.prototype. We set this object as a prototype of Bus. For Vehicle.prototype we haven’t specified any prototype so by default it inherits from Object.prototype.

Let’s see the magic below:

We can access the make property as it is myBus's own property.

We can access the brake property from myBus's prototype.

We can access the blowHorn property from myBus's prototype’s prototype.

We can access the hasOwnProperty property from myBus's prototype’s prototype’s prototype. :)

This is called prototype chaining. Whenever you access a property of an object in JavaScript, it first checks if the property is available inside the object. If not it checks its prototype object. If it is there then good, you get the value of the property. Otherwise, it will check if the property exists in the prototype’s prototype, if not then again in the prototype’s prototype’s prototype and so on.

So how long it will check in this manner? It will stop if the property is found at any point or if the value of __proto__ at any point is null or undefined. Then it will throw an error to notify you that it was unable to find the property you were looking for.

This is how inheritance works in JavaScript with the help of prototype chaining.

Feel free to try the above example with myCar and myMotorBike.

As we know, in JavaScript everything is an object. You will find that for every instance, the prototype chain ends with Object.prototype.

The exception for the above rule is if you create an object with Object.create(null)

var obj = Object.create(null)

With the above code obj will be an empty object without any prototype.

For more information on Object.create check out the documentation on MDN.

Can you change the prototype object of an existing object? Yes, with Object.setPrototypeOf() you can. Check out the documentation in MDN.

Want to check if a property is the object’s own property? You already know how to do this.Object.hasOwnProperty will tell you if the property is coming from the object itself or from its prototype chain. Check out its documentation on MDN.

Note that __proto__ also referred to as [[Prototype]].

Ahora es el momento de otro descanso. Una vez que esté listo, vuelva a este artículo. Luego continuaremos y prometo que esta es la última parte.

Comprensión de las clases en JavaScript

Según MDN:

Las clases de JavaScript, introducidas en ECMAScript 2015, son principalmente azúcar sintáctico sobre la herencia existente basada en prototipos de JavaScript. La sintaxis de la clase no introduce un nuevo modelo de herencia orientado a objetos en JavaScript.

Las clases en JavaScript proporcionarán una mejor sintaxis para lograr lo que hicimos anteriormente de una manera mucho más limpia. Primero echemos un vistazo a la sintaxis de la clase.

class Myclass { constructor(name) { this.name = name; } tellMyName() { console.log(this.name) }}
const myObj = new Myclass("John");

constructorEl método es un tipo especial de método. Se ejecutará automáticamente cada vez que cree una instancia de esta clase. Dentro de tu cuerpo de clase. Solo constructores posible una aparición de .

The methods that you will define inside the class body will be moved to the prototype object.

If you want some property inside the instance you can define it in the constructor, as we did with this.name = name.

Let’s have a look into our myObj.

Note that we have the name property inside the instance that is myObj and the method tellMyName is in the prototype.

Consider the code snippet below:

class Myclass { constructor(firstName) { this.name = firstName; } tellMyName() { console.log(this.name) } lastName = "lewis";}
const myObj = new Myclass("John");

Let’s see the output:

See that lastName is moved into the instance instead of prototype. Only methods you that you declare inside the Class body will be moved to prototype. There is an exception though.

Consider the code snippet below:

class Myclass { constructor(firstName) { this.name = firstName; } tellMyName = () => { console.log(this.name) } lastName = "lewis";}
const myObj = new Myclass("John");

Output:

Note that tellMyName is now an arrow function, and it has been moved to the instance instead of prototype. So remember that arrow functions will always be moved to the instance, so use them carefully.

Let’s look into static class properties:

class Myclass { static welcome() { console.log("Hello World"); }}
Myclass.welcome();const myObj = new Myclass();myObj.welcome();

Output:

Static properties are something that you can access without creating an instance of the class. On the other hand, the instance will not have access to the static properties of a class.

So is static property a new concept that is available only with the class and not in the old school JavaScript? No, it’s there in old school JavaScript also. The old school method of achieving static property is:

function Myclass() {}Myclass.welcome = function() { console.log("Hello World");}

Now let’s have a look at how we can achieve inheritance with classes.

class Vehicle { constructor(type) { this.vehicleType= type; } blowHorn() { console.log("Honk! Honk! Honk!"); }}
class Bus extends Vehicle { constructor(make) { super("Bus"); this.make = make; } accelerator() { console.log('Accelerating Bus'); } brake() { console.log('Braking Bus'); }}
Bus.prototype.noOfWheels = 6;
const myBus = new Bus("Mercedes");

We inherit other classes using the extends keyword.

super() will simply execute the parent class’s constructor. If you are inheriting from other classes and you use the constructor in your child class, then you have to call super() inside the constructor of your child class otherwise it will throw an error.

We already know that if we define any property other than a normal function in the class body it will be moved to the instance instead of prototype. So we define noOfWheel on Bus.prototype.

Inside your class body if you want to execute parent class’s method you can do that using super.parentClassMethod().

Output:

The above output looks similar to our previous function based approach in Fig: 7.

Wrapping up

So should you use new class syntax or old constructor based syntax? I guess there is no definite answer to this question. It depends on your use case.

In this article, for the classes part I have just demonstrated how you can achieve prototypical inheritance classes. There is more to know about JavaScript classes, but that’s out of the scope of this article. Check out the documentation of classes on MDN. Or I will try to write an entire article on classes at some time.

If this article helped you in understanding prototypes, I would appreciate if you could applaud a little.

If you want me to write on some other topic, let me know in the responses.

You can also connect with me over LinkedIn.

Thank You for Reading. :)