質問するログイン新規登録

回答編集履歴

6

typo修正

2018/04/15 04:57

投稿

think49
think49

スコア18194

answer CHANGED
@@ -205,6 +205,6 @@
205
205
  渡している `this` 値は `new X` とほぼ同一ですが、`this.prop = event.target` のようにインスタンスにプロパティ拡張する場合には参照値が異なるので、`this.thisArg` 経由で渡してやります。
206
206
  プロパティ拡張を `listener` 内でやらなければ、`this.adddListener`でメソッドは呼べますし、メソッド経由でプロパティ拡張をすれば、`thisArg` プロパティは不要ですが、設計思想の違いが現われるところですね。
207
207
 
208
- それから、このコードは簡易版なので、enumerable` 等の descriptor まではコピーしません。 `Object.getOwnPropertyDescriptor` を併用する事で、本来の `this` 値と同じ挙動となります。
208
+ それから、このコードは簡易版なので、`enumerable` 等の descriptor まではコピーしません。 `Object.getOwnPropertyDescriptor` を併用する事で、本来の `this` 値と同じ挙動となります。
209
209
 
210
210
  Re: lazex さん

5

listenerを共通化したい、まとめ節を追記

2018/04/15 04:57

投稿

think49
think49

スコア18194

answer CHANGED
@@ -73,6 +73,9 @@
73
73
  document.documentElement.click();
74
74
  ```
75
75
 
76
+
77
+ ### listenerを共通化したい
78
+
76
79
  > 理由は公開したくないのと、リスナをインスタンスごとに持たせるのがムダに思うからです(可能なら共通のものにしたい)。
77
80
 
78
81
  無駄ではないと思います。
@@ -80,4 +83,128 @@
80
83
  インスタンス毎に独立した処理に出来ず、`removeEventListener` は全てのインスタンスに影響してしまいます。
81
84
  イベント周りの都合を踏まえると、listenerは各々、別の参照であるべきと考えます。
82
85
 
