Yang perlu anda ketahui untuk memahami Prototaip JavaScript

Selalunya, prototaip JavaScript membingungkan orang yang baru mulai belajar JavaScript - terutama jika mereka berasal dari latar belakang C ++ atau Java.

Dalam JavaScript, pewarisan berfungsi sedikit berbeza berbanding C ++ atau Java. Pewarisan JavaScript lebih dikenali sebagai "warisan prototaip".

Perkara menjadi lebih sukar difahami apabila anda juga menghadapi classJavaScript. classSintaks baru kelihatan serupa dengan C ++ atau Java, tetapi pada kenyataannya, ia berfungsi berbeza.

Dalam artikel ini, kita akan cuba memahami "prototaip warisan" dalam JavaScript. Kami juga melihat classsintaks berasaskan baru dan cuba memahami apa sebenarnya. Oleh itu, mari kita mulakan.

Pertama, kita akan mulakan dengan fungsi dan prototaip JavaScript sekolah lama.

Memahami keperluan prototaip

Sekiranya anda pernah bekerja dengan tatasusunan atau objek atau rentetan JavaScript, anda telah memperhatikan bahawa terdapat beberapa kaedah yang tersedia secara lalai.

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

Adakah anda pernah terfikir dari mana kaedah ini berasal? Anda belum menentukan kaedah ini sendiri.

Bolehkah anda menentukan kaedah anda sendiri seperti ini? Anda boleh mengatakan bahawa anda boleh dengan cara ini:

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

Ini akan berfungsi, tetapi hanya untuk pemboleh ubah ini yang dipanggil arr. Katakan kita mempunyai pemboleh ubah lain yang disebut arr2maka arr2.test()akan membuang ralat "TypeError: arr2.test bukan fungsi".

Jadi bagaimana kaedah tersebut tersedia untuk setiap contoh array / string / objek? Bolehkah anda membuat kaedah anda sendiri dengan tingkah laku yang sama? Jawapannya adalah ya. Anda perlu melakukannya dengan cara yang betul. Untuk membantu ini, terdapat prototaip JavaScript.

Mari lihat dahulu dari mana fungsi-fungsi ini berasal. Pertimbangkan coretan kod di bawah:

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

Kami telah membuat dua tatasusunan dengan dua cara yang berbeza: arr1dengan literal array dan arr2dengan Arrayfungsi konstruktor. Kedua-duanya setara antara satu sama lain dengan beberapa perbezaan yang tidak penting untuk artikel ini.

Sekarang datang ke fungsi konstruktor Array- ini adalah fungsi konstruktor yang telah ditentukan dalam JavaScript. Sekiranya anda membuka alat Pembangun Chrome dan pergi ke konsol dan menaip console.log(Array.prototype)dan memukul, enteranda akan melihat sesuatu seperti di bawah:

Di sana anda akan melihat semua kaedah yang kami tertanya-tanya. Jadi sekarang kita dapat dari mana fungsi-fungsi itu datang. Jangan ragu untuk mencuba String.prototypedan Object.prototype.

Mari buat fungsi pembina mudah kami 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

Bolehkah anda mengenal pasti masalah asas dengan kod di atas? Masalahnya ialah kita membuang ingatan dengan pendekatan di atas. Perhatikan bahawa kaedahnya tellMyNamesama untuk setiap contoh foo. Setiap kali kita membuat contoh fookaedah tellMyNameakhirnya mengambil ruang dalam memori sistem. Sekiranya tellMyNamesama untuk semua kejadian, lebih baik menyimpannya di satu tempat dan membuat semua contoh kami merujuk dari tempat itu. Mari 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 periksa perbezaannya dengan pendekatan di atas dan pendekatan sebelumnya. Dengan pendekatan di atas, jika anda console.dir()melihatnya, anda akan melihat sesuatu seperti ini:

Perhatikan bahawa sebagai harta benda yang hanya kita miliki myname. tellMyNameditakrifkan di bawah __proto__. Saya akan sampai __proto__selepas ini . Yang paling penting diperhatikan bahawa membandingkan tellMyNamekedua-dua keadaan dinilai menjadi benar. Perbandingan fungsi dalam JavaScript menilai benar hanya jika rujukannya sama. Ini membuktikan bahawa tellMyNametidak menggunakan memori tambahan untuk beberapa kejadian.

Mari kita lihat perkara yang sama dengan pendekatan sebelumnya:

Perhatikan bahawa masa tellMyNameini ditakrifkan sebagai harta benda kejadian. Tidak lagi di bawah itu __proto__. Juga, perhatikan bahawa kali ini membandingkan fungsi dinilai menjadi salah. Ini kerana mereka berada di dua lokasi memori yang berbeza dan rujukannya berbeza.

Saya harap sekarang anda sudah faham akan keperluan prototype.

Sekarang mari kita perhatikan lebih terperinci mengenai prototaip.

