web-dev-qa-db-fra.com

Portée Javascript addEventListener et ce

Je suis un développeur C # expérimentant avec JavaScript et j'essaie de me faire une idée de la portée :)

J'ai le code suivant qui contient un addEventListener dans lequel je veux utiliser un champ de mon objet:

(function(window) {

    function Keyboard() {
        this.keys = {};
    }

    Keyboard.prototype.handle_keydown = function(args) {
        this.keys[args.keyCode] = true;
    }

    Keyboard.prototype.listen = function() {
        window.addEventListener('keydown', this.handle_keydown);
    }

    app.util.keyboard = new Keyboard();

})(window);

Je voudrais utiliser le tableau de clés dans mon hander, mais comprenez que je ne peux pas y accéder en l'utilisant, car c'est la fenêtre dans ce contexte (correct?). Si je le change en

app.util.keyboard.keys[args.keyCode] = true;

cela fonctionne, mais je ne suis pas sûr que ce soit un bon moyen de le réparer.

J'ai trouvé cette question , ce qui semble assez similaire, mais je ne sais pas comment je peux l'adapter à mon exemple.

Merci de votre aide!

21
Raf

Quelques choses:

  • La plupart des gens suggèrent quelque chose comme var self = this Parce que c'est rapide et facile.

  • Mais var self = this Ne sépare pas entièrement l'objet de vue de la voir la logique, qui provenant d'un arrière-plan C # plus formel et regardant votre code , ressemble à quelque chose que vous voulez faire.

  • Afin que le rappel ne s'exécute que lorsque l'événement se déclenche, encapsulez le gestionnaire dans une fonction, afin qu'il soit évalué immédiatement, mais uniquement exécuté lorsque et si un événement keydown se déclenche (voir le code ci-dessous).

  • Comprendre la portée dans JS: quel que soit le contexte d'exécution, c'est aussi la portée actuelle. Votre écouteur a été ajouté dans une méthode (appelée listen) sur Keyboard.prototype, Mais l'événement keydown est effectivement déclenché sur window - le gestionnaire s'exécute dans un contexte différent de celui où il a été défini; il s'exécute dans le contexte de ce qui l'appelle, dans ce cas, window, il est donc limité à window sauf si vous le liez à un autre objet via bind ou apply quand il est défini.

Dans votre code, window est la vue avec laquelle un utilisateur interagit et Keyboard est le contrôleur de cette vue. Dans les modèles MVC comme ce à quoi vous êtes probablement habitué dans C # /. NET, les vues ne se disent pas quoi faire quand les choses se produisent, les contrôleurs disent aux vues quoi faire. Donc, si vous deviez assigner une référence au contrôleur en utilisant var self = this Comme tant d'autres, la vue se gérerait d'elle-même - mais uniquement pour ce gestionnaire spécifique pour les événements keydown. Ceci est incohérent et deviendrait difficile à gérer dans un grand projet.

Une solution:

Keyboard.prototype.listen = function() {
    window.addEventListener('keydown', function(e) {
        this.handle_keydown(e);
    }.bind(this), false);
}

Une meilleure solution:

Keyboard.prototype.view = window;

Keyboard.prototype.listen = function() {
    this.view.addEventListener('keydown', function(e) {
        this.handle_keydown(e);
    }.bind(this), false);
}

La meilleure solution (jusqu'à ce que ES6 class soit prêt):

// define
function addViewController(view) {

    function ViewController() {

        this.handle_keydown = function(args) {
            // handle keydown events
        };

        this.listen = function() {
            this.view.addEventListener('keydown', function(e) {
                this.handle_keydown(e);
            }.bind(this), false);
        };

        this.view = view;
        return this;

    }

    return new ViewController(view);

}

// implement
var keyboard = addViewController(window);
keyboard.listen();
  • Remarque: .bind() est compatible avec ECMAScript 5+; si vous avez besoin d'une solution pour les navigateurs plus anciens, Mozilla a publié une excellente alternative à .bind() en utilisant functions et .call():

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind

Edit: Voici à quoi ressemblera votre objet keyboard instancié en utilisant cette nouvelle solution modulaire: enter image description here

39
Benny
Keyboard.prototype.listen = function() {
    var self = this;
    window.addEventListener('keydown', function(event) {
       self.handle_keydown(event);
       // self is your Keyboard object. You can refer to all your properties from this
    });
}

Comment ce code fonctionne:

  1. Nous créons la variable self, qui stocke la référence à la variable this.
  2. La fonction intérieure est une fermeture, donc elle fait référence à soi.
  3. Lorsque la fonction de fermeture est appelée: this pointe vers l'objet dom, tandis que self pointe vers l'objet clavier.
  4. La fermeture est appelée avec event comme paramètre que nous transmettons à la fonction membre de l'objet clavier.
5
closure

Que diriez-vous

function Keyboard() {
    this.keys = {};
    var self = this;
    this.handle_keydown = function(args) {
        self.keys[args.keyCode] = true;
    }
    this.listen = function() {
        window.addEventListener('keydown', this.handle_keydown);
    }
}
app.util.keyboard = new Keyboard();
3
Musa