Τι είναι ο μεταπρογραμματισμός σε JavaScript; Στα αγγλικά παρακαλώ.

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

Ο μεταπρογραμματισμός σε JavaScript είναι μια τέτοια ιδέα που πολλοί από εμάς μπορεί να μην γνωρίζουμε. Σε αυτό το άρθρο, θα μάθουμε για τον μεταπρογραμματισμό και πώς είναι χρήσιμο για εμάς.

Με το ES6 (ECMAScript 2015), έχουμε υποστήριξη για τα αντικείμενα Reflectκαι Proxyτα οποία μας επιτρέπουν να κάνουμε μετα-προγραμματισμό με ευκολία. Σε αυτό το άρθρο, θα μάθουμε πώς να τα χρησιμοποιούμε με παραδείγματα.

Τι είναι ο μεταπρογραμματισμός;

Metaprogrammingδεν είναι τίποτα λιγότερο από τη μαγεία του προγραμματισμού ! Τι λέτε για τη σύνταξη ενός προγράμματος που διαβάζει, τροποποιεί, αναλύει, ακόμη και δημιουργεί ένα πρόγραμμα; Δεν ακούγεται παράξενο και δυνατό;

Να πώς θα περιέγραφα το Μεταπρογραμματισμό ως προγραμματιστή που το χρησιμοποιεί συνεχώς:

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

Με απλά λόγια, ο Μεταπρογραμματισμός περιλαμβάνει τη σύνταξη κώδικα που μπορεί

  • Δημιουργία κώδικα
  • Χειριστείτε τις δομές γλώσσας κατά το χρόνο εκτέλεσης. Αυτό το φαινόμενο είναι γνωστό ως Reflective Metaprogrammingή Reflection.

Τι είναι ο προβληματισμός στον μεταπρογραμματισμό;

Reflectionείναι ένας κλάδος του Μεταπρογραμματισμού. Ο προβληματισμός έχει τρία υποκαταστήματα:

  1. Ενδοσκόπηση : Ο κώδικας μπορεί να επιθεωρήσει τον εαυτό του. Χρησιμοποιείται για την ανακάλυψη πολύ χαμηλού επιπέδου πληροφοριών σχετικά με τον κώδικα.
  2. Αυτο-τροποποίηση : Όπως υποδηλώνει το όνομα, ο κώδικας μπορεί να τροποποιηθεί.
  3. Μεσολάβηση : Εν ενεργεί για λογαριασμό κάποιου άλλου. Αυτό μπορεί να επιτευχθεί με περιτύλιξη, παγίδευση, αναχαίτιση.

Το ES6 μας δίνει το Reflectαντικείμενο (γνωστό και ως Reflect API) για επίτευξη Introspection. Το Proxyαντικείμενο του ES6 μας βοηθάει Intercession. Δεν θα μιλήσουμε πάρα πολύ για   Self-Modificationτο ότι θέλουμε να μείνουμε μακριά από αυτό όσο το δυνατόν περισσότερο.

Περίμενε ένα λεπτό! Για να είμαι ξεκάθαρος, ο μεταπρογραμματισμός δεν παρουσιάστηκε στο ES6. Αντίθετα, ήταν διαθέσιμο στη γλώσσα από την αρχή. Το ES6 το έκανε πολύ πιο εύκολο στη χρήση.

Προ-ES6 εποχή μετα-προγραμματισμού

Θυμάσαι eval; Ας ρίξουμε μια ματιά στο πώς χρησιμοποιήθηκε:

const blog = { name: 'freeCodeCamp' } console.log('Before eval:', blog); const key = 'author'; const value = 'Tapas'; testEval = () => eval(`blog.${key} = '${value}'`); // Call the function testEval(); console.log('After eval magic:', blog); 

Όπως μπορεί να παρατηρήσετε, το evalβοήθησε με επιπλέον δημιουργία κώδικα. Σε αυτήν την περίπτωση, το αντικείμενο blogέχει τροποποιηθεί με μια πρόσθετη ιδιότητα κατά το χρόνο εκτέλεσης.

Before eval: {name: freeCodeCamp} After eval magic: {name: "freeCodeCamp", author: "Tapas"} 

Ενδοσκόπηση

Πριν από την ένταξη του Reflect objectES6, θα μπορούσαμε να κάνουμε ενδοσκόπηση. Ακολουθεί ένα παράδειγμα ανάγνωσης της δομής του προγράμματος:

var users = { 'Tom': 32, 'Bill': 50, 'Sam': 65 }; Object.keys(users).forEach(name => { const age = users[name]; console.log(`User ${name} is ${age} years old!`); }); 

