Προς το περιεχόμενο

Προτεινόμενες αναρτήσεις

Δημοσ.

Καλησπέρα παίδες

Έχω τον παρακάτω κώδικα

 

function apply_events(cmp, events) {
	if (!(cmp && events)) {
		return;
	}
	for (var event in events) {
		if (events.hasOwnProperty(event)) {
			cmp.bind(event, function (e) {
				events[event](cmp, e);
			});
		}
	}
};

var button = $('<button />');
var events = {
  click :function(cmp, e){
     alert('click');
  },
  keyup : function(cmp, e){
    alert('keyup');
  }
};

apply_events(button, events);




 

Το πρόβλημα μου είναι ότι γίνεται apply μόνο το τελευταίο event του object events. Πώς θα μπορούσα να κάνω apply πολλά events δυναμικά αλλά το καθένα να έχει την δική του function?Σημειωτέον ότι έχω δοκιμάσει και την  'on' εκτός από την 'bind'

Ευχαριστώ 

Δημοσ.

Δε φαίνεται κάποιος λόγος να συμβαίνει αυτό, ένα απλό for έχεις χωρίς τίποτα το περίεργο. Επομένως θα κοίταζα στη θέση σου μήπως κάτι δεν πάει καλά και πέφτει κανένα error, ή μήπως αφότου καλείς την apply_events η μεταβλητή events αλλάζει τιμή κι έτσι όταν έρθει η ώρα δε βρίσκει τους handlers σου.

Δημοσ. (επεξεργασμένο)

Αααα ναι. Τώρα που το λές obvious εκ των υστέρων. Στραβούλιακας που δεν το πήρα πρέφα νωρίτερα.  :)

 

Λοιπόν το πρόβλημα είναι το κλασικό με τις closures στη JS. Πρόσεξε τι γίνεται εδώ:

 

    cmp.bind(event, function (e) {
        events[event](cmp, e);
    }); 

 

Βάζεις σαν event handler μια function η οποία όταν τρέξει διαβάζει την τιμή 3 μεταβλητών που δεν είναι στο local scope της: events, event, cmp. Αυτό που πρέπει να έχεις υπόψη είναι πως εσύ "πακετάρεις" τη function τώρα, αλλά αυτή θα εκτελεστεί αργότερα. Οι μεταβλητές που κάνεις close over θα αποτιμηθούν αργότερα. Αυτό σημαίνει πως αν ανάμεσα στο τώρα και στο αργότερα αλλάξουν οι τιμές τους, θα γίνει κάτι που δεν περίμενες (αυτό βασικά είχα στο μυαλό μου όταν είπα "αν η events αλλάξει τιμή", αλλά δεν το σκέφτηκα αρκετά).

 

Το events λοιπόν θεωρούμε πως δεν πρόκειται να το πειράξεις. Το cmp είμαστε σίγουροι ότι δε μπορεί να πειραχτεί, μιας και είναι περιορισμένο στο local scope της apply_events η οποία βλέπουμε ότι δεν το πειράζει καθόλου αφότου βάλεις τους event handlers. Δεν έχει καμία σημασία αν αλλάξει η τιμή της button απέξω από την apply_events -- τη στιγμή που την καλείς δημιουργείται ένα αντίγραφο της αναφοράς στο button, οπότε παρόλο που μπορείς να αλλάξεις properties του button μετά την κλήση της apply_events και οι event handler σου θα δούν τις αλλαγές αυτές στα properties του cmp, δεν μπορείς να αλλάξεις "εκ των υστέρων" την ίδια την τιμή του cmp. Ψιλοπροφανές αλλά το λέω για πληρότητα.

 

Αυτό που μπορεί να πειραχτεί (και πειράζεται) όμως, είναι το event. Συγκεκριμένα, όταν το for τελειώσει η μεταβλητή event θα έχει σαν τιμή το τελευαίο key στο οποίο έκανε iterate, δηλαδή στο παράδειγμά σου "keyup". Αυτό σημαίνει πως όταν με το καλό εκτελεστεί ο οποιοσδήποτε handler από τους 2 που βάζεις, όταν μέσα σ' αυτόν αποτιμηθεί το events[event]... η τιμή του θα είναι και στις 2 περιπτώσεις ο handler για το keyup event. Οπότε καταλήγεις να κάνεις bind τον handler του keyup και στα 2 events, όπως είδες και μόνος σου.

 

Άρα.

 

Στην ουσία την έπαθες από μια πιο πολύπλοκη περίπτωση του παρακάτω, που είμαι σίγουρος ότι δε σου προκαλεί καμία εντύπωση:

 

 

var i = 10;
var f = function() { alert(i); };
i = 20;
f(); // "20"

 

Edit: Ακολουθεί παράγραφος στιγμής χαζομάρας στο αρχικό post, please skip.

 

 

 

 

Πρώτος λογικός τρόπος επίλυσης: κάνεις fix το σε ποιά handler function αναφέρεσαι τη στιγμή που κάνεις το bind και όχι τη στιγμή που τρέχει ο handler σου:

 

    if (events.hasOwnProperty(event)) {
        var handler = events[event];
        cmp.bind(event, function (e) {
            handler(cmp, e);
        });
    }

 

 

 

 

