Εγχειρίδιο κλεισίματος JavaScript - με παράδειγμα κώδικα κλεισίματος JS

Κλείσιμο - πολλοί από εσάς οι προγραμματιστές JavaScript πιθανότατα έχουν ακούσει αυτόν τον όρο στο παρελθόν. Όταν ξεκίνησα το ταξίδι μου με JavaScript, αντιμετώπισα συχνά κλείσιμο. Και νομίζω ότι είναι μία από τις πιο σημαντικές και ενδιαφέρουσες έννοιες στο JavaScript.

Δεν νομίζετε ότι είναι ενδιαφέροντα; Αυτό συμβαίνει συχνά όταν δεν καταλαβαίνετε μια έννοια - δεν την ενδιαφέρεστε. (Δεν ξέρω αν αυτό συμβαίνει σε εσάς ή όχι, αλλά αυτό ισχύει για μένα).

Έτσι, σε αυτό το άρθρο, θα προσπαθήσω να κάνω το κλείσιμο ενδιαφέρον για εσάς.

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

Λεξικό Πεδίο

Ίσως σκέφτεστε - ξέρω το τοπικό και το παγκόσμιο πεδίο, αλλά τι είναι το λεξιλόγιο; Αντέδρασα με τον ίδιο τρόπο όταν άκουσα αυτόν τον όρο. Μην ανησυχείς! Ας ρίξουμε μια πιο προσεκτική ματιά.

Είναι απλό όπως και άλλα δύο πεδία:

function greetCustomer() { var customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); // Hi! anchal } greetingMsg(); }

Μπορείτε να δείτε από την παραπάνω έξοδο ότι η εσωτερική λειτουργία μπορεί να έχει πρόσβαση στη μεταβλητή της εξωτερικής λειτουργίας. Πρόκειται για λεξιλογικό εύρος, όπου το εύρος και η τιμή μιας μεταβλητής καθορίζεται από το πού ορίζεται / δημιουργείται (δηλαδή, η θέση της στον κώδικα). Το έπιασα?

Ξέρω ότι το τελευταίο κομμάτι μπορεί να σε μπερδέψει. Επιτρέψτε μου λοιπόν να σας πάρω βαθύτερα. Γνωρίζατε ότι το λεξικό scoping είναι επίσης γνωστό ως στατικό scoping ; Ναι, αυτό είναι το άλλο του όνομα.

Υπάρχει επίσης δυναμικό πεδίο εφαρμογής , το οποίο υποστηρίζουν ορισμένες γλώσσες προγραμματισμού. Γιατί ανέφερα το δυναμικό πεδίο εφαρμογής; Επειδή μπορεί να σας βοηθήσει να κατανοήσετε καλύτερα το λεξικό.

Ας δούμε μερικά παραδείγματα:

function greetingMsg() { console.log(customerName);// ReferenceError: customerName is not defined } function greetCustomer() { var customerName = "anchal"; greetingMsg(); } greetCustomer();

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

Ας δούμε ένα άλλο παράδειγμα:

function addNumbers(number1) { console.log(number1 + number2); } function addNumbersGenerate() { var number2 = 10; addNumbers(number2); } addNumbersGenerate();

Η παραπάνω έξοδος θα είναι 20 για μια δυναμική γλώσσα. Οι γλώσσες που υποστηρίζουν τη λεξική εμβέλεια θα δώσουνreferenceError: number2 is not defined. Γιατί;

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

Το όνομά του είναι αυτονόητο - «δυναμικό» σημαίνει αλλαγή. Το εύρος και η τιμή της μεταβλητής μπορεί να διαφέρουν καθώς εξαρτάται από το πού καλείται η συνάρτηση. Η έννοια μιας μεταβλητής μπορεί να αλλάξει κατά το χρόνο εκτέλεσης.

Έχετε την ουσία της δυναμικής κάλυψης; Εάν ναι, τότε απλώς θυμηθείτε ότι το λεξικό είναι το αντίθετο.

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

Έτσι, η λεξική ή στατική εμβέλεια σημαίνει ότι το εύρος και η τιμή μιας μεταβλητής καθορίζεται από το σημείο που ορίζεται. Δεν αλλάζει.

Ας δούμε και πάλι το παραπάνω παράδειγμα και θα προσπαθήσουμε να καταλάβουμε τη δική σας έξοδο. Μόνο μια περιστροφή - δηλώστε number2στην κορυφή:

var number2 = 2; function addNumbers(number1) { console.log(number1 + number2); } function addNumbersGenerate() { var number2 = 10; addNumbers(number2); } addNumbersGenerate(); 

Ξέρετε ποια θα είναι η έξοδος;

Σωστό - είναι 12 για λεξιλογικές γλώσσες. Αυτό συμβαίνει επειδή πρώτα, εξετάζει μια addNumbersσυνάρτηση (το πιο εσωτερικό πεδίο) και έπειτα αναζητά προς τα μέσα, όπου ορίζεται αυτή η συνάρτηση. Καθώς παίρνει τη number2μεταβλητή, που σημαίνει ότι η έξοδος είναι 12.

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

Γιατί; Θα λάβετε την απάντησή σας όταν εξετάσουμε τον ορισμό του κλεισίματος. Ας πάμε λοιπόν στην πίστα και επιστρέψουμε στο κλείσιμο

Τι είναι το κλείσιμο;

Ας δούμε τον ορισμό του κλεισίματος:

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

1. Οι δικές του μεταβλητές.

2. Οι μεταβλητές και τα επιχειρήματα της εξωτερικής συνάρτησης.

3. Καθολικές μεταβλητές.

Περίμενε! Είναι αυτός ο ορισμός ενός κλεισίματος ή λεξικού σκοπού; Και οι δύο ορισμοί φαίνονται ίδιοι. Πώς είναι διαφορετικά;

Λοιπόν, γι 'αυτό ορίσαμε το λεξιλογικό πεδίο παραπάνω. Επειδή το κλείσιμο σχετίζεται με λεξική / στατική κάλυψη.

Ας δούμε και πάλι τον άλλο ορισμό του που θα σας πει πώς διαφέρουν τα κλεισίματα.

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

Ή,

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

Ταραγμένος? Μην ανησυχείτε εάν δεν έχετε ακόμη καταλάβει το θέμα. Έχω παραδείγματα για να σας βοηθήσω να κατανοήσετε καλύτερα. Ας τροποποιήσουμε το πρώτο παράδειγμα λεξιλογικού σκοπού:

function greetCustomer() { const customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); } return greetingMsg; } const callGreetCustomer = greetCustomer(); callGreetCustomer(); // output – Hi! anchal

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

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

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

