JavaScriptのthis参照について



JavaScriptのthisは、関数の呼ばれ方によって、参照するオブジェクトが異なる。ただし、アロー関数はこの限りではない。


呼び出し方参照
普通の関数呼び出しグローバルオブジェクト
メソッドとして呼び出しレシーバーオブジェクト
明示的に指定指定したオブジェクト
コンストラクターとして呼び出しインスタンス
イベントハンドラー/リスナーとして呼び出しイベントの発火元オブジェクト


普通の関数として呼ばれた場合、メソッドとして呼ばれた場合、明示的に指定した場合



main.js
function test() {
console.log('this === global');
console.log(this === global);
console.log('this === obj');
console.log(this === obj);
console.log('this === obj2');
console.log(this === obj2);
console.log('');
}

var obj = {
test: test
};

var obj2 = {}

console.log('***** test() *****');
test();
console.log('***** obj.test() *****');
obj.test();
console.log('***** (test.bind(obj2))() *****');
(test.bind(obj2))();


$ node main.js 
***** test() *****
this === global
true
this === obj
false
this === obj2
false

***** obj.test() *****
this === global
false
this === obj
true
this === obj2
false

***** (test.bind(obj2))() *****
this === global
false
this === obj
false
this === obj2

true

test()のように普通の関数として呼んだ時は、グローバル変数globalと等しい。obj.test()のようにメソッド呼び出しした時は、レシーバーオブジェクトobj(.の左辺のオブジェクト)と等しい。bindで明示的にthisが参照するオブジェクトを指定した時は、指定したオブジェクト(上の例ではobj2)と等しい。なお、(test.bind(obj2))();は、test.call(obj2);、test.apply(obj2);などとしても同じ。

コンストラクターとして呼ばれた場合

生成されたインスタンスを参照。

main.js
var obj = null;
function Constructor() {
obj = this;
}
var instance = new Constructor();
console.log(obj === instance);

$ node main.js 

true

イベントハンドラー/リスナーとして呼ばれた場合

イベントハンドラー/リスナー内では、thisはイベントソースオブジェクト(イベントの発行元オブジェクト)を参照する。
この動作は、クライアントサイド、サーバーサイドともに共通である。

クライアントサイドの場合

クライアントサイドでは、イベントハンドラー/リスナー内のthisは、設定先の要素自身を参照する。

<button id="test"></button>

(function() {
document.addEventListener('DOMContentLoaded', function() {
var button = document.querySelector('#test');
button.addEventListener('click', function() {
console.log(this.constructor)
});
})
})();


ボタンを押すと、コンソールに下記が出力

ƒ HTMLButtonElement() { [native code] }

HTMLButtonElementは、querySelectorとかの戻り値と同じクラス。

補足だが、継承構造は下記。

https://developer.mozilla.org/en-US/docs/Web/API/HTMLButtonElement


Nodeの場合

下記に例を示す。
下記では、人を表現するPersonクラスを定義した。Personクラスのクラスメソッドであるaddにより、新しい人が作成され、人のリスト_personsに追加される。新しい人が追加されると、addという名前のイベントが発行される。Observerオブジェクトは、addイベントを監視するオブザーバーである。addイベントが通知されると、Observer.handleAddメソッドが呼び出される。Person.addListenerメソッドにより、オブザーバーとして登録される。

main.js
const events = require('events');
const EventEmitter = events.EventEmitter;

class Person {
constructor(name) {
this._name = name;
}
}

// クラス変数
Person._persons = [];
Person._eventEmitter = new EventEmitter();
// クラスメソッド
Person.add = function(name) {
var person = new this(name);
this._persons.push(person);
this._eventEmitter.emit('add');
}
Person.addListener = function(event, listener) {
this._eventEmitter.on(event, listener);
}

// オブザーバー
const Observer = {
handleAdd: function() {
console.debug(this)
}
}

// イベントハンドラーの登録
Person.addListener('add', Observer.handleAdd)

Person.add('John');

上記コードを実行すると、thisがイベントソースであるEventEmitterを参照していることがわかる。

$ node main.js 
EventEmitter {
  _events: { add: [Function: handleAdd] },
  _eventsCount: 1,

  _maxListeners: undefined }

thisにObserver自身を参照させるには、下記のようにbindを使う。

// イベントハンドラーの登録
// Person.addListener('add', Observer.handleAdd)
Person.addListener('add', Observer.handleAdd.bind(Observer))


$ node main.js 

{ handleAdd: [Function: handleAdd] }

thisがObserverオブジェクトを参照するようになった。



コメント

このブログの人気の投稿

PowerShell 6で、Shift_JISのCSVをImport-Csvで読み込んだら文字化けした

Windowsで、特定のユーザーに特定のサービスの再起動を許可する

PowerShellでイベントログを取得する時、「指定した選択条件に一致するイベントが見つかりませんでした。」が煩わしいのでcatchする