Το πρωτότυπο κληρονομικότητα του JavaScript εξηγείται χρησιμοποιώντας CSS

Η πρωτοτυπική κληρονομιά είναι αναμφισβήτητα η λιγότερο κατανοητή πτυχή του JavaScript. Λοιπόν τα καλά νέα είναι ότι αν καταλαβαίνετε πώς λειτουργεί το CSS, μπορείτε επίσης να καταλάβετε πώς λειτουργούν τα πρωτότυπα JavaScript.

Είναι όμορφο όταν κάτι απλό μπορεί να εξηγήσει κάτι φαινομενικά περίπλοκο, μια αναλογία - όπως ένας οδηγός οδηγού οδηγεί έναν πόλο βαθιά στο έδαφος, μια αναλογία οδηγεί το σημείο στο σπίτι.

Είμαι λάτρης των αναλογιών, ενός αναλόγου.

Ορίστε.

Πρωτότυπα σε κουμπιά CSS

Βλέπετε τα δύο κουμπιά παραπάνω; Θα τα σχεδιάσουμε σε CSS.

Ας προχωρήσουμε και γράψτε γρήγορα στυλ για αυτά τα δύο κουμπιά, ξεκινώντας από .btn

.btn { min-width: 135px; min-height: 45px; font-family: ‘Avenir Next’, sans-serif; font-size: 18px; font-weight: bold; letter-spacing: 1.3px; color: #4D815B; background-color: #FFF; border: 2px solid #4D815B; border-radius: 4px; padding: 5px 20px; cursor: pointer;}

Αυτό είναι ένα αρκετά απλό μπλοκ κώδικα CSS.

Τώρα ας προχωρήσουμε στα στυλ για .btn-solid

.btn-solid { min-width: 135px; min-height: 45px; font-family: ‘Avenir Next’, sans-serif; font-size: 18px; font-weight: bold; letter-spacing: 1.3px; color: #FFF; background-color: #4D815B; border: 2px solid #4D815B; border-radius: 4px; padding: 5px 20px; cursor: pointer;}

Όπως ίσως έχετε ήδη παρατηρήσει, εκτός από τα τολμηρά, όλα τα άλλα στυλ .btn-solidείναι πανομοιότυπα με αυτά του .btn. Και αν είστε εξοικειωμένοι με το Sass, ίσως γνωρίζετε ότι τα .btn-solidστυλ μπορούν να ξαναγραφούν στο SASS όπως έτσι:

.btn-solid { @extend .btn; color: #FFF; background-color: #4D815B;}

Όπως μπορείτε να δείτε, .btn-solidκληρονομεί στυλ από .btn, στη συνέχεια, παρακάμπτει ορισμένα από αυτά (χρώμα γραμματοσειράς και φόντου) για να δημιουργηθεί. Αυτό μας οδηγεί στο συμπέρασμα ότι.btn-solidείναι μια εξειδικευμένη έκδοση του .btn. Ή, με άλλα λόγια, .btn-solidείναι .btnμόνο με διαφορετικά χρώματα γραμματοσειράς και φόντου. Αυτό έχει νόημα, σωστά; Αλλά περίμενε ότι υπάρχουν περισσότερα.

Ας πούμε ότι θέλουμε να δημιουργήσουμε ένα μεγαλύτερο κουμπί, .btn-lg. Θα χρησιμοποιήσουμε .btnως πρωτότυπο για την παροχή βασικών στυλ. Στη συνέχεια, όπως και στον τρόπο τροποποίησης των χρωμάτων φόντου και γραμματοσειράς .btn-solid, θα τροποποιήσουμε την ιδιότητα μεγέθους γραμματοσειράς σε μεγαλύτερη τιμή για να δημιουργήσουμε ένα μεγαλύτερο κουμπί.

Και οι δύο .btn-lgκαι .btn-solidείναι εξειδικευμένες εκδόσεις του .btn. .btnπαρέχει βασικά στυλ .btn-lgκαι τα .btn-solidοποία στη συνέχεια αντικαθιστούν ορισμένα από τα βασικά στυλ για να δημιουργηθούν. Αυτό μας λέει ότι ένα μόνο κουμπί που αποφασίζουμε - .btnστην περίπτωσή μας - μπορεί να χρησιμοποιηθεί ως προμηθευτής βασικών στυλ σε πολλά αντικείμενα.

Σε αυτήν την ενότητα, προσπαθήσαμε να ορίσουμε την έννοια των πρωτοτύπων για τα κουμπιά CSS. Ένα πρωτότυπο είναι μια οντότητα που παρέχει βασικά στυλ, τα οποία μπορούν να επεκταθούν για να δημιουργήσουν διαφορετικές εμφανίσεις κουμπιών. Αυτός ο ορισμός ενός πρωτοτύπου είναι πολύ κοντά σε αυτό που πραγματικά σημαίνουν τα πρωτότυπα σε όρους προγραμματισμού.

Σε όρους προγραμματισμού, ένα πρωτότυπο είναι ένα αντικείμενο που παρέχει βασική συμπεριφορά σε ένα δεύτερο αντικείμενο. Στη συνέχεια, το δεύτερο αντικείμενο επεκτείνει αυτή τη βασική συμπεριφορά για να διαμορφώσει τη δική του εξειδίκευση. Ας δούμε στην επόμενη ενότητα πώς οι γνώσεις μας για τα πρωτότυπα σε κουμπιά CSS αντιστοιχούν σε JavaScript.

Πρωτότυπα σε JavaScript

Εξετάστε το ακόλουθο αντικείμενο JavaScript:

let obj = { a: 1};

Γνωρίζουμε ότι η τιμή του aείναι προσβάσιμη obj.a, δεδομένου ότι aείναι σαφώς μια ιδιοκτησία του obj. Αλλά υπάρχουν περισσότερα, μπορείτε επίσης να καλέσετε obj.hasOwnProperty('a')για να ελέγξετε αν objέχει πράγματι ένα όνομα a.

Τώρα περιμένετε ένα δευτερόλεπτο - από αυτό που μπορούμε να δούμε, objδεν έχει καμία ιδιότητα που να ονομάζεται hasOwnProperty. Από που hasOwnPropertyπροήλθε; Για να απαντήσουμε σε αυτήν την ερώτηση θα πρέπει να επιστρέψουμε στα κουμπιά που μόλις ολοκληρώσαμε τη δημιουργία.

.btn-solidαπλώς έχει ορίσει χρώματα φόντου και γραμματοσειράς Από πού προέρχεται, για παράδειγμα, border-radiusαπό; Γνωρίζουμε ότι .btn-solidείναι μια εξειδίκευση .btn, έτσι μπορούμε να δούμε ότι .btn-solidπαίρνει στυλ όπως border-radius, widthκαι height, paddingαπό .btn. Θα μπορούσε να είναι το ίδιο με obj;

Ακριβώς όπως .btn-solidκαι .btn-lgνα πάρει τις μορφές τους βάση από .btn, objή οποιοδήποτε άλλο αντικείμενο τη Javascript για να λαμβάνετε το θέμα της συμπεριφοράς τους βάσης από ένα άλλο αντικείμενο -Object.prototype . Και αυτό το Object.prototypeέχει hasOwnPropertyορίσει. Και ως εκ τούτου, αυτό δίνει objπρόσβαση στην hasOwnPropertyμέθοδο - όπως ακριβώς .btn-solidείχαν πρόσβαση σε .btn«s border-radiusιδιοκτησίας.

Αυτό - ένα αντικείμενο (obj) που κληρονομεί τις ιδιότητές του και τη βασική συμπεριφορά από ένα άλλο αντικείμενο (Object.prototype) - είναι αυτό που ονομάζουμε πρωτότυπη κληρονομιά. Παρατηρήστε ότι δεν υπάρχει classεμπλοκή στην αλληλεπίδραση.

Η πραγματική εσωτερική λειτουργία των πρωτότυπων JavaScript και τα "πρωτότυπα" CSS μας είναι πολύ διαφορετικά. Αλλά για τους σκοπούς της αναλογίας μας, μπορούμε να αγνοήσουμε πώς λειτουργούν πίσω από τα παρασκήνια.

Object.prototypeδεν είναι το μόνο διαθέσιμο πρωτότυπο στο JavaScript. Υπάρχει Array.prototype, Function.prototype, Number.prototypeκαι πολλά άλλα. Η δουλειά όλων αυτών των πρωτοτύπων είναι να παρέχει βασικές συμπεριφορές ή μεθόδους χρησιμότητας στις παρουσίες τους.

Για παράδειγμα, κάθε σειρά που δηλώνονται σε JavaScript έχει πρόσβαση σε .push, .sort, .forEachκαι .mapμόνο λόγω της πρωτότυπη σύνδεσης. Και για τον ίδιο λόγο, κάθε συνάρτηση έχει πρόσβαση σε .call, .apply, .bind.

Τα πρωτότυπα και τα πρωτότυπα κληρονομιά δεν είναι συγκεκριμένα JavaScript. Είναι μια κατασκευή που χρησιμοποιεί το JavaScript εσωτερικά και μας επιτρέπει να τη χρησιμοποιούμε στα δικά μας προγράμματα. Πριν εξετάσουμε πώς ακριβώς μπορούμε να το κάνουμε αυτό, πρέπει να καταλάβουμε τι είναι η πρωτοτυπική αλυσίδα.

Πρωτοτυπική αλυσίδα

Θα πρέπει να επιστρέψουμε στην αναλογία κουμπιών για μία φορά. Ας πούμε ότι θέλω να δημιουργήσω ένα μεγάλο σταθερό κουμπί .btn-solid-lg:

Τα βασικά στυλ .btn-solid-lgπου παρέχονται από .btn-solid, και .btn-solid-lgαντικαθιστούν την ιδιότητα μεγέθους γραμματοσειράς για να δημιουργηθεί.

Ρίξτε μια πιο προσεκτική ματιά. .btn-solidέχει μόνο δύο στυλ φόντο-χρώμα και χρώμα (γραμματοσειρά) που ορίζονται σε αυτό. Αυτό σημαίνει ότι .btn-solid-lgέχει μόνο 3 στυλ για τον εαυτό του: χρώμα φόντου, χρώμα και μέγεθος γραμματοσειράς. Όπου width, height, border-radiusπροέρχεται από;

Εντάξει, εδώ είναι μια υπόδειξη:

If you wanted to create an extra large button .btn-solid-xlg you could do so with .btn-solid-lg as prototype. But how does all of this map to JavaScript?

In JavaScript, you’re allowed to create prototype chains too. Once you understand this, you unlock a whole set of tools to write amazingly powerful code. Yes, amazingly powerful.

Let’s see how prototype chains in JavaScript work.

Remember the object we created in the previous section? The one we carefully named obj? Did you know that you can create as many objects as you want with obj as a prototype?

Object.create lets you create a new object from a specified prototype object. This means that you can create another object, obj2, which has obj as its first prototype:

let obj2 = Object.create(obj);
// Add a property 'b' to obj2obj2.b = 2;

If you have been following so far, you should realize that even though obj2 doesn’t have a property a defined on it, doing console.log(obj2.a) won’t result in an error, but instead 1 getting logged to the console. Kind of like this:

When obj2 looks for a, it first searches its own properties. If it can’t find the corresponding property, it asks its prototype (obj), where it finally finds a. If such was the case that it still couldn’t find a, the search would continue up the prototype chain until it reaches the last link, Object.prototype.

On the other hand, if a was defined on obj2, it would override all other as if defined on any of its prototypes. Similar to how .btn-solid overrode .btn's color and background-color properties. This is called property overshadowing.

But what about the length of prototype chain? Is there a limit?

There’s no limit to the length of prototype chain. There also aren’t any limits on branching. This means you can create multiple instances with Object.prototype, obj, or obj2 as prototype.

So how will this new knowledge of prototypes and prototypal chaining help you write better code?

Writing better code with Prototypes

The goal of this article was to explain to you what prototypes are, and how prototypal inheritance works. I hope I’ve succeeded in this.

For this last section, I’ll allow myself to go on a little rant. I hope you don’t mind.

If you look at the JavaScript code available online — whether in open source projects on Github or in pens on Codepen — you’ll find that a majority of them use the constructor pattern for creating objects.

function Circle(radius) { this.radius = radius;}
Circle.prototype.area = function() { return Math.PI * this.radius * this.radius;}
// Constructor pattern for creating new objectslet circ = new Circle(5);

The constructor pattern looks like classes. In the early days, when JavaScript was far less popular than what it is today, the new keyword was added as a marketing strategy.

This indirection was intended to make JavaScript seem more familiar to classically trained programmers. Though it’s debatable how successful it was in doing so, it unintentionally also obscured the true prototypal nature of the language.

The reality is that although constructors look like classes, they don’t behave like classes at all. In JavaScript, there are objects, and objects extending from other objects. Constructors and classes never come into picture. The constructor pattern unnecessarily complicates things, there’s a lot that happens behind the scenes.

I implore you — now that you have a solid understanding of prototypes — to stop using the constructor pattern.

Why not do this instead?

let Circle = { create(radius) { // Creating prototypal linkage using Object.create let obj = Object.create(this); obj.radius = radius; return obj; }, area() { return Math.PI * this.radius * this.radius; }};
let circ = Circle.create(5);

I hope this analogy has helped you better understand prototypes, prototypal chaining and prototypal inheritance with Object.create. Now you can write better code, and stop using pretentious classes.

Thanks for reading! If my article was helpful, click the little green heart below to recommend it, and please share this with your fellow devs.

Και για περαιτέρω ανάγνωση, ρίξτε μια ματιά στο Aadit Shah's Why Prototypal Inheritance Matters.

Ψάχνετε περισσότερα; Δημοσιεύω τακτικά στο blog μου στο nashvail.me. Τα λέμε εκεί, καλή!