Πώς λειτουργεί η δέσμευση δεδομένων στο AngularJS;

Πώς λειτουργεί η δέσμευση δεδομένων στο πλαίσιο AngularJS;

Δεν έχω βρει τεχνικές λεπτομέρειες στον ιστότοπό τους. Είναι περισσότερο ή λιγότερο σαφές πώς λειτουργεί όταν τα δεδομένα διαδίδονται από την προβολή στο μοντέλο. Αλλά πώς παρακολουθεί το AngularJS τις αλλαγές των ιδιοτήτων του μοντέλου χωρίς setters και getters;

Βρήκα ότι υπάρχουν JavaScript watchers που μπορούν να κάνουν αυτή τη δουλειά. Αλλά δεν υποστηρίζονται από τον Internet Explorer 6 και τον Internet Explorer 7. Πώς λοιπόν το AngularJS γνωρίζει ότι άλλαξα για παράδειγμα το παρακάτω και αντανακλά αυτή την αλλαγή σε μια προβολή;

myobject.myproperty="new value";
Λύση

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

Η μέθοδος $apply(), την οποία καλείτε όταν μεταβαίνετε από έναν κόσμο που δεν είναι AngularJS σε έναν κόσμο AngularJS, καλεί την $digest(). Ένα digest είναι ένας απλός παλιός βρώμικος έλεγχος. Λειτουργεί σε όλα τα προγράμματα περιήγησης και είναι απολύτως προβλέψιμη.

Για να αντιπαραβάλουμε το dirty-checking (AngularJS) έναντι των change listeners (KnockoutJS και Backbone.js): Ενώ το dirty-checking μπορεί να φαίνεται απλό, ακόμα και αναποτελεσματικό (θα αναφερθώ σε αυτό αργότερα), αποδεικνύεται ότι είναι σημασιολογικά σωστό όλη την ώρα, ενώ οι change listeners έχουν πολλές περίεργες γωνιακές περιπτώσεις και χρειάζονται πράγματα όπως η παρακολούθηση εξαρτήσεων για να είναι πιο σημασιολογικά σωστό. Η παρακολούθηση εξαρτήσεων του KnockoutJS είναι ένα έξυπνο χαρακτηριστικό για ένα πρόβλημα που δεν έχει το AngularJS.

Προβλήματα με τους ακροατές αλλαγών:

  • Η σύνταξη είναι φρικτή, αφού οι φυλλομετρητές δεν την υποστηρίζουν εγγενώς. Ναι, υπάρχουν πληρεξούσια, αλλά δεν είναι σημασιολογικά σωστά σε όλες τις περιπτώσεις, και φυσικά δεν υπάρχουν πληρεξούσια σε παλιούς browsers. Η ουσία είναι ότι το dirty-checking σας επιτρέπει να κάνετε POJO, ενώ το KnockoutJS και το Backbone.js σας αναγκάζουν να κληρονομείτε από τις κλάσεις τους και να έχετε πρόσβαση στα δεδομένα σας μέσω accessors.
  • Αλλαγή συνένωσης. Ας υποθέσουμε ότι έχετε έναν πίνακα στοιχείων. Ας υποθέσουμε ότι θέλετε να προσθέσετε στοιχεία σε έναν πίνακα, καθώς κάνετε βρόχο για να προσθέσετε, κάθε φορά που προσθέτετε πυροδοτείτε συμβάντα αλλαγής, τα οποία αποδίδουν το UI. Αυτό είναι πολύ κακό για την απόδοση. Αυτό που θέλετε είναι να ενημερώνετε το UI μόνο μία φορά, στο τέλος. Τα συμβάντα αλλαγής είναι πολύ λεπτομερή.
  • Οι ακροατές αλλαγής πυροδοτούν αμέσως σε ένα setter, το οποίο είναι πρόβλημα, καθώς ο ακροατής αλλαγής μπορεί να αλλάξει περαιτέρω δεδομένα, τα οποία πυροδοτούν περισσότερα γεγονότα αλλαγής. Αυτό είναι κακό, αφού στη στοίβα σας μπορεί να συμβαίνουν πολλά γεγονότα αλλαγής ταυτόχρονα. Ας υποθέσουμε ότι έχετε δύο πίνακες που πρέπει να διατηρούνται συγχρονισμένοι για οποιονδήποτε λόγο. Μπορείτε να προσθέσετε μόνο στη μία ή στην άλλη, αλλά κάθε φορά που προσθέτετε πυροδοτείτε ένα συμβάν αλλαγής, το οποίο τώρα έχει μια ασυνεπή εικόνα του κόσμου. Αυτό είναι ένα πολύ παρόμοιο πρόβλημα με το κλείδωμα νημάτων, το οποίο η JavaScript αποφεύγει αφού κάθε επανάκληση εκτελείται αποκλειστικά και μέχρι τέλους. Τα γεγονότα αλλαγής το σπάνε αυτό, αφού οι ρυθμιστές μπορούν να έχουν εκτεταμένες συνέπειες που δεν είναι προβλεπόμενες και μη προφανείς, γεγονός που δημιουργεί ξανά το πρόβλημα του νήματος. Αποδεικνύεται ότι αυτό που θέλετε να κάνετε είναι να καθυστερήσετε την εκτέλεση του ακροατή, και να εγγυηθείτε, ότι μόνο ένας ακροατής εκτελείται κάθε φορά, επομένως οποιοσδήποτε κώδικας είναι ελεύθερος να αλλάξει δεδομένα, και γνωρίζει ότι κανένας άλλος κώδικας δεν εκτελείται ενώ αυτός το κάνει.