86
+ **(2018/04/15 13:55追記)**
87
+
88
+ > 最終的にどうなっている状態かがわかりづらいため全体のコードを追記します。
89
+
90
+ 関数 `listener` の生成コストを無くしたい意図で書いたコードとお見受けしました。
91
+ 前述のコードでは、コードの平易さ重視で関数をネストしましたが、私も自分でコードを書くときには関数を外出しするので気持ちは分かります。
92
+ (おそらく、「listener共通化」の本来の目的も生成コストにあるのではないでしょうか)
93
+
94
+ さて、実際に listener を共通化してみると問題点がはっきりします。
95
+
96
+ ```JavaScript
97
+ class X {
98
+ addListener () {
99
+ window.addEventListener("click", listener, false)
100
+ }
101
+ removeListener () {
102
+ window.removeEventListener("click", listener, false)
103
+ }
104
+ }
105
+
106
+ function listener (event) {
107
+ console.log(event.type);
108
+ }
109
+
110
+ const x1 = new X(),
111
+ x2 = new X();
112
+
113
+ x1.addListener(); // x1 で listener 追加
114
+ x2.addListener(); // x2 で listener 追加(上書き)
115
+ document.body.click(); // コンソールには "click" が「1回だけ」出力される
116
+ x2.removeListener(); // 上書きされた listener 一つを削除
117
+ document.body.click(); // x1, x2 共に click イベントハンドラが発動しない
118
+ ```
119
+
120
+ 内部的には以下のコードが実行されています。
121
+
122
+ ```JavaScript
123
+ window.addEventListener("click", listener, false); // x1 で listener 追加
124
+ window.addEventListener("click", listener, false); // x2 で listener 追加 (listener が x1 と同一なので、上書き処理)
125
+ document.body.click(); // listener は一つしか追加されなかったので、コンソールには "click" が「1回だけ」出力される
126
+ window.removeEventListener("click", listener, false); // x2 で listener 削除 (listener は1つしか登録されてないので全削除)
127
+ document.body.click(); // 何も起きない
128
+ ```
129
+
130
+ `addEventListener` から見れば、同じ `listener` を与えれば同じイベントハンドラと認識します。
131
+ 従って、`listener` を共通化出来ないのです。
132
+
133
+ ### まとめ
134
+
135
+ 最終的に質問文に追記されたコードを元に今までのロジックを詰め込むと、下記コードになります。
136
+
137
+ ```JavaScript
138
+ const wmap = new WeakMap();
139
+
140
+ class X {
141
+ addListener () {
142
+ const listener = templateListener.bind(this);
143
+
144
+ wmap.set(this, listener);
145
+ window.addEventListener("click", listener, false);
146
+ }
147
+ removeListener(){
148
+ window.removeEventListener("click", wmap.get(this), false);
149
+ }
150
+ }
151
+
152
+ function templateListener (event) {
153
+ this.prop = event.target;
154
+ console.log(this);
155
+ }
156
+
157
+ const x1 = new X(),
158
+ x2 = new X();
159
+
160
+ x1.addListener();
161
+ x2.addListener();
162
+ document.body.click(); // 2回発火
163
+ document.documentElement.click(); // 2回発火
164
+ x2.removeListener();
165
+ document.body.click(); // 1回発火
166
+ ```
167
+
168
+ listener に任意の引数を指定したい場合は `Function.prototype.bind` の第二引数以降を利用するか、`handleEvent` プロパティ付のオブジェクトを渡して、プロパティ経由で取得します。
169
+
170
+ ```JavaScript
171
+ const wmap = new WeakMap()
172
+
173
+ class X {
174
+ addListener () {
175
+ const listener = createListener(this, templateListener);
176
+
177
+ wmap.set(this, listener);
178
+ window.addEventListener("click", listener, false);
179
+ }
180
+ removeListener(){
181
+ window.removeEventListener("click", wmap.get(this), false);
182
+ }
183
+ }
184
+
185
+ function createListener (thisArg, handleEvent) {
186
+ return Object.assign(Object.create(Object.getPrototypeOf(thisArg)), thisArg, {handleEvent: handleEvent, thisArg: thisArg});
187
+ }
188
+
189
+ function templateListener (event) {
190
+ this.thisArg.prop = event.target
191
+ console.log(this);
192
+ }
193
+
194
+ const x1 = new X(),
195
+ x2 = new X();
196
+
197
+ x1.addListener();
198
+ x2.addListener();
199
+ document.body.click(); // 2回発火
200
+ document.documentElement.click(); // 2回発火
201
+ x2.removeListener();
202
+ document.body.click(); // 1回発火
203
+ ```
204
+
205
+ 渡している `this` 値は `new X` とほぼ同一ですが、`this.prop = event.target` のようにインスタンスにプロパティ拡張する場合には参照値が異なるので、`this.thisArg` 経由で渡してやります。
206
+ プロパティ拡張を `listener` 内でやらなければ、`this.adddListener`でメソッドは呼べますし、メソッド経由でプロパティ拡張をすれば、`thisArg` プロパティは不要ですが、設計思想の違いが現われるところですね。
207
+
208
+ それから、このコードは簡易版なので、enumerable` 等の descriptor まではコピーしません。 `Object.getOwnPropertyDescriptor` を併用する事で、本来の `this` 値と同じ挙動となります。
209
+
83
210
  Re: lazex さん

4

プライベートプロパティを定義する

2018/04/15 04:56

投稿

think49
think49

スコア18194

answer CHANGED
@@ -36,4 +36,48 @@
36
36
  };
37
37
  ```
38
38
 