Για να αποκτήσετε μια καλύτερη αίσθηση για αυτό, ας χρησιμοποιήσουμε τη dir()μέθοδο της κονσόλας για να εξετάσουμε τη λίστα των ιδιοτήτων των callGreetCustomer:

console.dir(callGreetCustomer);

Από την παραπάνω εικόνα, μπορείτε να δείτε πώς η εσωτερική συντήρηση διατηρεί το γονικό της εύρος ( customerName) όταν greetCustomer()εκτελείται. Και αργότερα, χρησιμοποιήθηκε customerNameόταν callGreetCustomer()εκτελέστηκε.

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

Τι ακολουθεί λοιπόν; Ας κάνουμε αυτό το θέμα πιο ενδιαφέρον, εξετάζοντας διαφορετικά παραδείγματα.

Παραδείγματα κλεισίματος σε δράση

function counter() { let count = 0; return function() { return count++; }; } const countValue = counter(); countValue(); // 0 countValue(); // 1 countValue(); // 2

Κάθε φορά που καλείτε countValue, η τιμή της μεταβλητής μέτρησης αυξάνεται κατά 1. Περιμένετε - νομίζατε ότι η τιμή της μέτρησης είναι 0;

Well, that would be wrong as a closure doesn’t work with a value. It stores the reference of the variable. That’s why, when we update the value, it reflects in the second or third call and so on as the closure stores the reference.

Feeling a bit clearer now? Let’s look at another example:

function counter() { let count = 0; return function () { return count++; }; } const countValue1 = counter(); const countValue2 = counter(); countValue1(); // 0 countValue1(); // 1 countValue2(); // 0 countValue2(); // 1 

I hope you guessed the right answer. If not, here is the reason. As countValue1 and countValue2, both preserve their own lexical scope. They have independent lexical environments. You can use dir() to check the [[scopes]] value in both the cases.

Let’s look at a third example.

This one's a bit different. In it, we have to write a function to achieve the output:

const addNumberCall = addNumber(7); addNumberCall(8) // 15 addNumberCall(6) // 13

Simple. Use your newly-gained closure knowledge:

function addNumber(number1) { return function (number2) { return number1 + number2; }; }

Now let’s look at some tricky examples:

function countTheNumber() { var arrToStore = []; for (var x = 0; x < 9; x++) { arrToStore[x] = function () { return x; }; } return arrToStore; } const callInnerFunctions = countTheNumber(); callInnerFunctions[0]() // 9 callInnerFunctions[1]() // 9

Every array element that stores a function will give you an output of 9. Did you guess right? I hope so, but still let me tell you the reason. This is because of the closure's behavior.

The closure stores the reference, not the value. The first time the loop runs, the value of x is 0. Then the second time x is 1, and so on. Because the closure stores the reference, every time the loop runs it's changing the value of x. And at last, the value of x will be 9. So callInnerFunctions[0]() gives an output of 9.

But what if you want an output of 0 to 8? Simple! Use a closure.

Think about it before looking at the solution below:

function callTheNumber() { function getAllNumbers(number) { return function() { return number; }; } var arrToStore = []; for (var x = 0; x < 9; x++) { arrToStore[x] = getAllNumbers(x); } return arrToStore; } const callInnerFunctions = callTheNumber(); console.log(callInnerFunctions[0]()); // 0 console.log(callInnerFunctions[1]()); // 1

Here, we have created separate scope for each iteration. You can use console.dir(arrToStore) to check the value of x in [[scopes]] for different array elements.

Αυτό είναι! Ελπίζω να μπορείτε τώρα να πείτε ότι θεωρείτε ενδιαφέρον το κλείσιμο.

Για να διαβάσετε τα άλλα άρθρα μου, δείτε το προφίλ μου εδώ.