Τι γίνεται με την απόδοση;

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

Οι άνθρωποι είναι:

  • Αργά - Οτιδήποτε είναι ταχύτερο από 50 ms είναι ανεπαίσθητο για τους ανθρώπους και συνεπώς μπορεί να θεωρηθεί "στιγμιαίο".

  • Περιορισμένα - Δεν μπορείτε πραγματικά να δείξετε περισσότερες από περίπου 2000 πληροφορίες σε έναν άνθρωπο σε μία μόνο σελίδα. Οτιδήποτε περισσότερο από αυτό είναι πραγματικά κακό UI, και οι άνθρωποι δεν μπορούν να το επεξεργαστούν αυτό ούτως ή άλλως.

Έτσι, το πραγματικό ερώτημα είναι το εξής: ms; πόσες συγκρίσεις μπορείτε να κάνετε σε ένα πρόγραμμα περιήγησης σε 50 ms; Αυτή είναι μια δύσκολη ερώτηση για να απαντηθεί, καθώς πολλοί παράγοντες παίζουν ρόλο, αλλά εδώ είναι μια περίπτωση δοκιμής: http://jsperf.com/angularjs-digest/6 που δημιουργεί 10.000 παρατηρητές. Σε ένα σύγχρονο πρόγραμμα περιήγησης αυτό διαρκεί λίγο λιγότερο από 6 ms. Στον Internet Explorer 8 χρειάζεται περίπου 40 ms. Όπως μπορείτε να δείτε, αυτό δεν αποτελεί πρόβλημα ακόμη και σε αργούς φυλλομετρητές στις μέρες μας. Υπάρχει μια προειδοποίηση: Οι συγκρίσεις πρέπει να είναι απλές για να χωρέσουν στο χρονικό όριο... Δυστυχώς είναι πάρα πολύ εύκολο να προσθέσετε μια αργή σύγκριση στο AngularJS, οπότε είναι εύκολο να δημιουργήσετε αργές εφαρμογές όταν δεν ξέρετε τι κάνετε. Ελπίζουμε όμως να έχουμε μια απάντηση παρέχοντας ένα instrumentation module, το οποίο θα σας δείχνει ποιες είναι οι αργές συγκρίσεις.

Αποδεικνύεται ότι τα βιντεοπαιχνίδια και οι GPUs χρησιμοποιούν την προσέγγιση dirty-checking, ειδικά επειδή είναι συνεπής. Εφόσον ξεπερνούν το ρυθμό ανανέωσης της οθόνης (συνήθως 50-60 Hz, ή κάθε 16,6-20 ms), οποιαδήποτε απόδοση πάνω από αυτό είναι σπατάλη, οπότε καλύτερα να σχεδιάζετε περισσότερα πράγματα, παρά να ανεβάζετε τα FPS υψηλότερα.

Σχόλια (38)

Αυτή είναι η βασική μου αντίληψη. Μπορεί κάλλιστα να είναι λάθος!

  1. Τα αντικείμενα παρακολουθούνται περνώντας μια συνάρτηση (επιστρέφοντας το πράγμα που πρέπει να που παρακολουθείται) στη μέθοδο $watch.
  2. Οι αλλαγές στα παρακολουθούμενα αντικείμενα πρέπει να γίνονται μέσα σε ένα μπλοκ κώδικα που τυλίγεται από τη μέθοδο $apply.
  3. Στο τέλος της $apply καλείται η μέθοδος $digest η οποία πηγαίνει μέσα από κάθε ένα από τα watches και ελέγχει αν έχουν αλλάξει από τότε που την τελευταία φορά που εκτελέστηκε η $digest.
  4. Εάν βρεθούν αλλαγές τότε η digest καλείται ξανά μέχρι να σταθεροποιηθούν όλες οι αλλαγές.

Στην κανονική ανάπτυξη, η σύνταξη δέσμευσης δεδομένων στην HTML λέει στον μεταγλωττιστή του AngularJS να δημιουργήσει τα watches για εσάς και οι μέθοδοι του ελεγκτή εκτελούνται ήδη μέσα στο $apply. Έτσι, για τον προγραμματιστή της εφαρμογής είναι όλα διαφανή.

Σχόλια (4)

Το αναρωτήθηκα κι εγώ για λίγο. Χωρίς setters πώς παρατηρεί το AngularJS τις αλλαγές στο αντικείμενο $scope; Μήπως τις ρωτάει;

Αυτό που κάνει στην πραγματικότητα είναι το εξής: Οποιοδήποτε "κανονικό" μέρος που τροποποιείτε το μοντέλο έχει ήδη κληθεί από τα σπλάχνα του AngularJS, οπότε καλεί αυτόματα το $apply για εσάς μετά την εκτέλεση του κώδικά σας. Ας πούμε ότι ο ελεγκτής σας έχει μια μέθοδο που συνδέεται με το ng-click σε κάποιο στοιχείο. Επειδή το AngularJS καλωδιώνει την κλήση αυτής της μεθόδου μαζί για εσάς, έχει την ευκαιρία να κάνει ένα $apply στο κατάλληλο σημείο. Ομοίως, για τις εκφράσεις που εμφανίζονται ακριβώς στις προβολές, αυτές εκτελούνται από το AngularJS, ώστε να κάνει το $apply.

Όταν η τεκμηρίωση μιλάει για το ότι πρέπει να καλέσετε χειροκίνητα το $apply για κώδικα _εκτός του AngularJS, μιλάει για κώδικα που, όταν εκτελείται, δεν προέρχεται από το ίδιο το AngularJS στη στοίβα κλήσεων.

Σχόλια (0)