Εδώ διαβάζουμε τη usersδομή του αντικειμένου και καταγράφουμε την τιμή κλειδιού σε μια πρόταση.

User Tom is 32 years old! User Bill is 50 years old! User Sam is 65 years old! 

Αυτο τροποποίηση

Ας πάρουμε ένα αντικείμενο ιστολογίου που έχει μια μέθοδο τροποποίησης:

var blog = { name: 'freeCodeCamp', modifySelf: function(key, value) {blog[key] = value} } 

Το blogαντικείμενο μπορεί να τροποποιηθεί κάνοντας αυτό:

blog.modifySelf('author', 'Tapas'); 

Μεσολάβηση

Intercessionείναι να ενεργούμε για λογαριασμό κάποιου άλλου αλλάζοντας τη σημασιολογία της γλώσσας. Η   Object.defineProperty()μέθοδος μπορεί να αλλάξει τη σημασιολογία ενός αντικειμένου:

var sun = {}; Object.defineProperty(sun, 'rises', { value: true, configurable: false, writable: false, enumerable: false }); console.log('sun rises', sun.rises); sun.rises = false; console.log('sun rises', sun.rises); 

Παραγωγή,

sun rises true sun rises true 

Όπως βλέπετε, το sunαντικείμενο δημιουργήθηκε ως κανονικό αντικείμενο και στη συνέχεια το σημασιολογικό έχει αλλάξει έτσι ώστε να μην είναι εγγράψιμο.

Τώρα ας περάσουμε στην κατανόηση Reflectκαι των Proxyαντικειμένων με τις αντίστοιχες χρήσεις τους.

Το API Reflect

Στο ES6, το Reflect είναι ένα νέο Global Object(όπως μαθηματικά) που παρέχει μια σειρά από λειτουργίες χρησιμότητας, πολλές από τις οποίες φαίνεται να αλληλεπικαλύπτονται με τις μεθόδους ES5 που ορίζονται στο παγκόσμιο Object.

Όλες αυτές οι λειτουργίες είναι συναρτήσεις Introspection όπου μπορείτε να ζητήσετε κάποιες εσωτερικές λεπτομέρειες σχετικά με το πρόγραμμα κατά το χρόνο εκτέλεσης.

Εδώ είναι η λίστα των διαθέσιμων μεθόδων από το Reflectαντικείμενο. Επισκεφθείτε αυτήν τη σελίδα για να δείτε περισσότερες λεπτομέρειες για καθεμία από αυτές τις μεθόδους.

// Reflect object methods Reflect.apply() Reflect.construct() Reflect.get() Reflect.has() Reflect.ownKeys() Reflect.set() Reflect.setPrototypeOf() Reflect.defineProperty() Reflect.deleteProperty() Reflect.getOwnPropertyDescriptor() Reflect.getPrototypeOf() Reflect.isExtensible() 

Αλλά περιμένετε, εδώ είναι μια ερώτηση: Γιατί χρειαζόμαστε ένα νέο αντικείμενο API, όταν αυτά θα μπορούσαν απλά να υπάρχουν ήδη ή θα μπορούσαν να προστεθούν Objectή Function;

Ταραγμένος? Ας προσπαθήσουμε να το καταλάβουμε.

Όλα σε ένα χώρο ονομάτων

Το JavaScript είχε ήδη υποστήριξη για την αντανάκλαση αντικειμένων. Αλλά αυτά τα API δεν οργανώθηκαν σε ένα χώρο ονομάτων. Από το ES6 είναι πλέον κάτω Reflect.

Σε αντίθεση με τα περισσότερα παγκόσμια αντικείμενα, το Reflect δεν είναι κατασκευαστής. Δεν μπορείτε να το χρησιμοποιήσετε με νέο χειριστή ή να καλέσετε το αντικείμενο Reflect ως συνάρτηση. Όλες οι ιδιότητες και οι μέθοδοι Reflectείναι staticσαν το μαθηματικό αντικείμενο.

Απλό στη χρήση

Οι introspectionμέθοδοι Objectρίψης εξαίρεσης όταν δεν ολοκληρώνουν τη λειτουργία. Αυτό είναι ένα πρόσθετο βάρος για τον καταναλωτή (προγραμματιστής) να χειριστεί αυτήν την εξαίρεση στον κώδικα.

Μπορεί να προτιμάτε να το χειρίζεστε boolean(true | false)αντί να χρησιμοποιείτε χειρισμό εξαιρέσεων. Το αντικείμενο Reflect σας βοηθά να το κάνετε αυτό.

