web-dev-qa-db-ja.com

JavaScriptスコープaddEventListenerとthis

私はJavaScriptを試すC#開発者であり、スコープを理解しようとしています:)

オブジェクトのフィールドを使用するaddEventListenerを含む次のコードがあります。

(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);

ハンドラーでkeys配列を使用したいのですが、これはそのコンテキストのウィンドウであるため、これを使用するとアクセスできないことを理解しています(正しいですか?)。に変更した場合

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

それは機能しますが、それがそれを修正する良い方法であるかどうかはわかりません。

私は この質問 を見つけましたが、これはかなり似ているようですが、どのようにして自分の例に当てはめることができるかわかりません。

ご協力いただきありがとうございます!

21
Raf

いくつかのこと:

  • 高速で簡単なので、ほとんどの人は_var self = this_のようなものを提案します。

  • しかし、_var self = this_は、ビューオブジェクトビューロジックから完全に分離しません。コードを見ると、やりたいことのように聞こえます。

  • イベントが発生したときにのみコールバックを実行するには、ハンドラーを関数にラップして、すぐに評価されるようにしますが、keydownイベントが発生したときにのみ実行されます(以下のコードを参照)。

  • JSのスコープの理解:実行コンテキストが何であれ、現在のスコープでもあります。リスナーは_Keyboard.prototype_のメソッド(listenと呼ばれます)に追加されましたが、keydownイベントは実際にはwindowで発生します-ハンドラーは定義された場所とは異なるコンテキストで実行されています。呼び出し元のコンテキスト(この場合はwindow)内で実行されているため、定義時にwindowまたはbindを介して別のオブジェクトにバインドしない限り、スコープはapplyになります。

コードでは、windowはユーザーが操作するビューであり、Keyboardはそのビューのコントローラーです。 C#/。NETでおそらく慣れているようなMVCパターンでは、何かが発生したときにビューは何をすべきかを自分自身に伝えません。コントローラーはビューに何をすべきかを伝えます。したがって、多くの場合のように_var self = this_を使用してコントローラへの参照を割り当てる場合、ビューはそれ自体を管理しますが、keydownイベントの特定のハンドラに対してのみです。これは一貫性がなく、大規模なプロジェクトでは管理が難しくなります。

解決策:

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

より良い解決策:

_Keyboard.prototype.view = window;

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

最良の解決策(ES6 classの準備ができるまで):

_// 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();
_
  • 注:.bind()はECMAScript 5+と互換性があります。古いブラウザのソリューションが必要な場合、Mozillaはfunctions.bind()を使用した.call()の優れた代替案を投稿しています。

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

編集:インスタンス化されたkeyboardオブジェクトは、この新しいモジュラーソリューションを使用して次のようになります。 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
    });
}

このコードの仕組み:

  1. this変数への参照を格納する変数selfを作成しています。
  2. 内部関数はクロージャであるため、自己を参照しています。
  3. クロージャー関数が呼び出されたとき:thisはdomオブジェクトを指し、selfはキーボードオブジェクトを指します。
  4. クロージャーは、キーボードオブジェクトのメンバー関数に渡すパラメーターとしてeventを指定して呼び出されます。
5
closure

いかがですか

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