Οπότε τι κάνεις; Αναγκάζεις τη δημιουργία ενός νέου αντίγραφου της τιμής του event σε κάθε iteration, ενός αντιγράφου του οποίου το scope είναι μικρότερο από το body του for loop για να μη μπορεί να πειραχτεί στο επόμενο iteration. Μιας και ο μόνος τρόπος να ανοίξεις καινούριο scope στη JS είναι η κλήση μιας function, αυτό θα φανεί λίγο εξεζητημένο...

 

 

    if (events.hasOwnProperty(event)) {
        (function(event2) {
            cmp.bind(event2, function (e) {
                events[event2](cmp, e);
            });
        })(event);
    }

 

Ονόμασα τη formal παράμετρο της συνάρτησης event2 για να το κάνω σαφές ότι πρόκειται για το νέο αντίγραφο και να μη σε μπερδέψω περισσότερο, αλλά θα δούλευε το ίδιο ακόμα κι αν την είχαμε ονομάσει και αυτήν "event" -- η ύπαρξη μιας παραμέτρου μ' αυτό το όνομα θα "έκρυβε" την άλλη event από το εξωτερικό scope.

 

Ελπίζω να μη σε μπέρδεψα χειρότερα, αν έχεις ακόμα απορίες ρώτα ελεύθερα.

 

PS: Και μη χρησιμοποιείς τη .bind(), πλέον το σωστό είναι .on().

PPS: Η περιγραφή σου έφτασε οπότε δεν πάτησα να δω το fiddle, αλλά μπράβο που το έβαλες.

PPPS: Η δεύτερη μέθοδος είναι πολύ χρήσιμη να την ξέρεις, γιατί δεν έχεις πάντα την πολυτέλεια της πρώτης. Για παράδειγμα, αν θέλεις να γεμίσεις ένα πίνακα 10 θέσεων με 10 συναρτήσεις που όταν κληθούν θα τυπώσουν με τη σειρά 1, 2, 3, 4..., αυτό γίνεται μόνο με τη δεύτερη μέθοδο.

Επεξ/σία από defacer
  • Like 1
Δημοσ.

Κατ'αρχάς σε ευχαριστώ για την πλήρη ανάλυση. Respect, πάντα κατατοπιστικότατος και περιγραφικός όσο δεν πάει. Την πρώτη σου μέθοδο την είχα δοκιμάσει κι εγώ αλλά δεν δούλεψε. Νομίζω δεν δουλεύει ακόμη. Μπορείς να δεις το fiddle εάν θέλεις και να το δοκιμάσεις εκεί. Ο δεύτερος τρόπος όμως δούλεψε. Και νομίζω ότι θα το κρατήσω να το έχω υπ' όψιν μου.  Γενικά μετά την 1.8 της jquery χρησιμοποιώ μόνο την .on. Η .bind  μπήκε εκεί μόνο για δοκιμαστικούς λόγους. 

Το συγκεκριμένο παράδειγμα το πλησίασα για να καταλάβω λίγο πως οι τύποι που έφτιαξαν την Extjs, κάνουν apply τα events σε αντικείμενα. Ήθελα να δω εάν μπορώ να το κάνω κι εγώ σε jquery.

Ευχαριστώ και πάλι

Δημοσ. (επεξεργασμένο)

Ευχαριστώ για τα καλά λόγια.

 

Παρόλα αυτά όπως βλέπεις ακόμα κι όταν υποτίθεται πως τα ξέρεις, σε μια στιγμή αδυναμίας μπορεί πάλι να κάνεις χαζά λάθη όπως έκανα παραπάνω στην πρώτη υποτιθέμενη λύση -- η οποία με το φως της ημέρας φανερά δε δουλεύει γιατί η μεταβλητή handler πάλι έχει το ίδιο scope με την event οπότε μια τρύπα στο νερό.

 

Πάμε όλοι μαζί: η JavaScript έχει function-level scope. Αν θέλεις καινούριο scope πρέπει να καλέσεις function. Μόνο αν καλέσεις function δημιουργείται καινούριο scope, και αν δεν καλέσεις function δε θα καταφέρεις να δημιουργήσεις καινούριο scope. Ελπίζω την επόμενη φορά να το θυμηθώ απ' την αρχή. :P

 

Update: Ανάλυση για το πώς λειτουργεί το scope στη JS με αναφορά σε closures. Λίγο βαρύ (δεν είναι για χομπίστες) αλλά καλό.

Επεξ/σία από defacer
Δημοσ.

Με την ευκαιρία να με διορθώσω ακόμα μια φορά λέγοντας πως technically υπάρχει και ένας ακόμα τρόπος να δημιουργήσεις νέο scope στη JS: το with statement:

if (events.hasOwnProperty(event)) {
    with({event2: event}) {
        cmp.bind(event2, function (e) {
            events[event2](cmp, e);
         });
    }
} 

 

Βεβαίως αυτό καλύτερα να κάνουμε ότι δεν το ξέρουμε γιατί long story short θεωρείται κακή πρακτική (και κατά το Crockford ευαγγέλιο) και δεν λειτουργεί σε strict mode (όπως δηλαδή θα έπρεπε να γράφει κανείς κώδικα). Σχετική πολύ καλή question στο StackOverflow.

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα
  • Δημιουργία νέου...