39
+ ### プライベートプロパティを定義する
40
+
41
+ ECMAScript® 2017 (ES8) にはプライベートプロパティを作る方法が用意されていない為、`WeakMap` を利用します。
42
+
43
+ - [[JavaScript] プライベートプロパティを作る方法.md](https://gist.github.com/think49/66c8a8a6589ec3aacc1689ee7d7fddb6)
44
+
45
+ 下記コードは、上記リンク先の `createPivateMap()` を利用したコードです。
46
+
47
+ ```JavaScript
48
+ const Clicker = (() => {
49
+ var pm = createPivateMap();
50
+
51
+ return class Clicker {
52
+ constructor (message) {
53
+ pm(this, {message: message});
54
+ }
55
+ addListener () {
56
+ pm(this).listener = (event) => console.log(event.type, pm(this).message);
57
+ document.addEventListener('click', pm(this).listener, false);
58
+ }
59
+ removeListener () {
60
+ document.removeEventListener('click', pm(this).listener, false);
61
+ }
62
+ };
63
+ })();
64
+
65
+ const c1 = new Clicker('Hello, World!'),
66
+ c2 = new Clicker('Hello, JavaScript!');
67
+
68
+ c1.addListener();
69
+ c2.addListener();
70
+ document.documentElement.click();
71
+ c1.removeListener();
72
+ c2.removeListener();
73
+ document.documentElement.click();
74
+ ```
75
+
76
+ > 理由は公開したくないのと、リスナをインスタンスごとに持たせるのがムダに思うからです(可能なら共通のものにしたい)。
77
+
78
+ 無駄ではないと思います。
79
+ listenerを共通参照にするというのは、[[JavaScript] プライベートプロパティを作る方法.md](https://gist.github.com/think49/66c8a8a6589ec3aacc1689ee7d7fddb6)のStatic版のコードにするという事です。
80
+ インスタンス毎に独立した処理に出来ず、`removeEventListener` は全てのインスタンスに影響してしまいます。
81
+ イベント周りの都合を踏まえると、listenerは各々、別の参照であるべきと考えます。
82
+
39
83
  Re: lazex さん

3

typo

2018/04/14 08:10

投稿

think49
think49

スコア18194

answer CHANGED
@@ -10,10 +10,10 @@
10
10
 
11
11
  ```JavaScript
12
12
  Foo.prototype = {
13
- bar: function bar () {
13
+ bar: function bar () {
14
14
  document.addEventListener('click'. this, false);
15
15
  },
16
- piyo: piyo
16
+ piyo: piyo,
17
17
  handleEvent: function handleEvent (event) {
18
18
  this.piyo();
19
19
  }

2

bind, アロー関数

2018/04/13 03:47

投稿

think49
think49

スコア18194

answer CHANGED
@@ -1,7 +1,39 @@
1
+ ### handleEvent
2
+
1
3
  `handleEvent` を持つオブジェクトを指定してみては、どうでしょう。
2
4
 
3
5
  - [JavaScript - javascriptのプログラム構造について(24798)|teratail](https://teratail.com/questions/24798)
4
6
  - [addEventListenerで関数に引数を渡す - Qiita](https://qiita.com/rukiadia/items/03aaa8955c0f6576a5e7)
5
7
  - [EventTarget.addEventListener() - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener)
6
8
  - [2.6. Interface EventTarget - DOM
7
- Living Standard](https://dom.spec.whatwg.org/#interface-eventtarget)
9
+ Living Standard](https://dom.spec.whatwg.org/#interface-eventtarget)
10
+
11
+ ```JavaScript
12
+ Foo.prototype = {
13
+ bar: function bar () {
14
+ document.addEventListener('click'. this, false);
15
+ },
16
+ piyo: piyo
17
+ handleEvent: function handleEvent (event) {
18
+ this.piyo();
19
+ }
20
+ };
21
+ ```
22
+
23
+ ### Function.prototype.bind
24
+
25
+ ```JavaScript
26
+ Foo.prototype.bar = function bar () {
27
+ document.addEventListener('click'. function handleClick (event) { this.method(); }.bind(this), false);
28
+ };
29
+ ```
30
+
31
+ ### アロー関数
32
+
33
+ ```JavaScript
34
+ Foo.prototype.bar = function bar () {
35
+ document.addEventListener('click', (event) = > this.method(), false);
36
+ };
37
+ ```
38
+
39
+ Re: lazex さん

1

Qiita

2018/04/13 03:46

投稿

think49
think49

スコア18194

answer CHANGED
@@ -1,6 +1,7 @@
1
1
  `handleEvent` を持つオブジェクトを指定してみては、どうでしょう。
2
2
 
3
3
  - [JavaScript - javascriptのプログラム構造について(24798)|teratail](https://teratail.com/questions/24798)
4
+ - [addEventListenerで関数に引数を渡す - Qiita](https://qiita.com/rukiadia/items/03aaa8955c0f6576a5e7)
4
5
  - [EventTarget.addEventListener() - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener)
5
6
  - [2.6. Interface EventTarget - DOM
6
7
  Living Standard](https://dom.spec.whatwg.org/#interface-eventtarget)