diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js index d5a09efb3..7790dfa0e 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js @@ -1,7 +1,7 @@ function spy(func) { function wrapper(...args) { - // using ...args instead of arguments to store "real" array in wrapper.calls + // χρησιμοποιώντας ...args αντί για ορίσματα για να κρατήσουμε τον "πραγματικό" πίνακα στην wrapper.calls wrapper.calls.push(args); return func.apply(this, args); } diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md index 0c8a211b4..2a3e076aa 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md @@ -1 +1 @@ -The wrapper returned by `spy(f)` should store all arguments and then use `f.apply` to forward the call. +Η συνάρτηση-κάλλυμα που επιστρέφεται από τη `spy(f)` πρέπει να αποθηκεύει όλα τα ορίσματα και μετά να χρησιμοποιεί την `f.apply` για να προωθήσει τη κλήση. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md index a3843107c..1f935ea23 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md @@ -2,17 +2,17 @@ importance: 5 --- -# Spy decorator +# Διακοσμητής κατάσκοπος -Create a decorator `spy(func)` that should return a wrapper that saves all calls to function in its `calls` property. +Δημιούργησε ένα διακοσμητή `spy(func)` που επιστρέφει έναν wrapper που αποθηκέυει όλες τις κλήσεις στη συνάρτηση στην `calls` ιδιότητα του. -Every call is saved as an array of arguments. +Κάθε κλήση αποθηκέυεται σαν ένας πίνακας από ορίσματα. -For instance: +Για παράδειγμα: ```js function work(a, b) { - alert( a + b ); // work is an arbitrary function or method + alert( a + b ); // η work είναι μια τυχαία συνάρτηση η μέθοδος } *!* @@ -27,4 +27,4 @@ for (let args of work.calls) { } ``` -P.S. That decorator is sometimes useful for unit-testing. Its advanced form is `sinon.spy` in [Sinon.JS](http://sinonjs.org/) library. + Αυτός ο διακοσμητής είναι μερικές φορές χρήσιμος για unit-testing. Η προηγμένη του μορφή είναι το `sinon.spy` στην [Sinon.JS](http://sinonjs.org/) βιβλιοθήκη. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md index 24bb4d448..032428ca1 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md @@ -1,4 +1,4 @@ -The solution: +Η λύση: ```js run demo function delay(f, ms) { @@ -13,20 +13,19 @@ let f1000 = delay(alert, 1000); f1000("test"); // shows "test" after 1000ms ``` +Παρατήρησε πώς μια συνάρτηση-arrow χρησιμοποιείται εδώ. Όπως γνωρίζουμε, οι συναρτήσεις arrow δεν έχουν δικό τους `this`και `arguments`, οπότε το `f.apply(this, arguments)`παίρνει το `this` και τα `arguments` από τη συνάρτηση-κάλυμμα. -Please note how an arrow function is used here. As we know, arrow functions do not have own `this` and `arguments`, so `f.apply(this, arguments)` takes `this` and `arguments` from the wrapper. +Αν περάσουμε μια απλή συνάρτηση, η `setTimeout` θα το καλέσει χωρίς ορίσματα και θα ισχύει `this=window` (εφόσον είμαστε στον δρομολογήτη). -If we pass a regular function, `setTimeout` would call it without arguments and `this=window` (assuming we're in the browser). - -We still can pass the right `this` by using an intermediate variable, but that's a little bit more cumbersome: +Μπορούμε να περάσουμε το σωστό `this` χρησιμοποιώντας μια ενδιάμεση μεταβλητή, αλλά αυτή η τεχνική είναι λίγο πιο "δυσκίνητη": ```js function delay(f, ms) { return function(...args) { - let savedThis = this; // store this into an intermediate variable + let savedThis = this; // κράτησε αυτό σε μια ενδιάμεση μεταβλητή setTimeout(function() { - f.apply(savedThis, args); // use it here + f.apply(savedThis, args); // χρησιμοποίησε το εδώ }, ms); }; diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md index c04c68d7e..6e8a17e70 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md @@ -2,25 +2,25 @@ importance: 5 --- -# Delaying decorator +# Διακοσμητής καθυστέρησης -Create a decorator `delay(f, ms)` that delays each call of `f` by `ms` milliseconds. +Δημιούργησε ένα διακοσμητή `delay(f, ms)` που καθυστερεί κάθε κλήση της `f` χρόνο ίσο με `ms` δέκατα του δευτερολέπτου. -For instance: +Για παράδειγμα: ```js function f(x) { alert(x); } -// create wrappers +// δημιουργία συναρτήσεων-καλύμματα let f1000 = delay(f, 1000); let f1500 = delay(f, 1500); -f1000("test"); // shows "test" after 1000ms -f1500("test"); // shows "test" after 1500ms +f1000("τέστ"); // δείχνει τέστ μετά από 1000 δέκατα του δευτερολέπτου +f1500("τέστ"); // δείχνει τέστ μετά από 1500 δέκατα του δευτερολέπτου ``` -In other words, `delay(f, ms)` returns a "delayed by `ms`" variant of `f`. +Με άλλα λόγια, η `delay(f, ms)` επιστρέφει μια "καθυστερημένη κατά `ms`" εκδοχή της `f`. -In the code above, `f` is a function of a single argument, but your solution should pass all arguments and the context `this`. +Στον παραπάνω κώδικα, η `f` είναι μια συνάρτηση ενός μονού ορίσματος, αλλά η λύση σου πρέπει να περνάει όλα τα ορίσματα και το context `this`. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md index 83e75f315..43badc7cd 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md @@ -7,6 +7,20 @@ function debounce(func, ms) { }; } +``` + +Μια κλήση στη `debounce` επιστρέφει μια συνάρτηση-κάλυμμα. Μπορεί να υπάρχουν δύο καταστάσεις: + +- `isCooldown = false` -- έτοιμη να τρέξει. +- `isCooldown = true` -- περιμένοντας για το timeout. + +Στη πρώτη κλήση η `isCooldown` επιστρέφει μια τιμή λάθους, οπότε η κλήση προχωράει, και η κατάσταση αλλάζει σε `true`. + +Όοσ η `isCooldown` είναι αληθής, όλες οι άλλες κλήσεις μπορούν να αγνοηθούν. + +Τότε η `setTimeout` την ξανακάνει `false` μετά τη δοσμένη καθυστέρηση. + + ``` A call to `debounce` returns a wrapper. When called, it schedules the original function call after given `ms` and cancels the previous such timeout. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md index 347a5e64f..4384e75d1 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md @@ -2,15 +2,24 @@ importance: 5 --- -# Debounce decorator +# Debounce διακοσμητής -The result of `debounce(f, ms)` decorator is a wrapper that suspends calls to `f` until there's `ms` milliseconds of inactivity (no calls, "cooldown period"), then invokes `f` once with the latest arguments. -In other words, `debounce` is like a secretary that accepts "phone calls", and waits until there's `ms` milliseconds of being quiet. And only then it transfers the latest call information to "the boss" (calls the actual `f`). +Το αποτέλεσμα του `debounce(f, ms)` διακοσμητή πρέπει να είναι μια συνάρτηση-κάλυμμα η οποία περνάει τη κλήση στην `f` το πολύ μια φορά ανά `ms` δέκατα του δευτερολέπτου. -For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`. +Με άλλα λόγια, όταν καλούμε μια "debounced" συνάρτηση, μας εγγυάται ότι όλες οι μελλοντικές κλήσεις στη συνάρτηση που κάναν λιγότερο από `ms` δέκατα του δευτερολέπτου μετά τη προηγούμενη κλήση θα αγνοηθούν. -Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there are no calls, then the actual `f` will be only called once, at 1500ms. That is: after the cooldown period of 1000ms from the last call. +Για παράδειγμα: + +f(1); // τρέχει αμέσως +f(2); // αγνοείται + +setTimeout( () => f(3), 100); // αγνοείται ( μόνο 100 ms περάσαν ) +setTimeout( () => f(4), 1100); // τρέχει +setTimeout( () => f(5), 1500); // αγνοείται (λιγότερο από 1000 ms από το τελευταίο τρέξιμο) +``` + +Στη πρακτική η `debounce` είναι χρήσιμη για συναρτήσεις που ανακτούν/ανανεώνουν κάτι όταν γνωρίζουμε ότι τίποτα καινούργιο δεν μπορεί να γίνει σε μια τόσο μικρή περίοδο χρόνου, οπότε είναι καλύτερο να μην σπαταληθούν πόροι. ![](debounce.svg) @@ -49,3 +58,4 @@ It waits the given time after the last call, and then runs its function, that ca The task is to implement `debounce` decorator. Hint: that's just a few lines if you think about it :) + diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md index cf851f771..f939b8ffd 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md @@ -30,10 +30,10 @@ function throttle(func, ms) { } ``` -A call to `throttle(func, ms)` returns `wrapper`. +Μια κλήση στη `throttle(func, ms)` επιστρέφει το `wrapper`. -1. During the first call, the `wrapper` just runs `func` and sets the cooldown state (`isThrottled = true`). -2. In this state all calls are memorized in `savedArgs/savedThis`. Please note that both the context and the arguments are equally important and should be memorized. We need them simultaneously to reproduce the call. -3. After `ms` milliseconds pass, `setTimeout` triggers. The cooldown state is removed (`isThrottled = false`) and, if we had ignored calls, `wrapper` is executed with the last memorized arguments and context. +1. Κατά τη διάρκεια της πρώτης κλήσης, η `wrapper` απλά τρέχει τη `func` και θέτει τη κατάσταση παγώματος (`isThrottled = true`). +2. Στη κατάσταση αυτή όλες οι κλήσεις αποθηκεύονται στις `savedArgs/savedThis`. Παρακαλώ παρατήρησε ότι και το πλαίσιο και τα ορίσματα είναι το ίδιο σημαντικά και πρέπει να αποθηκευτούν. Τα θέλουμε ταυτόχρονα για να ξανακάνουμε τη κλήση. +3. Μετά από `ms` δέκατα του δευτερολέπτου, η `setTimeout` τρέχει. Η περίοδος παγώματος αφαιρείται (`isThrottled = false`) και, αν είχαμε αγνοημένες κλήσεις, η `wrapper` εκτελείται με τα τελευταία αποθηκευμένα ορίσματα και πλαίσιο. -The 3rd step runs not `func`, but `wrapper`, because we not only need to execute `func`, but once again enter the cooldown state and setup the timeout to reset it. +Το 3ο βήμα τρέχει όχι τη `func`, αλλά τη `wrapper`, γιατί δεν θέλουμε μόνο να εκτελέσουμε τη `func`, αλλά για άλλη μια φορά να μπούμε στη κατάσταση παγώματος και να θέσουμε το timeout για να το επαναφέρει. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md index 6df7af132..d68d15b9a 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md @@ -2,7 +2,12 @@ importance: 5 --- -# Throttle decorator +# Throttle διακοσμητής + + +Δημιούργησε ένα "throttling" διακοσμητή `throttle(f, ms)` -- που επιστρέφει μια συνάρτηση κάλυμμα, δίνοντας τη κλήση στην `f` το πολύ μια φορά ανά `ms` δέκατα του δευτερολέπτου. Αυτές οι κλήσεις που πέφτου μέσα στη περίοδο "παγώματος", αγνοούνται. + +**Η διαφορά με τη `debounce` -- αν μια κλήση που αγνοήθηκε είναι η τελευταία κατά το πάγωμα, τότε εκτελείται στο τέλος της καθυστέρησης.** Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper. @@ -14,40 +19,44 @@ The difference with debounce is that it's completely different decorator: In other words, `throttle` is like a secretary that accepts phone calls, but bothers the boss (calls the actual `f`) not more often than once per `ms` milliseconds. -Let's check the real-life application to better understand that requirement and to see where it comes from. -**For instance, we want to track mouse movements.** +Ας δουμε την πρακτική εφαρμογή για να καταλάβουμε καλύτερα την απαίτηση αυτή και να δούμε από που προέρχεται. + +**Για παράδειγμα, θέλουμε να ιχνηλατήσουμε της κινήσεις του ποντικιού.** -In a browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms). -**We'd like to update some information on the web-page when the pointer moves.** +Σε ένα δρομολογητή, μπορούμε να φτιάξουμε μια συνάρτηση να τρέχει σε κάθε κίνηση του ποντικιού και να παίρνουμε τη θέση του δείκτη όσο κινείται. Κατά τη διάρκεια μια ενεργούς χρήσης του ποντικιού, η συνάρτηση αυτή συνήθως τρέχει πολύ συχνά, μπορεί να είναι κάτι σαν 100 φορές το δευτερόλεπτο (κάθε 10 ms). -...But updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in updating more often than once per 100ms. +**Θα θέλαμε να ανανεώσουμε κάποια πληροφορία στην ιστοσελίδα όταν ο δείκτης κινείται.** -So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but forward the call to `update()` at maximum once per 100ms. -Visually, it will look like this: +..Αλλα ανανεώνοντας τη συνάρτηση `update()` είναι πολύ βαρύ για να γίνεται σε κάθε μικρο-κίνηση. Επίσης, δεν υπάρχει λόγος να ανανεώνεται πιο συχνά από μια φορά ανά 100ms. -1. For the first mouse movement the decorated variant immediately passes the call to `update`. That's important, the user sees our reaction to their move immediately. -2. Then as the mouse moves on, until `100ms` nothing happens. The decorated variant ignores calls. -3. At the end of `100ms` -- one more `update` happens with the last coordinates. -4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, quite important, the final mouse coordinates are processed. +Οπότε θα το τυλίξουμε σε ένα διακοσμητή: χρησιμοποίησε το `throttle(update, 100)` σαν τη συνάρτηση για να τρέχει σε κάθε κίνηση του ποντικιού αντί για την αρχική `update()`. Ο διακοσμητής θα καλείται συχνά, αλλα θα προωθεί τη κλήση στην `update()` το πολύ μια φορά ανά 100ms. -A code example: +Οπτικά, θα μοιάζει κάπως έτσι: + +1. Για τη πρώτη κίνηση του ποντικιού η διακοσμημένη εκδοχή αμέσως δίνει τη κλήσει στην `update`. Αυτό είναι σημαντικό, ο χρήστης βλέπει την αντίδραση μας στην κίνηση του αμέσως. +2. Μετά, όσο το ποντίκι κινείται, μέχρι τα `100ms` δεν γίνεται τίποτα. Η διακοσμημένη εκδοχή αγνοεί τις κλήσεις. +3. Στο τέλος των `100ms` -- μια ακόμα `update` γίνεται με τις τελευταίες συντεταγμένες. +4. Τότε, τελικά, το ποντίκι σταματάει κάπου. Η διακοσμημένη εκδοχή περιμένει μέχρι τα `100ms` περάσουν και μετά τρέχει την `update` με τις τελευταίες συντεταγμένες. Άρα, αρκετά σημαντικό είναι ότι, η τελευταίες συντεταγμένες του ποντικιού επεξεργάζονται. + +Ένα παράδειγμα κώδικα: ```js function f(a) { console.log(a); } -// f1000 passes calls to f at maximum once per 1000 ms +// f1000 περνάει τις κλήσεις στην f το πολύ μια φορά αν 1000 ms let f1000 = throttle(f, 1000); -f1000(1); // shows 1 -f1000(2); // (throttling, 1000ms not out yet) -f1000(3); // (throttling, 1000ms not out yet) +f1000(1); // δείχνει 1 +f1000(2); // (throttling, 1000ms δεν έχουν περάσει) +f1000(3); // (throttling, 1000ms δεν έχουν περάσει) -// when 1000 ms time out... -// ...outputs 3, intermediate value 2 was ignored +// όταν 1000 ms περάσουν... +// ...εξάγει 3, η ενδιάμεση τιμή 2 αγνοείται ``` -P.S. Arguments and the context `this` passed to `f1000` should be passed to the original `f`. +Υ.Σ. Ορίσματα και το πλαίσιο `this` που δίνεται στην `f1000` πρέπε να περαστεί στην αρική συνάρτηση `f`. + diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md index d0dda4df1..9cfebc299 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md @@ -1,21 +1,21 @@ -# Decorators and forwarding, call/apply +# Διακοσμητές και προώθηση, call/apply -JavaScript gives exceptional flexibility when dealing with functions. They can be passed around, used as objects, and now we'll see how to *forward* calls between them and *decorate* them. +Η JavaScript δίνει εξαιρετική ευελιξία στη χρήση συναρτήσεων. Μπορούν να δοθούν σαν ορίσματα, να χρησιμοποιηθούν σαν αντικείμενα και τώρα θα δούμε πως γίνεται να *προωθήσουμε* κλήσεις μεταξύ τους και να τις *διακοσμήσουμε*. -## Transparent caching +## Διαφανές caching -Let's say we have a function `slow(x)` which is CPU-heavy, but its results are stable. In other words, for the same `x` it always returns the same result. +Ας υποθέσουμε ότι έχουμε μια συνάρτηση `slow(x)` η οποία απαιτεί υψηλή επεξεργαστική ισχύ όμως τα αποτελέσματα της είναι σταθερά. Με άλλα λόγια, για το ίδιο `x` επιστρέφει πάντα το ίδιο αποτέλεσμα. -If the function is called often, we may want to cache (remember) the results to avoid spending extra-time on recalculations. +Αν η συνάρτηση καλείται συχνά, ενδέχεται να θέλουμε να κάνουμε cache (θυμόμαστε) τα αποτελέσματα προκείμενου να μην χρειάζεται να δαπανήσουμε επιπλέον χρόνο σε επανυπολογισμούς. -But instead of adding that functionality into `slow()` we'll create a wrapper function, that adds caching. As we'll see, there are many benefits of doing so. +Όμως, αντί να προσθέσουμε αυτή τη λειτουργικότητα στη `slow()` θα δημιουρήσουμε μια συνάρτηση-κάλυμμα, η οποία θα προσθέσει το caching. Όπως θα δούμε, υπάρχουν αρκετά πλεονεκτήματα σε αυτή τη μέθοδο. -Here's the code, and explanations follow: +Αυτός είναι ο κώδικας, και οι επεξηγήσεις ακολουθούν: ```js run function slow(x) { - // there can be a heavy CPU-intensive job here - alert(`Called with ${x}`); + // μπορεί να είναι μια διεργασία που απαιτεί υψηλή επεξεργαστική ισχύ εδώ + alert(`Καλέστηκε με ${x}`); return x; } @@ -23,65 +23,65 @@ function cachingDecorator(func) { let cache = new Map(); return function(x) { - if (cache.has(x)) { // if there's such key in cache - return cache.get(x); // read the result from it + if (cache.has(x)) { // αν υπάρχει τέτοιο κλειδί στο cache + return cache.get(x); // διάβασε το αποτέλεσμα από αυτό } - let result = func(x); // otherwise call func + let result = func(x); // αλλιώς κάλεσε την func - cache.set(x, result); // and cache (remember) the result + cache.set(x, result); // και κανε cached (θυμήσου) το αποτέλεσμα return result; }; } slow = cachingDecorator(slow); -alert( slow(1) ); // slow(1) is cached -alert( "Again: " + slow(1) ); // the same +alert( slow(1) ); // slow(1) είναι cached +alert( "Ξανά: " + slow(1) ); // το ίδιο -alert( slow(2) ); // slow(2) is cached -alert( "Again: " + slow(2) ); // the same as the previous line +alert( slow(2) ); // slow(2) είναι cached +alert( "Ξανά: " + slow(2) ); // το ίδιο με τη προηγούμενη γραμμή ``` -In the code above `cachingDecorator` is a *decorator*: a special function that takes another function and alters its behavior. +Στον από πάνω κώδικα, ο `cachingDecorator` είναι ένας *διακοσμητής*: μια ειδική συνάρτηση η οποία παίρνει μια άλλη συνάρτηση και επηρεάζει την συμπεριφορά της. -The idea is that we can call `cachingDecorator` for any function, and it will return the caching wrapper. That's great, because we can have many functions that could use such a feature, and all we need to do is to apply `cachingDecorator` to them. +Η ιδέα είναι ότι μπορούμε να καλέσουμε τον `cachingDecorator` για κάθε συνάρτηση, και αυτός θα επιστρέψει τη συνάρτηση-κάλυμμα του cache. Αυτό είναι πολύ καλό, γιατί μπορούμε να έχουμε πολλές συναρτήσεις που μπορούν να χρησιμοποιήσουν αυτό το χαρακτηριστικό, και το μόνο που χρειάζεται να κάνουμε είναι να εφαρμόσουμε τον `cachingDecorator` σε αυτές. -By separating caching from the main function code we also keep the main code simpler. +Με το να διαχωρίζουμε το caching από τη συνάρτηση με τον βασικό κώδικα, διατηρούμε και τον βασικό κώδικα πιο απλό. -The result of `cachingDecorator(func)` is a "wrapper": `function(x)` that "wraps" the call of `func(x)` into caching logic: +Το αποτέλεσμα της `cachingDecorator(func)` είναι μια συνάρτηση-κάλυμμα: `function(x)` που "καλύπτει" το κάλεσμα της `func(x)` με λογική caching: ![](decorator-makecaching-wrapper.svg) -From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior. +Από κάποιον εξωτερικό κώδικα, η συνάρτηση που επικαλύπτεται `slow` συνεχίζει να κάνει το ίδιο. Απλά προστέθηκε ένας caching παράγοντας στη συμπεριφορά της. -To summarize, there are several benefits of using a separate `cachingDecorator` instead of altering the code of `slow` itself: +Για να συνοψίσουμε, υπάρχουν αρκετά πλεονεκτήματα της χρήσης ενός ξεχωριστού `cachingDecorator` αντί να αλλάξει ο κώδικας της ίδιας της `slow`. -- The `cachingDecorator` is reusable. We can apply it to another function. -- The caching logic is separate, it did not increase the complexity of `slow` itself (if there was any). -- We can combine multiple decorators if needed (other decorators will follow). +- Ο `cachingDecorator` είναι επαναχρησιμοποιήσιμος. Μπορούμε να την εφαρμόσουμε σε άλλη συνάρτηση. +- Η λογική cache είναι διαχωρισμένη, δεν αύξησε τη πολυπλοκότητα της ίδιας της `slow` (εαν υπήρχε κάποια). +- Μπορούμε να συνδυάσουμε πολλαπλούς διακοσμητές αν χρειάζεται (οι άλλοι διακοσμητές θα ακολουθήσουν). -## Using "func.call" for the context +## Χρησιμοποιώντας "func.call" για το πλαίσιο. -The caching decorator mentioned above is not suited to work with object methods. +O διακοσμητής caching που αναφέρθυηκε πάνω δεν είναι κατάλληλος για χρήση με μεθόδους αντικειμένου. -For instance, in the code below `worker.slow()` stops working after the decoration: +Για παράδειγμα, στον κώδικα κάτω η `worker.slow()` σταματάει να τρέχει μετά την διακόσμηση. ```js run -// we'll make worker.slow caching +// θα παράξουμε caching με τη worker.slow let worker = { someMethod() { return 1; }, slow(x) { - // scary CPU-heavy task here - alert("Called with " + x); + // "βαριά" υπολογιστική διαδικασία εδώ + alert("Καλέστηκε με " + x); return x * this.someMethod(); // (*) } }; -// same code as before +// ο ίδιος κώδικας με πριν function cachingDecorator(func) { let cache = new Map(); return function(x) { @@ -96,78 +96,78 @@ function cachingDecorator(func) { }; } -alert( worker.slow(1) ); // the original method works +alert( worker.slow(1) ); // η αρχική μέθοδος δουλεύει -worker.slow = cachingDecorator(worker.slow); // now make it caching +worker.slow = cachingDecorator(worker.slow); // χρησιμοποίησε το caching *!* alert( worker.slow(2) ); // Whoops! Error: Cannot read property 'someMethod' of undefined */!* ``` -The error occurs in the line `(*)` that tries to access `this.someMethod` and fails. Can you see why? +Το error προκύπτει στην γραμμή `(*)` που προσπαθεί να έχει πρόσβαση στη `this.someMethod` και αποτυγχάνει. Μπορείς να δεις γιατί? -The reason is that the wrapper calls the original function as `func(x)` in the line `(**)`. And, when called like that, the function gets `this = undefined`. +Ο λόγος είναι ότι η συνάρτηση-κάλυμμα καλεί την αρχική συνάρτηση σαν `func(x)` στη γραμμή `(**)`. Και, όταν καλείται με αυτό το τρόπο, η συνάρτηση παίρνει `this = undefined`. -We would observe a similar symptom if we tried to run: +Θα παρατηρούσαμε ένα παρόμοιο αποτέλεσμα αν προσπαθούσαμε να τρέξουμε: ```js let func = worker.slow; func(2); ``` -So, the wrapper passes the call to the original method, but without the context `this`. Hence the error. +Οπότε, η συνάρτηση-κάλυμμα δίνει τη κλήση στην αρχική μέθοδο, αλλά χωρίς το πλαίσιο `this`. Έτσι δημιουργείτε το error. -Let's fix it. +Ας το διορθώσουμε. -There's a special built-in function method [func.call(context, ...args)](mdn:js/Function/call) that allows to call a function explicitly setting `this`. +Υπάρχει μια ειδική ενσωματωμένη συνάρτηση-μέθοδος [func.call(context, ...args)](mdn:js/Function/call) η οποία επιτρέπει τη κλήση μιας συνάρτησης ρητά θέτωντας το `this`. -The syntax is: +Η σύνταξη είναι: ```js func.call(context, arg1, arg2, ...) ``` -It runs `func` providing the first argument as `this`, and the next as the arguments. +Τρέχει το `func` δίνοντας το πρώτο όρισμα σαν `this`, και το επόμενο σαν τα ορίσματα. + +Για να το πούμε απλά, αυτές οι δύο κλήσεις κάνουν σχεδόν το ίδιο. -To put it simply, these two calls do almost the same: ```js func(1, 2, 3); func.call(obj, 1, 2, 3) ``` -They both call `func` with arguments `1`, `2` and `3`. The only difference is that `func.call` also sets `this` to `obj`. +Καλούν και οι δύο τη `func` με ορίσματα τα `1`, `2` and `3`. Η μόνη διαφορά είναι ότι η `func.call` επίσης θέτει το `this` στο `obj`. -As an example, in the code below we call `sayHi` in the context of different objects: `sayHi.call(user)` runs `sayHi` providing `this=user`, and the next line sets `this=admin`: +Σαν παράδειγμα, στον κώδικα από κάτω, καλούμε τη `sayHi` στο πλαίσιο διαφορετικών αντικειμένων: η `sayHi.call(user)` τρέχει τη `sayHi` δίνοντας `this=user`, και η επόμενη γραμμή θέτει `this=admin`: ```js run function sayHi() { alert(this.name); } -let user = { name: "John" }; -let admin = { name: "Admin" }; +let user = { name: "Γιάννης" }; +let admin = { name: "Διαχειριστής" }; -// use call to pass different objects as "this" -sayHi.call( user ); // John -sayHi.call( admin ); // Admin +// χρησιμοποιήσε τη κλήση για το θέσιμο διαφορετικών αντικειμένων σαν "this" +sayHi.call( user ); // this = Γιάννης +sayHi.call( admin ); // this = Διαχειριστής ``` -And here we use `call` to call `say` with the given context and phrase: - +Και εδώ χρησιμοποιούμε `call` για να καλέσουμε τη `say` με το δοσμένο context και τη φράση: ```js run function say(phrase) { alert(this.name + ': ' + phrase); } -let user = { name: "John" }; +let user = { name: "Γιάννης" }; -// user becomes this, and "Hello" becomes the first argument -say.call( user, "Hello" ); // John: Hello +// ο χρήστης γίνεται this, και το "Γεια σου" γίνεται το πρώτο όρισμα +say.call( user, "Γεια" ); // Γιάννης: Γεια ``` -In our case, we can use `call` in the wrapper to pass the context to the original function: +Στη περίπτωση μας, μπορούμε να χρησιμοποιήσουμε τη `call` στη συνάρτηση-κάλυμμα για να δώσουμε το context στην αρχική συνάρτηση: ```js run let worker = { @@ -176,7 +176,7 @@ let worker = { }, slow(x) { - alert("Called with " + x); + alert("Καλέστηκε με " + x); return x * this.someMethod(); // (*) } }; @@ -188,62 +188,65 @@ function cachingDecorator(func) { return cache.get(x); } *!* - let result = func.call(this, x); // "this" is passed correctly now + let result = func.call(this, x); // "this" δίνεται σωστά τώρα */!* cache.set(x, result); return result; }; } -worker.slow = cachingDecorator(worker.slow); // now make it caching +worker.slow = cachingDecorator(worker.slow); // τώρα να κάνει caching -alert( worker.slow(2) ); // works -alert( worker.slow(2) ); // works, doesn't call the original (cached) +alert( worker.slow(2) ); // δουλεύει +alert( worker.slow(2) ); // δουλεύει, δεν καλεί το αρχικό (cached) ``` -Now everything is fine. +Τώρα όλα λειτουργούν σωστά. + +Για να γίνουν όλα πιο ξεκάθαρα, ας δούμε πιο βαθιά πώς το `this` περνάει μέσα από τις κλήσεις: + +1. Μετά τη διακόσμηση η `worker.slow` είναι τώρα η συνάρτηση-κάλυμμα `function (x) { ... }`. +2. Οπότε όταν η `worker.slow(2)` εκτελείται, η συνάρτηση-κάλυμμα παίρνει `2` σαν ένα όρισμα και το `this=worker` (είναι το αντικέιμενο πρίν τη τελεία). +3. Μέσα στη συνάρτηση-κάλυμμα, υποθέτωντας ότι το αποτέλεσμα δεν έχει γίνει ακόμα cached, η `func.call(this, x)` δίνει το τωρινό `this` (`=worker`) και το τωρινό όρισμα (`=2`) στην αρχική μέθοδο. -To make it all clear, let's see more deeply how `this` is passed along: -1. After the decoration `worker.slow` is now the wrapper `function (x) { ... }`. -2. So when `worker.slow(2)` is executed, the wrapper gets `2` as an argument and `this=worker` (it's the object before dot). -3. Inside the wrapper, assuming the result is not yet cached, `func.call(this, x)` passes the current `this` (`=worker`) and the current argument (`=2`) to the original method. +## Πολλαπλά ορίσματα με τη "func.apply" -## Going multi-argument -Now let's make `cachingDecorator` even more universal. Till now it was working only with single-argument functions. +Τώρα ας κάνουμε τον `cachingDecorator` ακόμα πιο γενικό. Μέχρι τώρα δούλευε μόνο με συναρτήσεις με ένα όρισμα. -Now how to cache the multi-argument `worker.slow` method? +Πώς γίνεται να κάνουμε cache τη μέθοδο `worker.slow` που έχει πολλαπλά ορίσματα? ```js let worker = { slow(min, max) { - return min + max; // scary CPU-hogger is assumed + return min + max; // εργασία που απαιτεί υψηλή επεξεργαστική ισχύ εδώ } }; -// should remember same-argument calls +// πρέπει να θυμάται τις κλήσεις με τα ίδια ορίσματα worker.slow = cachingDecorator(worker.slow); ``` -Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key. +Προηγουμένως, για ένα μονό όρισμα `x` θα μπορούσαμε να χρησιμοποιήσουμε τη `cache.set(x, result)` για να σώσουμε το αποτέλεσμα και μετά με την `cache.get(x)` να το ανακτήσουμε. Τώρα όμως πρέπει να θυμόμαστε το αποτέλεσμα για ένα *συνδυασμό από ορίσματα* `(min,max)`. Το υπάρχον `Map` παίρνει μια τιμή μόνο σαν κλειδί. -There are many solutions possible: +Υπάρχουν αρκετές δυνατές λύσεις: -1. Implement a new (or use a third-party) map-like data structure that is more versatile and allows multi-keys. -2. Use nested maps: `cache.set(min)` will be a `Map` that stores the pair `(max, result)`. So we can get `result` as `cache.get(min).get(max)`. -3. Join two values into one. In our particular case we can just use a string `"min,max"` as the `Map` key. For flexibility, we can allow to provide a *hashing function* for the decorator, that knows how to make one value from many. +1. Η δημιουργία μιας νέας (η χρήση μιας τρίτης) δομής δεδομένων που να μοιάζει με map η οποία θα έιναι πιο εύελικτη και θα επιτρέπει πολλαπλά κλειδιά. +2. Η χρήση εμφωλευμένων maps: `cache.set(min)` θα είναι ένα `Map` που θα αποθηκέυει το ζευγάρι `(max, result)`. Έτσι μπορούμε να πάρουμε το `result` σαν `cache.get(min).get(max)`. +3. Η ένωση δύο τιμών σε μία. Στη συγκεκρίμενη περίπτωση μπορούμε να χρησιμοποιήσουμε ένα string `"min,max"` σαν το κλειδί για το `Map`. Για ευελιξία, μπορούμε να δώσουμε μια *συνάρτηση κατακερματισμού* για τον διακοσμητή, η οποία θα γνωρίζει πώς να δημιουργεί μια τιμή από πολλές. -For many practical applications, the 3rd variant is good enough, so we'll stick to it. +Για αρκετές πρακτικές εφαρμογές, η 3η επιλογή είναι αρκετά καλή, οπότε θα μείνουμε με αυτή. -Also we need to pass not just `x`, but all arguments in `func.call`. Let's recall that in a `function()` we can get a pseudo-array of its arguments as `arguments`, so `func.call(this, x)` should be replaced with `func.call(this, ...arguments)`. +Επίσης, χρειάζεται να αντικαταστήσουμε τη `func.call(this, x)` με τη `func.call(this, ...arguments)`, για να δώσουμε όλα τα ορίσματα στο κάλεσμα της συνάρτησης-κάλυμμα, όχι μόνο το πρώτο. -Here's a more powerful `cachingDecorator`: + +Ας δούμε ένα πιο ισχυρό `cachingDecorator`: ```js run let worker = { slow(min, max) { - alert(`Called with ${min},${max}`); + alert(`Καλέστηκε με ${min},${max}`); return min + max; } }; @@ -273,50 +276,49 @@ function hash(args) { worker.slow = cachingDecorator(worker.slow, hash); -alert( worker.slow(3, 5) ); // works -alert( "Again " + worker.slow(3, 5) ); // same (cached) +alert( worker.slow(3, 5) ); // δουλεύει +alert( "Ξανά " + worker.slow(3, 5) ); // το ίδιο (cached) ``` -Now it works with any number of arguments (though the hash function would also need to be adjusted to allow any number of arguments. An interesting way to handle this will be covered below). - -There are two changes: +Τώρα δουλεύει με οποιοδήποτε αριθμό ορισμάτων (ωστόσο η συνάρτηση κατακερματισμού θα πρέπει να προσαρμοστεί για να επιτρέπει οποιοδήποτε αριθμό ορισμάτων. Ένας ενδιαφέρον τρόπος για χειριστούμε αυτό θα καλυφθεί πιο κάτω). -- In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions. -- Then `(**)` uses `func.call(this, ...arguments)` to pass both the context and all arguments the wrapper got (not just the first one) to the original function. +Υπάρχουν δύο αλλαγές: -## func.apply +- Στη γραμμή `(*)` καλείται η `hash` για να δημιουργήσει ένα μόνο κλειδί από τα `arguments`. Εδώ χρησιμοποιούμε μια απλή συνάρτηση "ένωσης" η οποία μετατρέπει τα ορίσματα `(3, 5)` στο κλειδί `"3,5"`. Πιο πολύπλοκες περιπτώσεις μπορεί να χρειάζονται άλλες συναρτήσεις κατακερματισμού. +- Έπειτα στο `(**)` χρησιμοποιείται η `func.call(this, ...arguments)` για να δωθούν τόσο το context όσο και όλα τα ορίσματα που απέκτησε ο wrapper (όχι μόνο το πρώτο) στην αρχική συνάρτηση. -Instead of `func.call(this, ...arguments)` we could use `func.apply(this, arguments)`. +Αντί για `func.call(this, ...arguments)` υα μπορούσαμε να χρησιμοποιήσουμε τη `func.apply(this, arguments)`. -The syntax of built-in method [func.apply](mdn:js/Function/apply) is: +Η σύνταξη της ενσωματωμένης συνάρτησης [func.apply](mdn:js/Function/apply) είναι: ```js func.apply(context, args) ``` -It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments. +Τρέχει τη `func` θέτωντας `this=context` και χρησιμοποιώντας ένα αντικέιμενο που μοιάζει με πίνακα και ονομάζεται `args` σαν τη λίστα των ορισμάτων. -The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them. +Η μόνη συντακτική διαφορά ανάμεσα στο `call` και το `apply` είναι ότι το `call` περιμένει μια λίστα από ορίσματα, ενώ το `apply` δέχεται ένα αντικέιμενο που έχει τη μορφή πίνακα με αυτά. -So these two calls are almost equivalent: +Οπότε οι δύο αυτές κλήσεις είναι σχεδόν ίδιες: ```js -func.call(context, ...args); // pass an array as list with spread syntax -func.apply(context, args); // is same as using call +func.call(context, ...args); // δώσε ένα πίνακα σαν λίστα με τον τελεστή διασποράς +func.apply(context, args); // είναι το ίδιο χρησιμοποιώντας apply ``` -There's only a subtle difference: -- The spread syntax `...` allows to pass *iterable* `args` as the list to `call`. -- The `apply` accepts only *array-like* `args`. +Υπάρχει μόνο μία μικρή διαφορά: -So, where we expect an iterable, `call` works, and where we expect an array-like, `apply` works. +- Ο τελεστής διασποράς `...` επιτρέπει το δώσιμο του *iterable* `args` σαν τη λίστα στο `call`. +- Η `apply` δέχεται μόνο *σαν-πίνακα* `args`. -And for objects that are both iterable and array-like, like a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. +Άρα, αυτές οι δύο κλήσεις συμπληρώνουν η μία την άλλη. Όπου περιμένουν ένα iterable, η `call` λειτουργεί, όπου περιμένουμε κάτι σαν πίνακα, η `apply` λειτουργεί εκεί. -Passing all arguments along with the context to another function is called *call forwarding*. +Και για αντικέιμενα που είναι και iterable και σαν πίνακες, όπως ένας πραγματικός πίνακας, τεχνικά θα μπορούσαμε να χρησιμοποιήσουμε οποιαδήποτε από αυτές, όμως η `apply` θα είναι πιθανότατα πιο γρήγορη, διότι οι περισσότερες μηχανές JavaScript εσωτερικά τη βελτιστοποιούν καλύτερα. -That's the simplest form of it: +Δίνοντας όλα τα ορίσματα μαζί με το context σε μια άλλη συνάρτηση λέγεται *προώθηση κλήσης* + +Η πιο απλή του μορφή είναι αυτή: ```js let wrapper = function() { @@ -324,11 +326,11 @@ let wrapper = function() { }; ``` -When an external code calls such `wrapper`, it is indistinguishable from the call of the original function `func`. +Όταν ένας εξωτερικός κώδικας καλεί μια τέτοια `wrapper`, είναι δυσδιάκριτο από τη κλήση της αρχικής συνάρτησης `func`. -## Borrowing a method [#method-borrowing] +## Δανεισμός μιας μεθόδου [#δανεισμός-μεδόδου] -Now let's make one more minor improvement in the hashing function: +Τώρα ας κάνουμε μια μικρή βελτιστοποίηση στη συνάρτηση κατακερματισμού: ```js function hash(args) { @@ -336,9 +338,9 @@ function hash(args) { } ``` -As of now, it works only on two arguments. It would be better if it could glue any number of `args`. +Μέχρι τώρα, δουλεύει μόνο σε δύο ορίσματα. Θα ήταν καλύτερο αν μπορούσε να ενώσει οποιοδήποτε αριθμό από `args`. -The natural solution would be to use [arr.join](mdn:js/Array/join) method: +Η φυσική λύση θα ήταν η χρήση της μεθόδου [arr.join](mdn:js/Array/join): ```js function hash(args) { @@ -346,9 +348,9 @@ function hash(args) { } ``` -...Unfortunately, that won't work. Because we are calling `hash(arguments)`, and `arguments` object is both iterable and array-like, but not a real array. +...Δυστυχώς, αυτό δεν θα δουλέψει. Επειδή καλούμε τη `hash(arguments)` και το αντικέιμενο `arguments` είναι και iterable και σαν-πίνακας, αλλά όχι ένας πραγματικός πίνακας. -So calling `join` on it would fail, as we can see below: +Έτσι το να καλέσουμε τη `join` θα αποτύγχανε, όπως βλέπουμε παρακάτω: ```js run function hash() { @@ -360,7 +362,7 @@ function hash() { hash(1, 2); ``` -Still, there's an easy way to use array join: +Ωστόσο, υπάρχει ένα εύκολος τρόπος να χρησιμοποιήσουμε την ένωση πινάκων: ```js run function hash() { @@ -372,48 +374,49 @@ function hash() { hash(1, 2); ``` -The trick is called *method borrowing*. +Ο τρόπος ονομάζεται *δανεισμός μεθόδου*. + +Παίρνουμε (δανειζόμαστε) μια μέθοδο ένωσης από ένα κανονικό πίνακα (`[].join`) και χρησιμοποιούμε `[].join.call` για να το τρέξουμε στο context των `arguments`. -We take (borrow) a join method from a regular array (`[].join`) and use `[].join.call` to run it in the context of `arguments`. +Γιατί δουλεύει? -Why does it work? +Αυτό είναι γιατί ο εσωτερικός αλγόριθμος της υπάρχουσας μεθόδου `arr.join(glue)` είναι πολύ απλός. -That's because the internal algorithm of the native method `arr.join(glue)` is very simple. +Από το specification της γλώσσας ισχύει: -Taken from the specification almost "as-is": +1. Έστω ότι το `glue` είναι το πρώτο όρισμα ή, αν δεν υπάρχουν ορίσματα, τότε μια κόμμα `","`. +2. Έστω ότι το `result` είναι μια άδεια συμβολοσειρά. +3. Προσάρτησε το `this[0]` στο `result`. +4. Προσάρτησε το `glue` στο `this[1]`. +5. Προσάρτησε το `glue` στο `this[2]`. +6. ...Συνέχισε έτσι μέχρι `this.length` από πράγματα έχουν ενωθεί. +7. Επέστρεψε το `result`. -1. Let `glue` be the first argument or, if no arguments, then a comma `","`. -2. Let `result` be an empty string. -3. Append `this[0]` to `result`. -4. Append `glue` and `this[1]`. -5. Append `glue` and `this[2]`. -6. ...Do so until `this.length` items are glued. -7. Return `result`. +Οπότε, τεχνικά, παίρνει το `this` και ενώνει `this[0]`, `this[1]` ...κτλπ μαζί. Είναι επίτηδες γραμμένο με αυτό το τρόπο που να επιτρέπει οποιοδήποτε `this` με τη μορφή πίνακα (δεν είναι σύμπτωση, πολλές μέθοδοι ακολουθούν αυτή τη πρακτική). Για το λόγο αυτό δουλεύει και με το `this=arguments`. -So, technically it takes `this` and joins `this[0]`, `this[1]` ...etc together. It's intentionally written in a way that allows any array-like `this` (not a coincidence, many methods follow this practice). That's why it also works with `this=arguments`. +## Διακοσμητές και ιδιότητες συναρτήσεων -## Decorators and function properties +Είναι γενικά ασφαλής η αντικατάσταση μιας συνάρτησης η μιας μεθόδου με μια διακοσμημένη, εκτός από μια μικρή περίπτωση. Αν η αρχική συνάρτηση έχει ιδιότητες μέσα της, όπως η `func.calledCount` η οποιαδήποτε άλλη, τότε η διακοσμημένη συνάρτηση δεν θα της διαθέσει. Διότι αυτή είναι μια συνάρτηση-κάλυμμα. Οπότε θέλουν ιδιαίτερη προσόχη στη χρήση τους. -It is generally safe to replace a function or a method with a decorated one, except for one little thing. If the original function had properties on it, like `func.calledCount` or whatever, then the decorated one will not provide them. Because that is a wrapper. So one needs to be careful if one uses them. +Π.χ. στο παράδειγμα πάνω αν η συνάρτηση `slow` είχε ιδιότητες μέσα της, τότε ο `cachingDecorator(slow)` είναι μια συνάρτηση-κάλυμμα χωρίς αυτές. -E.g. in the example above if `slow` function had any properties on it, then `cachingDecorator(slow)` is a wrapper without them. +Κάποιοι διακοσμητές ενδέχεται να προσφέρουν τις δικές τους ιδιότητες. Π.χ. ένας διακοσμητής μπορεί να μετράει πόσες φορές μια συνάρτηση έτρεξε και πόσο χρόνο πήρε, και να προωθεί τη πληροφορία αυτή μέσω ιδιοτήτων της συνάρτησης-κάλυμμα. -Some decorators may provide their own properties. E.g. a decorator may count how many times a function was invoked and how much time it took, and expose this information via wrapper properties. +Υπάρχει ένας τρόπος για να δημιουργηθούν διακοσμητές που κρατάνε πρόσβαση στις ιδιότητες της συνάρτησης, αλλά αυτό απαιτεί τη χρήση ενός ειδικού `Proxy` αντικέιμενου για να περικλύσει την συνάρτηση. Θα το συζητήσουμε αυτό αργότερα στο άρθρο . -There exists a way to create decorators that keep access to function properties, but this requires using a special `Proxy` object to wrap a function. We'll discuss it later in the article . +## Σύνοψη -## Summary +*Διακοσμητής* είναι ένα κάλυμμα γύρω από μια συνάρτηση το οποία αλλάζει τη συμπεριφορά της. Η κύρια δουλειά συνεχίζει να γίνεται από την συνάρτηση. -*Decorator* is a wrapper around a function that alters its behavior. The main job is still carried out by the function. +Οι διακοσμητές μπορούν να θεωρηθούν σαν "χαρακτηριστικά" ή "γνωρίσματα" που μπορούν να προστεθούν σε μια συνάρτηση. Μπορούμε να προσθέσουμε ένα ή πολλά. Και όλα αυτά χωρίς να αλλάζουμε τον κώδικα της. -Decorators can be seen as "features" or "aspects" that can be added to a function. We can add one or add many. And all this without changing its code! +Για να υλοποιήσουμε τον `cachingDecorator`, είδαμε τις μεθόδους: -To implement `cachingDecorator`, we studied methods: +- [func.call(context, arg1, arg2...)](mdn:js/Function/call) -- καλεί τη `func` με το δοσμένο context και ορίσματα. +- [func.apply(context, args)](mdn:js/Function/apply) -- καλεί τη `func` δίνοντας το `context` σαν το `this` και στη μορφή πίνακα `args` σε μια λίστα από ορίσματα. -- [func.call(context, arg1, arg2...)](mdn:js/Function/call) -- calls `func` with given context and arguments. -- [func.apply(context, args)](mdn:js/Function/apply) -- calls `func` passing `context` as `this` and array-like `args` into a list of arguments. +Η γενικότερη μορφή *προώθησης κλήσης* συνήθως γίνεται με την `apply`: -The generic *call forwarding* is usually done with `apply`: ```js let wrapper = function() { @@ -421,6 +424,6 @@ let wrapper = function() { }; ``` -We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to `arguments`. The alternative is to use rest parameters object that is a real array. +Επίσης είδαμε ένα παράδειγμα από *δανεισμό μεθόδου* όπου παίρνουμε μια μέθοδο από ένα αντικείμενο και κάνουμε `call` σε αυτό στο πλαίσιο ενός άλλου αντικειμένου. Είναι αρκετά συνηθισμένο να παίρνουμε μεθόδους πινάκων και να τις εφαρμόζουμε στα `arguments`. Η εναλλακτική λύση είναι να χρησιμοποιήσουμε τις παραμέτρους υπολοίπου ενός αντικειμένου που είναι ένας πραγματικός πίνακας. -There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter. +Υπάρχουν αρκετοί διακοσμητές εκεί έξω. Έλεγξε πόσο καλά τους κατάλαβες λύνοντας τις ασκήσεις αυτού του κεφαλαίου. diff --git a/9-regular-expressions/03-regexp-character-classes/article.md b/9-regular-expressions/03-regexp-character-classes/article.md deleted file mode 100644 index 911622162..000000000 --- a/9-regular-expressions/03-regexp-character-classes/article.md +++ /dev/null @@ -1,269 +0,0 @@ -# Character classes - -Consider a practical task -- we have a phone number `"+7(903)-123-45-67"`, and we need to turn it into pure numbers: `79035419441`. - -To do so, we can find and remove anything that's not a number. Character classes can help with that. - -A character class is a special notation that matches any symbol from a certain set. - -For the start, let's explore a "digit" class. It's written as `\d`. We put it in the pattern, that means "any single digit". - -For instance, the let's find the first digit in the phone number: - -```js run -let str = "+7(903)-123-45-67"; - -let reg = /\d/; - -alert( str.match(reg) ); // 7 -``` - -Without the flag `g`, the regular expression only looks for the first match, that is the first digit `\d`. - -Let's add the `g` flag to find all digits: - -```js run -let str = "+7(903)-123-45-67"; - -let reg = /\d/g; - -alert( str.match(reg) ); // array of matches: 7,9,0,3,1,2,3,4,5,6,7 - -alert( str.match(reg).join('') ); // 79035419441 -``` - -That was a character class for digits. There are other character classes as well. - -Most used are: - -`\d` ("d" is from "digit") -: A digit: a character from `0` to `9`. - -`\s` ("s" is from "space") -: A space symbol: that includes spaces, tabs, newlines. - -`\w` ("w" is from "word") -: A "wordly" character: either a letter of English alphabet or a digit or an underscore. Non-Latin letters (like cyrillic or hindi) do not belong to `\w`. - -For instance, `pattern:\d\s\w` means a "digit" followed by a "space character" followed by a "wordly character", like `"1 a"`. - -**A regexp may contain both regular symbols and character classes.** - -For instance, `pattern:CSS\d` matches a string `match:CSS` with a digit after it: - -```js run -let str = "CSS4 is cool"; -let reg = /CSS\d/ - -alert( str.match(reg) ); // CSS4 -``` - -Also we can use many character classes: - -```js run -alert( "I love HTML5!".match(/\s\w\w\w\w\d/) ); // ' HTML5' -``` - -The match (each character class corresponds to one result character): - -![](love-html5-classes.svg) - -## Word boundary: \b - -A word boundary `pattern:\b` -- is a special character class. - -It does not denote a character, but rather a boundary between characters. - -For instance, `pattern:\bJava\b` matches `match:Java` in the string `subject:Hello, Java!`, but not in the script `subject:Hello, JavaScript!`. - -```js run -alert( "Hello, Java!".match(/\bJava\b/) ); // Java -alert( "Hello, JavaScript!".match(/\bJava\b/) ); // null -``` - -The boundary has "zero width" in a sense that usually a character class means a character in the result (like a wordly character or a digit), but not in this case. - -The boundary is a test. - -When regular expression engine is doing the search, it's moving along the string in an attempt to find the match. At each string position it tries to find the pattern. - -When the pattern contains `pattern:\b`, it tests that the position in string is a word boundary, that is one of three variants: - -- Immediately before is `\w`, and immediately after -- not `\w`, or vise versa. -- At string start, and the first string character is `\w`. -- At string end, and the last string character is `\w`. - -For instance, in the string `subject:Hello, Java!` the following positions match `\b`: - -![](hello-java-boundaries.svg) - -So it matches `pattern:\bHello\b`, because: - -1. At the beginning of the string the first `\b` test matches. -2. Then the word `Hello` matches. -3. Then `\b` matches, as we're between `o` and a space. - -Pattern `pattern:\bJava\b` also matches. But not `pattern:\bHell\b` (because there's no word boundary after `l`) and not `Java!\b` (because the exclamation sign is not a wordly character, so there's no word boundary after it). - - -```js run -alert( "Hello, Java!".match(/\bHello\b/) ); // Hello -alert( "Hello, Java!".match(/\bJava\b/) ); // Java -alert( "Hello, Java!".match(/\bHell\b/) ); // null (no match) -alert( "Hello, Java!".match(/\bJava!\b/) ); // null (no match) -``` - -Once again let's note that `pattern:\b` makes the searching engine to test for the boundary, so that `pattern:Java\b` finds `match:Java` only when followed by a word boundary, but it does not add a letter to the result. - -Usually we use `\b` to find standalone English words. So that if we want `"Java"` language then `pattern:\bJava\b` finds exactly a standalone word and ignores it when it's a part of another word, e.g. it won't match `match:Java` in `subject:JavaScript`. - -Another example: a regexp `pattern:\b\d\d\b` looks for standalone two-digit numbers. In other words, it requires that before and after `pattern:\d\d` must be a symbol different from `\w` (or beginning/end of the string). - -```js run -alert( "1 23 456 78".match(/\b\d\d\b/g) ); // 23,78 -``` - -```warn header="Word boundary doesn't work for non-Latin alphabets" -The word boundary check `\b` tests for a boundary between `\w` and something else. But `\w` means an English letter (or a digit or an underscore), so the test won't work for other characters (like cyrillic or hieroglyphs). - -Later we'll come by Unicode character classes that allow to solve the similar task for different languages. -``` - - -## Inverse classes - -For every character class there exists an "inverse class", denoted with the same letter, but uppercased. - -The "reverse" means that it matches all other characters, for instance: - -`\D` -: Non-digit: any character except `\d`, for instance a letter. - -`\S` -: Non-space: any character except `\s`, for instance a letter. - -`\W` -: Non-wordly character: anything but `\w`. - -`\B` -: Non-boundary: a test reverse to `\b`. - -In the beginning of the chapter we saw how to get all digits from the phone `subject:+7(903)-123-45-67`. - -One way was to match all digits and join them: - -```js run -let str = "+7(903)-123-45-67"; - -alert( str.match(/\d/g).join('') ); // 79031234567 -``` - -An alternative, shorter way is to find non-digits `\D` and remove them from the string: - - -```js run -let str = "+7(903)-123-45-67"; - -alert( str.replace(/\D/g, "") ); // 79031234567 -``` - -## Spaces are regular characters - -Usually we pay little attention to spaces. For us strings `subject:1-5` and `subject:1 - 5` are nearly identical. - -But if a regexp doesn't take spaces into account, it may fail to work. - -Let's try to find digits separated by a dash: - -```js run -alert( "1 - 5".match(/\d-\d/) ); // null, no match! -``` - -Here we fix it by adding spaces into the regexp `pattern:\d - \d`: - -```js run -alert( "1 - 5".match(/\d - \d/) ); // 1 - 5, now it works -``` - -**A space is a character. Equal in importance with any other character.** - -Of course, spaces in a regexp are needed only if we look for them. Extra spaces (just like any other extra characters) may prevent a match: - -```js run -alert( "1-5".match(/\d - \d/) ); // null, because the string 1-5 has no spaces -``` - -In other words, in a regular expression all characters matter, spaces too. - -## A dot is any character - -The dot `"."` is a special character class that matches "any character except a newline". - -For instance: - -```js run -alert( "Z".match(/./) ); // Z -``` - -Or in the middle of a regexp: - -```js run -let reg = /CS.4/; - -alert( "CSS4".match(reg) ); // CSS4 -alert( "CS-4".match(reg) ); // CS-4 -alert( "CS 4".match(reg) ); // CS 4 (space is also a character) -``` - -Please note that the dot means "any character", but not the "absense of a character". There must be a character to match it: - -```js run -alert( "CS4".match(/CS.4/) ); // null, no match because there's no character for the dot -``` - -### The dotall "s" flag - -Usually a dot doesn't match a newline character. - -For instance, `pattern:A.B` matches `match:A`, and then `match:B` with any character between them, except a newline. - -This doesn't match: - -```js run -alert( "A\nB".match(/A.B/) ); // null (no match) - -// a space character would match, or a letter, but not \n -``` - -Sometimes it's inconvenient, we really want "any character", newline included. - -That's what `s` flag does. If a regexp has it, then the dot `"."` match literally any character: - -```js run -alert( "A\nB".match(/A.B/s) ); // A\nB (match!) -``` - -## Summary - -There exist following character classes: - -- `pattern:\d` -- digits. -- `pattern:\D` -- non-digits. -- `pattern:\s` -- space symbols, tabs, newlines. -- `pattern:\S` -- all but `pattern:\s`. -- `pattern:\w` -- English letters, digits, underscore `'_'`. -- `pattern:\W` -- all but `pattern:\w`. -- `pattern:.` -- any character if with the regexp `'s'` flag, otherwise any except a newline. - -...But that's not all! - -The Unicode encoding, used by JavaScript for strings, provides many properties for characters, like: which language the letter belongs to (if a letter) it is it a punctuation sign, etc. - -Modern JavaScript allows to use these properties in regexps to look for characters, for instance: - -- A cyrillic letter is: `pattern:\p{Script=Cyrillic}` or `pattern:\p{sc=Cyrillic}`. -- A dash (be it a small hyphen `-` or a long dash `—`): `pattern:\p{Dash_Punctuation}` or `pattern:\p{pd}`. -- A currency symbol, such as `$`, `€` or another: `pattern:\p{Currency_Symbol}` or `pattern:\p{sc}`. -- ...And much more. Unicode has a lot of character categories that we can select from. - -These patterns require `'u'` regexp flag to work. More about that in the chapter [](info:regexp-unicode). diff --git a/9-regular-expressions/03-regexp-character-classes/hello-java-boundaries.svg b/9-regular-expressions/03-regexp-character-classes/hello-java-boundaries.svg deleted file mode 100644 index 65714ef75..000000000 --- a/9-regular-expressions/03-regexp-character-classes/hello-java-boundaries.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - hello-java-boundaries.svg - Created with sketchtool. - - - - Hello, Java - ! - - - - - - - - \ No newline at end of file diff --git a/9-regular-expressions/03-regexp-character-classes/love-html5-classes.svg b/9-regular-expressions/03-regexp-character-classes/love-html5-classes.svg deleted file mode 100644 index 4b9f4d295..000000000 --- a/9-regular-expressions/03-regexp-character-classes/love-html5-classes.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - love-html5-classes.svg - Created with sketchtool. - - - - I love HTML - 5 - - - \s \w \w \w \w \ - d - - - - \ No newline at end of file diff --git a/9-regular-expressions/09-regexp-groups/article.md b/9-regular-expressions/09-regexp-groups/article.md deleted file mode 100644 index 141d03005..000000000 --- a/9-regular-expressions/09-regexp-groups/article.md +++ /dev/null @@ -1,242 +0,0 @@ -# Capturing groups - -A part of a pattern can be enclosed in parentheses `pattern:(...)`. This is called a "capturing group". - -That has two effects: - -1. It allows to place a part of the match into a separate array. -2. If we put a quantifier after the parentheses, it applies to the parentheses as a whole, not the last character. - -## Example - -In the example below the pattern `pattern:(go)+` finds one or more `match:'go'`: - -```js run -alert( 'Gogogo now!'.match(/(go)+/i) ); // "Gogogo" -``` - -Without parentheses, the pattern `pattern:/go+/` means `subject:g`, followed by `subject:o` repeated one or more times. For instance, `match:goooo` or `match:gooooooooo`. - -Parentheses group the word `pattern:(go)` together. - -Let's make something more complex -- a regexp to match an email. - -Examples of emails: - -``` -my@mail.com -john.smith@site.com.uk -``` - -The pattern: `pattern:[-.\w]+@([\w-]+\.)+[\w-]{2,20}`. - -1. The first part `pattern:[-.\w]+` (before `@`) may include any alphanumeric word characters, a dot and a dash, to match `match:john.smith`. -2. Then `pattern:@`, and the domain. It may be a subdomain like `host.site.com.uk`, so we match it as "a word followed by a dot `pattern:([\w-]+\.)` (repeated), and then the last part must be a word: `match:com` or `match:uk` (but not very long: 2-20 characters). - -That regexp is not perfect, but good enough to fix errors or occasional mistypes. - -For instance, we can find all emails in the string: - -```js run -let reg = /[-.\w]+@([\w-]+\.)+[\w-]{2,20}/g; - -alert("my@mail.com @ his@site.com.uk".match(reg)); // my@mail.com, his@site.com.uk -``` - -In this example parentheses were used to make a group for repeating `pattern:(...)+`. But there are other uses too, let's see them. - -## Contents of parentheses - -Parentheses are numbered from left to right. The search engine remembers the content matched by each of them and allows to reference it in the pattern or in the replacement string. - -For instance, we'd like to find HTML tags `pattern:<.*?>`, and process them. - -Let's wrap the inner content into parentheses, like this: `pattern:<(.*?)>`. - -We'll get both the tag as a whole and its content as an array: - -```js run -let str = '

Hello, world!

'; -let reg = /<(.*?)>/; - -alert( str.match(reg) ); // Array: ["

", "h1"] -``` - -The call to [String#match](mdn:js/String/match) returns groups only if the regexp only looks for the first match, that is: has no `pattern:/.../g` flag. - -If we need all matches with their groups then we can use `.matchAll` or `regexp.exec` as described in : - -```js run -let str = '

Hello, world!

'; - -// two matches: opening

and closing

tags -let reg = /<(.*?)>/g; - -let matches = Array.from( str.matchAll(reg) ); - -alert(matches[0]); // Array: ["

", "h1"] -alert(matches[1]); // Array: ["

", "/h1"] -``` - -Here we have two matches for `pattern:<(.*?)>`, each of them is an array with the full match and groups. - -## Nested groups - -Parentheses can be nested. In this case the numbering also goes from left to right. - -For instance, when searching a tag in `subject:` we may be interested in: - -1. The tag content as a whole: `match:span class="my"`. -2. The tag name: `match:span`. -3. The tag attributes: `match:class="my"`. - -Let's add parentheses for them: - -```js run -let str = ''; - -let reg = /<(([a-z]+)\s*([^>]*))>/; - -let result = str.match(reg); -alert(result); // , span class="my", span, class="my" -``` - -Here's how groups look: - -![](regexp-nested-groups.svg) - -At the zero index of the `result` is always the full match. - -Then groups, numbered from left to right. Whichever opens first gives the first group `result[1]`. Here it encloses the whole tag content. - -Then in `result[2]` goes the group from the second opening `pattern:(` till the corresponding `pattern:)` -- tag name, then we don't group spaces, but group attributes for `result[3]`. - -**If a group is optional and doesn't exist in the match, the corresponding `result` index is present (and equals `undefined`).** - -For instance, let's consider the regexp `pattern:a(z)?(c)?`. It looks for `"a"` optionally followed by `"z"` optionally followed by `"c"`. - -If we run it on the string with a single letter `subject:a`, then the result is: - -```js run -let match = 'a'.match(/a(z)?(c)?/); - -alert( match.length ); // 3 -alert( match[0] ); // a (whole match) -alert( match[1] ); // undefined -alert( match[2] ); // undefined -``` - -The array has the length of `3`, but all groups are empty. - -And here's a more complex match for the string `subject:ack`: - -```js run -let match = 'ack'.match(/a(z)?(c)?/) - -alert( match.length ); // 3 -alert( match[0] ); // ac (whole match) -alert( match[1] ); // undefined, because there's nothing for (z)? -alert( match[2] ); // c -``` - -The array length is permanent: `3`. But there's nothing for the group `pattern:(z)?`, so the result is `["ac", undefined, "c"]`. - -## Named groups - -Remembering groups by their numbers is hard. For simple patterns it's doable, but for more complex ones we can give names to parentheses. - -That's done by putting `pattern:?` immediately after the opening paren, like this: - -```js run -*!* -let dateRegexp = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/; -*/!* -let str = "2019-04-30"; - -let groups = str.match(dateRegexp).groups; - -alert(groups.year); // 2019 -alert(groups.month); // 04 -alert(groups.day); // 30 -``` - -As you can see, the groups reside in the `.groups` property of the match. - -We can also use them in the replacement string, as `pattern:$` (like `$1..9`, but a name instead of a digit). - -For instance, let's reformat the date into `day.month.year`: - -```js run -let dateRegexp = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/; - -let str = "2019-04-30"; - -let rearranged = str.replace(dateRegexp, '$.$.$'); - -alert(rearranged); // 30.04.2019 -``` - -If we use a function for the replacement, then named `groups` object is always the last argument: - -```js run -let dateRegexp = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/; - -let str = "2019-04-30"; - -let rearranged = str.replace(dateRegexp, - (str, year, month, day, offset, input, groups) => - `${groups.day}.${groups.month}.${groups.year}` -); - -alert(rearranged); // 30.04.2019 -``` - -Usually, when we intend to use named groups, we don't need positional arguments of the function. For the majority of real-life cases we only need `str` and `groups`. - -So we can write it a little bit shorter: - -```js -let rearranged = str.replace(dateRegexp, (str, ...args) => { - let {year, month, day} = args.pop(); - alert(str); // 2019-04-30 - alert(year); // 2019 - alert(month); // 04 - alert(day); // 30 -}); -``` - - -## Non-capturing groups with ?: - -Sometimes we need parentheses to correctly apply a quantifier, but we don't want the contents in results. - -A group may be excluded by adding `pattern:?:` in the beginning. - -For instance, if we want to find `pattern:(go)+`, but don't want to remember the contents (`go`) in a separate array item, we can write: `pattern:(?:go)+`. - -In the example below we only get the name "John" as a separate member of the `results` array: - -```js run -let str = "Gogo John!"; -*!* -// exclude Gogo from capturing -let reg = /(?:go)+ (\w+)/i; -*/!* - -let result = str.match(reg); - -alert( result.length ); // 2 -alert( result[1] ); // John -``` - -## Summary - -Parentheses group together a part of the regular expression, so that the quantifier applies to it as a whole. - -Parentheses groups are numbered left-to-right, and can optionally be named with `(?...)`. - -The content, matched by a group, can be referenced both in the replacement string as `$1`, `$2` etc, or by the name `$name` if named. - -So, parentheses groups are called "capturing groups", as they "capture" a part of the match. We get that part separately from the result as a member of the array or in `.groups` if it's named. - -We can exclude the group from remembering (make in "non-capturing") by putting `?:` at the start: `(?:...)`, that's used if we'd like to apply a quantifier to the whole group, but don't need it in the result. diff --git a/9-regular-expressions/09-regexp-groups/regexp-nested-groups.svg b/9-regular-expressions/09-regexp-groups/regexp-nested-groups.svg deleted file mode 100644 index 75ced6ff6..000000000 --- a/9-regular-expressions/09-regexp-groups/regexp-nested-groups.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - regexp-nested-groups.svg - Created with sketchtool. - - - - < - (( - [a-z]+ - ) - \s* - ( - [^>]* - )) - > - - - - - - - - - 1 - - - span class="my" - - - 2 - - - span - - - - - - 3 - - - class="my" - - - - \ No newline at end of file diff --git a/images.yaml b/images.yaml new file mode 100644 index 000000000..134c3c90d --- /dev/null +++ b/images.yaml @@ -0,0 +1,3 @@ +decorator-makecaching-wrapper.svg: + "wrapper": + text: "ραππερ"