Setiap fungsi JavaScript akan mempunyai prototypesifat yang merupakan jenis objek. Anda boleh menentukan harta tanah anda sendiri di bawah prototype. Apabila anda akan menggunakan fungsi tersebut sebagai fungsi konstruktor, semua contohnya akan mewarisi sifat dari prototypeobjek tersebut.

Sekarang mari kita ke __proto__harta yang anda lihat di atas. Ini __proto__hanyalah rujukan ke objek prototaip dari mana instance tersebut telah diwarisi. Bunyi rumit? Sebenarnya tidak begitu rumit. Mari kita gambarkan ini dengan contoh.

Pertimbangkan kod di bawah. Kita sudah tahu membuat Array dengan literal array akan mewarisi sifat dari Array.prototype.

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

Apa yang saya katakan di atas adalah " Ini __proto__hanyalah rujukan ke objek prototaip dari mana instance tersebut telah diwarisi ". Jadi arr.__proto__harus sama dengan Array.prototype. Mari kita sahkan ini.

Sekarang kita tidak boleh mengakses objek prototaip dengan __proto__. Menurut MDN, penggunaan __proto__sangat tidak digalakkan dan mungkin tidak disokong di semua pelayar. Cara yang betul untuk melakukan ini:

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

Baris terakhir coretan kod di atas menunjukkan bahawa __proto__dan Object.getPrototypeOfmengembalikan perkara yang sama.

Sekarang tiba masanya untuk berehat. Dapatkan kopi atau apa sahaja yang anda suka dan cuba sendiri contoh di atas. Setelah anda bersedia, kembali ke artikel ini dan kami akan meneruskannya.

Prototaip rantai & Warisan

Dalam Gambar: 2 di atas, adakah anda menyedari bahawa ada yang lain __proto__di dalam __proto__objek pertama ? Sekiranya tidak, tatal ke atas sedikit ke Gambar: 2. Lihat dan kembali ke sini. Kita sekarang akan membincangkan apa itu sebenarnya. Itu dikenali sebagai prototaip chaining.

Dalam JavaScript, kami mencapai Warisan dengan bantuan prototaip rantai.

Pertimbangkan contoh ini: Kita semua memahami istilah "Kenderaan". Sebuah bas boleh dipanggil sebagai kenderaan. Sebuah kereta boleh dipanggil kenderaan. Sepeda motor boleh dipanggil kenderaan. Bas, kereta, dan motosikal mempunyai beberapa sifat umum yang menyebabkan ia dipanggil kenderaan. Contohnya, mereka boleh berpindah dari satu tempat ke tempat lain. Mereka mempunyai roda. Mereka mempunyai tanduk, dll.

Sekali lagi bas, kereta, dan motosikal boleh menjadi pelbagai jenis seperti Mercedes, BMW, Honda, dll.

Dalam gambaran di atas, Bas mewarisi beberapa harta dari kenderaan, dan Bas Mercedes Benz mewarisi beberapa harta dari bas. Begitu juga dengan kereta dan motosikal.

Mari kita menjalin hubungan ini dalam JavaScript.

Pertama, mari kita anggap beberapa perkara demi kesederhanaan:

  1. Semua bas mempunyai 6 roda
  2. Prosedur mempercepat dan brek berbeza di antara bas, kereta, dan motosikal, tetapi sama di semua bas, semua kereta, dan semua motosikal.
  3. Semua kenderaan boleh meniup hon.
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');

Izinkan saya menjelaskan coretan kod di atas.

Kami mempunyai Vehiclekonstruktor yang mengharapkan jenis kenderaan. Oleh kerana semua kenderaan dapat meniup hon mereka, kita mempunyai blowHornharta benda dalam Vehicleprototaip.

Seperti Buskenderaan, ia akan mewarisi sifat dari Vehicleobjek.

Kami menganggap semua bas akan mempunyai 6 roda dan mempunyai prosedur pecutan dan pengereman yang sama. Jadi kita mempunyai noOfWheels, acceleratordan brakeharta yang ditentukan dalam Busprototaip.

Logik serupa berlaku untuk Kereta dan Motosikal.

Mari pergi ke Alat Pembangun Chrome -> Konsol dan laksanakan kod kami.

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

Kini tiba masanya untuk rehat yang lain. Setelah anda bersedia, kembali ke artikel ini. Kami kemudian akan meneruskan dan saya berjanji ini adalah bahagian terakhir.

Memahami Kelas dalam JavaScript

Menurut MDN:

Kelas JavaScript, yang diperkenalkan dalam ECMAScript 2015, terutama merupakan gula sintaksis berbanding warisan berasaskan prototaip JavaScript yang ada. Sintaks kelas tidak memperkenalkan model warisan berorientasikan objek baru ke JavaScript.

Kelas dalam JavaScript akan memberikan sintaks yang lebih baik untuk mencapai apa yang kami lakukan di atas dengan cara yang 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");

constructorkaedah adalah jenis kaedah khas. Ia akan dijalankan secara automatik setiap kali anda membuat contoh kelas ini. Di dalam badan kelas anda. Hanya satu kejadian constructoryang mungkin berlaku.

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