Semua yang perlu Anda ketahui untuk memahami Prototipe JavaScript

Sebagian besar waktu, prototipe JavaScript membingungkan orang-orang yang baru mulai belajar JavaScript - terutama jika mereka berasal dari latar belakang C ++ atau Java.

Dalam JavaScript, pewarisan bekerja sedikit berbeda dibandingkan dengan C ++ atau Java. Pewarisan JavaScript lebih dikenal sebagai "pewarisan prototipe".

Hal-hal menjadi lebih sulit untuk dipahami ketika Anda juga bertemu classdi JavaScript. classSintaks baru terlihat mirip dengan C ++ atau Java, tetapi kenyataannya, cara kerjanya berbeda.

Pada artikel ini, kami akan mencoba memahami "warisan prototypal" di JavaScript. Kami juga melihat classsintaks berbasis baru dan mencoba memahami apa itu sebenarnya. Jadi mari kita mulai.

Pertama, kita akan mulai dengan prototipe dan fungsi JavaScript jadul.

Memahami kebutuhan akan prototipe

Jika Anda pernah bekerja dengan array JavaScript atau objek atau string, Anda telah memperhatikan bahwa ada beberapa metode yang tersedia secara default.

Sebagai contoh:

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

Pernahkah Anda bertanya-tanya dari mana asal metode ini? Anda belum menentukan metode ini sendiri.

Bisakah Anda menentukan metode Anda sendiri seperti ini? Anda bisa mengatakan bahwa Anda bisa dengan cara ini:

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

Ini akan berhasil, tetapi hanya untuk variabel yang dipanggil ini arr. Katakanlah kita memiliki variabel lain yang dipanggil arr2maka arr2.test()akan memunculkan kesalahan "TypeError: arr2.test is not a function".

Jadi bagaimana metode tersebut menjadi tersedia untuk setiap instance dari array / string / object? Bisakah Anda membuat metode Anda sendiri dengan perilaku yang sama? Jawabannya iya. Anda perlu melakukannya dengan cara yang benar. Untuk membantu ini, datanglah prototipe JavaScript.

Pertama-tama, mari kita lihat dari mana fungsi-fungsi ini berasal. Perhatikan cuplikan kode di bawah ini:

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

Kami telah membuat dua array dengan dua cara berbeda: arr1dengan literal array dan arr2dengan Arrayfungsi konstruktor. Keduanya setara satu sama lain dengan beberapa perbedaan yang tidak penting untuk artikel ini.

Sekarang datang ke fungsi konstruktor Array- ini adalah fungsi konstruktor standar di JavaScript. Jika Anda membuka alat Pengembang Chrome dan pergi ke konsol dan mengetik console.log(Array.prototype)dan menekan enterAnda akan melihat sesuatu seperti di bawah ini:

Di sana Anda akan melihat semua metode yang kami tanyakan. Jadi sekarang kita dapatkan dari mana fungsi tersebut berasal. Jangan ragu untuk mencoba dengan String.prototypedan Object.prototype.

Mari buat fungsi konstruktor sederhana kita sendiri:

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

Dapatkah Anda mengidentifikasi masalah mendasar dengan kode di atas? Masalahnya adalah kita membuang-buang memori dengan pendekatan di atas. Perhatikan bahwa metode tellMyNameini sama untuk setiap instance foo. Setiap kali kita membuat sebuah instance dari foometode ini tellMyNameakan menghabiskan ruang di memori sistem. Jika tellMyNamesama untuk semua instance, lebih baik menyimpannya di satu tempat dan membuat semua instance kami merujuk dari tempat itu. Mari kita lihat bagaimana melakukan ini.

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

Mari kita periksa perbedaannya dengan pendekatan di atas dan pendekatan sebelumnya. Dengan pendekatan di atas, jika Anda console.dir()contoh maka Anda akan melihat sesuatu seperti ini:

Perhatikan bahwa sebagai properti instance yang hanya kita miliki myname. tellMyNamedidefinisikan di bawah __proto__. Saya akan membahas ini __proto__setelah beberapa saat. Yang paling penting perhatikan bahwa membandingkan tellMyNamekedua instance tersebut bernilai true. Perbandingan fungsi dalam JavaScript mengevaluasi true hanya jika referensinya sama. Ini membuktikan bahwa tellMyNametidak memakan memori ekstra untuk banyak contoh.

Mari kita lihat hal yang sama dengan pendekatan sebelumnya:

Perhatikan bahwa waktu tellMyNameini didefinisikan sebagai properti instance. Tidak lagi di bawah itu __proto__. Juga, perhatikan bahwa kali ini membandingkan fungsi mengevaluasi ke salah. Ini karena mereka berada di dua lokasi memori yang berbeda dan referensi mereka juga berbeda.

Saya harap sekarang Anda memahami perlunya prototype.

Sekarang mari kita lihat lebih detail tentang prototipe.

Masing-masing dan setiap fungsi JavaScript akan memiliki prototypeproperti bertipe objek. Anda dapat menentukan properti Anda sendiri di bawah prototype. Saat Anda akan menggunakan fungsi sebagai fungsi konstruktor, semua instance-nya akan mewarisi properti dari prototypeobjek.

Sekarang mari kita ke __proto__properti yang Anda lihat di atas. Ini __proto__hanyalah referensi ke objek prototipe yang mewarisi instance. Kedengarannya rumit? Sebenarnya tidak terlalu rumit. Mari kita visualisasikan ini dengan sebuah contoh.

Perhatikan kode di bawah ini. Kita sudah tahu membuat Array dengan literal array akan mewarisi properti dari Array.prototype.

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

Apa yang baru saja saya katakan di atas adalah " Ini __proto__hanyalah referensi ke objek prototipe yang mewarisi instance ". Jadi arr.__proto__harus sama dengan Array.prototype. Mari kita verifikasi ini.

Sekarang kita seharusnya tidak mengakses objek prototipe dengan __proto__. Menurut MDN, penggunaan __proto__sangat tidak disarankan dan mungkin tidak didukung di semua browser. Cara yang benar untuk melakukan ini:

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

Baris terakhir dari potongan kode di atas menunjukkan itu __proto__dan Object.getPrototypeOfmengembalikan hal yang sama.

Sekarang waktunya istirahat. Ambil kopi atau apa pun yang Anda suka dan coba contoh di atas sendiri. Setelah Anda siap, kembali ke artikel ini dan kami akan melanjutkan.

Rantai prototipe & Warisan

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]].

Sekarang waktunya istirahat lagi. Setelah Anda siap, kembali ke artikel ini. Kami kemudian akan melanjutkan dan saya berjanji ini adalah bagian terakhir.

Memahami Kelas di JavaScript

Menurut MDN:

Kelas JavaScript, yang diperkenalkan di ECMAScript 2015, pada dasarnya adalah gula sintaksis atas warisan berbasis prototipe JavaScript yang ada. Sintaks kelas tidak memperkenalkan model pewarisan berorientasi objek baru ke JavaScript.

Kelas dalam JavaScript akan memberikan sintaks yang lebih baik untuk mencapai apa yang kami lakukan di atas dengan cara yang jauh lebih bersih. Mari kita lihat sintaks kelas terlebih dahulu.

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

constructormetode adalah jenis metode khusus. Ini akan secara otomatis dijalankan setiap kali Anda membuat instance dari kelas ini. Di dalam tubuh kelas Anda. Hanya satu kemunculan constructoryang mungkin.

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. :)