Ακολουθεί ένα παράδειγμα με το Object.defineProperty:

 try { Object.defineProperty(obj, name, desc); // property defined successfully } catch (e) { // possible failure and need to do something about it }

Και με το Reflect API:

if (Reflect.defineProperty(obj, name, desc)) { // success } else { // failure (and far better) } 

Η εντύπωση της λειτουργίας πρώτης κατηγορίας

Μπορούμε να βρούμε την ύπαρξη μιας ιδιότητας για ένα αντικείμενο ως (prop in obj). Εάν πρέπει να την χρησιμοποιήσουμε πολλές φορές στον κώδικα μας, πρέπει να τυλίξουμε ρητά αυτήν τη λειτουργία σε μια συνάρτηση και να περάσουμε τη λειτουργία ως τιμή πρώτης κατηγορίας.

Στο ES6, είχαμε ήδη αυτά ως μέρος της Reflect APIλειτουργίας πρώτης κατηγορίας. Για παράδειγμα, το Reflect.has (obj, prop) είναι το λειτουργικό ισοδύναμο του (prop in obj).

Ας δούμε ένα άλλο παράδειγμα: Διαγράψτε μια ιδιότητα αντικειμένου.

const obj = { bar: true, baz: false}; // delete object[key] function deleteProperty(object, key) { delete object[key]; } deleteProperty(obj, 'bar'); 

Με το Reflect API:

// With Reflect API Reflect.deleteProperty(obj, 'bar'); 

Ένας πιο αξιόπιστος τρόπος χρήσης της μεθόδου apply ()

Στο ES5, μπορούμε να χρησιμοποιήσουμε τη apply()μέθοδο για να καλέσουμε μια συνάρτηση με μια δεδομένη thisτιμή και να περάσουμε έναν πίνακα ως όρισμα.

Function.prototype.apply.call(func, obj, arr); // or func.apply(obj, arr); 

Αυτό είναι λιγότερο αξιόπιστο γιατί funcθα μπορούσε να είναι ένα αντικείμενο που θα είχε ορίσει τη δική του applyμέθοδο.

Στο ES6 έχουμε έναν πιο αξιόπιστο και κομψό τρόπο επίλυσης αυτού:

Reflect.apply(func, obj, arr); 

Σε αυτήν την περίπτωση, θα λάβουμε ένα TypeErrorαν funcδεν είναι κλήσιμο. Επίσης, Reflect.apply()είναι λιγότερο ρητό και κατανοητό.

Βοηθώντας άλλα είδη προβληματισμού

Εμείςθα δει τι σημαίνει αυτό λίγο όταν μαθαίνουμε για το Proxyαντικείμενο. Οι μέθοδοι Reflect API μπορούν να χρησιμοποιηθούν με Proxy σε πολλές περιπτώσεις χρήσης.

Το αντικείμενο διακομιστή μεσολάβησης

Το Proxyαντικείμενο του ES6 βοηθάει intercession.

Το proxyαντικείμενο καθορίζει προσαρμοσμένες συμπεριφορές για θεμελιώδεις λειτουργίες (για παράδειγμα, αναζήτηση ιδιοκτησίας, ανάθεση, απαρίθμηση, επίκληση συναρτήσεων και ούτω καθεξής).

Ακολουθούν μερικοί χρήσιμοι όροι που πρέπει να θυμάστε και να χρησιμοποιήσετε:

  • Το target: Ένα αντικείμενο που ο διακομιστής μεσολάβησης εικονικοποιείται.
  • Το handler: Ένα αντικείμενο κράτησης θέσης που περιέχει παγίδες.
  • Οι trap: Μέθοδοι που παρέχουν πρόσβαση ιδιοκτησίας στο αντικείμενο προορισμού.

Είναι εντάξει αν δεν καταλαβαίνετε ακόμη από την παραπάνω περιγραφή. Θα το κατανοήσουμε μέσω κώδικα και παραδειγμάτων σε ένα λεπτό.

Η σύνταξη για τη δημιουργία ενός αντικειμένου διακομιστή μεσολάβησης έχει ως εξής:

let proxy = new Proxy(target, handler); 

Υπάρχουν πολλές παγίδες μεσολάβησης (λειτουργίες χειριστή) για πρόσβαση και προσαρμογή ενός αντικειμένου στόχου. Εδώ είναι η λίστα αυτών. Μπορείτε να διαβάσετε μια πιο λεπτομερή περιγραφή των παγίδων εδώ.

handler.apply() handler.construct() handler.get() handler.has() handler.ownKeys() handler.set() handler.setPrototypeOf() handler.getPrototypeOf() handler.defineProperty() handler.deleteProperty() handler.getOwnPropertyDescriptor() handler.preventExtensions() handler.isExtensible() 

Note that each of the traps has a mapping with the Reflect object's methods. This means that you can use Reflect and Proxy together in many use cases.

How to get unavailable object property values

Let's look at an example of an employee object and try to print some of its properties:

const employee = { firstName: 'Tapas', lastName: 'Adhikary' }; console.log(employee.firstName); console.log(employee.lastName); console.log(employee.org); console.log(employee.fullName); 

The expected output is the following:

Tapas Adhikary undefined undefined 

Now let's use the Proxy object to add some custom behavior to the employee object.

Step 1: Create a Handler that uses a get trap

We will use a trap called get which lets us get a property value. Here is our handler:

let handler = { get: function(target, fieldName) { if(fieldName === 'fullName' ) { return `${target.firstName} ${target.lastName}`; } return fieldName in target ? target[fieldName] : `No such property as, '${fieldName}'!` } }; 

The above handler helps to create the value for the fullName property. It also adds a better error message when an object property is missing.

Step 2: Create a Proxy Object

As we have the target employee object and the handler, we will be able to create a Proxy object like this:

let proxy = new Proxy(employee, handler); 

Step 3: Access the properties on the Proxy object

Now we can access the employee object properties using the proxy object, like this:

console.log(proxy.firstName); console.log(proxy.lastName); console.log(proxy.org); console.log(proxy.fullName); 

The output will be:

Tapas Adhikary No such property as, 'org'! Tapas Adhikary 

Notice how we have magically changed things for the employee object!

Proxy for Validation of Values

Let's create a proxy object to validate an integer value.

Step 1: Create a handler that uses a set trap

The handler looks like this:

const validator = { set: function(obj, prop, value) { if (prop === 'age') { if(!Number.isInteger(value)) { throw new TypeError('Age is always an Integer, Please Correct it!'); } if(value < 0) { throw new TypeError('This is insane, a negative age?'); } } } }; 

Step 2: Create a Proxy Object

Create a proxy object like this:

let proxy = new Proxy(employee, validator); 

Step 3: Assign a non-integer value to a property, say, age

Try doing this:

proxy.age = 'I am testing a blunder'; // string value 

The output will be like this:

TypeError: Age is always an Integer, Please Correct it! at Object.set (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:28:23) at Object. (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:40:7) at Module._compile (module.js:652:30) at Object.Module._extensions..js (module.js:663:10) at Module.load (module.js:565:32) at tryModuleLoad (module.js:505:12) at Function.Module._load (module.js:497:3) at Function.Module.runMain (module.js:693:10) at startup (bootstrap_node.js:188:16) at bootstrap_node.js:609:3 

Similarly, try doing this:

p.age = -1; // will result in error 

How to use Proxy and Reflect together

Here is an example of a handler where we use methods from the Reflect API:

const employee = { firstName: 'Tapas', lastName: 'Adhikary' }; let logHandler = { get: function(target, fieldName) { console.log("Log: ", target[fieldName]); // Use the get method of the Reflect object return Reflect.get(target, fieldName); } }; let func = () => { let p = new Proxy(employee, logHandler); p.firstName; p.lastName; }; func();

A few more Proxy use cases

There are several other use-cases where this concept can be used.

  • To protect the ID field of an object from deletion (trap: deleteProperty)
  • To trace Property Accesses (trap: get, set)
  • For Data Binding (trap: set)
  • With revocable references
  • To manipulate the in operator behavior

... and many more.

Metaprogramming Pitfalls

While the concept of Metaprogramming gives us lots of power, the magic of it can go the wrong way sometimes.

Be careful of:

  • Too much magic! Make sure you understand it before you apply it.
  • Possible performance hits when you're making the impossible possible
  • Could be seen as counter-debugging.

In Summary

To summarize,

  • Reflect and Proxy are great inclusions in JavaScript to help with Metaprogramming.
  • Lots of complex situations can be handled with their help.
  • Να γνωρίζετε και τα μειονεκτήματα.
  • Τα σύμβολα ES6 μπορούν επίσης να χρησιμοποιηθούν με τις υπάρχουσες τάξεις και αντικείμενα για να αλλάξουν τη συμπεριφορά τους.

Ελπίζω να βρείτε αυτό το άρθρο διορατικό. Όλος ο πηγαίος κώδικας που χρησιμοποιείται σε αυτό το άρθρο μπορεί να βρεθεί στο αποθετήριο GitHub.

Μοιραστείτε το άρθρο, ώστε να μπορούν να το διαβάσουν και άλλοι. Μπορείτε να μου @ στο Twitter (@tapasadhikary) με σχόλια ή να διστάσετε να με ακολουθήσετε.