回答編集履歴

6

typo修正

2018/04/15 04:57

投稿

think49
think49

スコア18164

test CHANGED
@@ -412,7 +412,7 @@
412
412
 
413
413
 
414
414
 
415
- それから、このコードは簡易版なので、enumerable` 等の descriptor まではコピーしません。 `Object.getOwnPropertyDescriptor` を併用する事で、本来の `this` 値と同じ挙動となります。
415
+ それから、このコードは簡易版なので、`enumerable` 等の descriptor まではコピーしません。 `Object.getOwnPropertyDescriptor` を併用する事で、本来の `this` 値と同じ挙動となります。
416
416
 
417
417
 
418
418
 

5

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

2018/04/15 04:57

投稿

think49
think49

スコア18164

test CHANGED
@@ -148,6 +148,12 @@
148
148
 
149
149
 
150
150
 
151
+
152
+
153
+ ### listenerを共通化したい
154
+
155
+
156
+
151
157
  > 理由は公開したくないのと、リスナをインスタンスごとに持たせるのがムダに思うからです(可能なら共通のものにしたい)。
152
158
 
153
159
 
@@ -162,4 +168,252 @@
162
168
 
163
169
 
164
170
 
171
+ **(2018/04/15 13:55追記)**
172
+
173
+
174
+
175
+ > 最終的にどうなっている状態かがわかりづらいため全体のコードを追記します。
176
+
177
+
178
+
179
+ 関数 `listener` の生成コストを無くしたい意図で書いたコードとお見受けしました。
180
+
181
+ 前述のコードでは、コードの平易さ重視で関数をネストしましたが、私も自分でコードを書くときには関数を外出しするので気持ちは分かります。
182
+
183
+ (おそらく、「listener共通化」の本来の目的も生成コストにあるのではないでしょうか)
184
+
185
+
186
+
187
+ さて、実際に listener を共通化してみると問題点がはっきりします。
188
+
189
+
190
+
191
+ ```JavaScript
192
+
193
+ class X {
194
+
195
+ addListener () {
196
+
197
+ window.addEventListener("click", listener, false)
198
+
199
+ }
200
+
201
+ removeListener () {
202
+
203
+ window.removeEventListener("click", listener, false)
204
+
205
+ }
206
+
207
+ }
208
+
209
+
210
+
211
+ function listener (event) {
212
+
213
+ console.log(event.type);
214
+
215
+ }
216
+
217
+
218
+
219
+ const x1 = new X(),
220
+
221
+ x2 = new X();
222
+
223
+
224
+
225
+ x1.addListener(); // x1 で listener 追加
226
+
227
+ x2.addListener(); // x2 で listener 追加(上書き)
228
+
229
+ document.body.click(); // コンソールには "click" が「1回だけ」出力される
230
+
231
+ x2.removeListener(); // 上書きされた listener 一つを削除
232
+
233
+ document.body.click(); // x1, x2 共に click イベントハンドラが発動しない
234
+
235
+ ```
236
+
237
+
238
+
239
+ 内部的には以下のコードが実行されています。
240
+
241
+
242
+
243
+ ```JavaScript
244
+
245
+ window.addEventListener("click", listener, false); // x1 で listener 追加
246
+
247
+ window.addEventListener("click", listener, false); // x2 で listener 追加 (listener が x1 と同一なので、上書き処理)
248
+
249
+ document.body.click(); // listener は一つしか追加されなかったので、コンソールには "click" が「1回だけ」出力される
250
+
251
+ window.removeEventListener("click", listener, false); // x2 で listener 削除 (listener は1つしか登録されてないので全削除)
252
+
253
+ document.body.click(); // 何も起きない
254
+
255
+ ```
256
+
257
+
258
+
259
+ `addEventListener` から見れば、同じ `listener` を与えれば同じイベントハンドラと認識します。
260
+
261
+ 従って、`listener` を共通化出来ないのです。
262
+
263
+
264
+
265
+ ### まとめ
266
+
267
+
268
+
269
+ 最終的に質問文に追記されたコードを元に今までのロジックを詰め込むと、下記コードになります。
270
+
271
+
272
+
273
+ ```JavaScript
274
+
275
+ const wmap = new WeakMap();
276
+
277
+
278
+
279
+ class X {
280
+
281
+ addListener () {
282
+
283
+ const listener = templateListener.bind(this);
284
+
285
+
286
+
287
+ wmap.set(this, listener);
288
+
289
+ window.addEventListener("click", listener, false);
290
+
291
+ }
292
+
293
+ removeListener(){
294
+
295
+ window.removeEventListener("click", wmap.get(this), false);
296
+
297
+ }
298
+
299
+ }
300
+
301
+
302
+
303
+ function templateListener (event) {
304
+
305
+ this.prop = event.target;
306
+
307
+ console.log(this);
308
+
309
+ }
310
+
311
+
312
+
313
+ const x1 = new X(),
314
+
315
+ x2 = new X();
316
+
317
+
318
+
319
+ x1.addListener();
320
+
321
+ x2.addListener();
322
+
323
+ document.body.click(); // 2回発火
324
+
325
+ document.documentElement.click(); // 2回発火
326
+
327
+ x2.removeListener();
328
+
329
+ document.body.click(); // 1回発火
330
+
331
+ ```
332
+
333
+
334
+
335
+ listener に任意の引数を指定したい場合は `Function.prototype.bind` の第二引数以降を利用するか、`handleEvent` プロパティ付のオブジェクトを渡して、プロパティ経由で取得します。
336
+
337
+
338
+
339
+ ```JavaScript
340
+
341
+ const wmap = new WeakMap()
342
+
343
+
344
+
345
+ class X {
346
+
347
+ addListener () {
348
+
349
+ const listener = createListener(this, templateListener);
350
+
351
+
352
+
353
+ wmap.set(this, listener);
354
+
355
+ window.addEventListener("click", listener, false);
356
+
357
+ }
358
+
359
+ removeListener(){
360
+
361
+ window.removeEventListener("click", wmap.get(this), false);
362
+
363
+ }
364
+
365
+ }
366
+
367
+
368
+
369
+ function createListener (thisArg, handleEvent) {
370
+
371
+ return Object.assign(Object.create(Object.getPrototypeOf(thisArg)), thisArg, {handleEvent: handleEvent, thisArg: thisArg});
372
+
373
+ }
374
+
375
+
376
+
377
+ function templateListener (event) {
378
+
379
+ this.thisArg.prop = event.target
380
+
381
+ console.log(this);
382
+
383
+ }
384
+
385
+
386
+
387
+ const x1 = new X(),
388
+
389
+ x2 = new X();
390
+
391
+
392
+
393
+ x1.addListener();
394
+
395
+ x2.addListener();
396
+
397
+ document.body.click(); // 2回発火
398
+
399
+ document.documentElement.click(); // 2回発火
400
+
401
+ x2.removeListener();
402
+
403
+ document.body.click(); // 1回発火
404
+
405
+ ```
406
+
407
+
408
+
409
+ 渡している `this` 値は `new X` とほぼ同一ですが、`this.prop = event.target` のようにインスタンスにプロパティ拡張する場合には参照値が異なるので、`this.thisArg` 経由で渡してやります。
410
+
411
+ プロパティ拡張を `listener` 内でやらなければ、`this.adddListener`でメソッドは呼べますし、メソッド経由でプロパティ拡張をすれば、`thisArg` プロパティは不要ですが、設計思想の違いが現われるところですね。
412
+
413
+
414
+
415
+ それから、このコードは簡易版なので、enumerable` 等の descriptor まではコピーしません。 `Object.getOwnPropertyDescriptor` を併用する事で、本来の `this` 値と同じ挙動となります。
416
+
417
+
418
+
165
419
  Re: lazex さん

4

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

2018/04/15 04:56

投稿

think49
think49

スコア18164

test CHANGED
@@ -74,4 +74,92 @@
74
74
 
75
75
 
76
76
 
77
+ ### プライベートプロパティを定義する
78
+
79
+
80
+
81
+ ECMAScript® 2017 (ES8) にはプライベートプロパティを作る方法が用意されていない為、`WeakMap` を利用します。
82
+
83
+
84
+
85
+ - [[JavaScript] プライベートプロパティを作る方法.md](https://gist.github.com/think49/66c8a8a6589ec3aacc1689ee7d7fddb6)
86
+
87
+
88
+
89
+ 下記コードは、上記リンク先の `createPivateMap()` を利用したコードです。
90
+
91
+
92
+
93
+ ```JavaScript
94
+
95
+ const Clicker = (() => {
96
+
97
+ var pm = createPivateMap();
98
+
99
+
100
+
101
+ return class Clicker {
102
+
103
+ constructor (message) {
104
+
105
+ pm(this, {message: message});
106
+
107
+ }
108
+
109
+ addListener () {
110
+
111
+ pm(this).listener = (event) => console.log(event.type, pm(this).message);
112
+
113
+ document.addEventListener('click', pm(this).listener, false);
114
+
115
+ }
116
+
117
+ removeListener () {
118
+
119
+ document.removeEventListener('click', pm(this).listener, false);
120
+
121
+ }
122
+
123
+ };
124
+
125
+ })();
126
+
127
+
128
+
129
+ const c1 = new Clicker('Hello, World!'),
130
+
131
+ c2 = new Clicker('Hello, JavaScript!');
132
+
133
+
134
+
135
+ c1.addListener();
136
+
137
+ c2.addListener();
138
+
139
+ document.documentElement.click();
140
+
141
+ c1.removeListener();
142
+
143
+ c2.removeListener();
144
+
145
+ document.documentElement.click();
146
+
147
+ ```
148
+
149
+
150
+
151
+ > 理由は公開したくないのと、リスナをインスタンスごとに持たせるのがムダに思うからです(可能なら共通のものにしたい)。
152
+
153
+
154
+
155
+ 無駄ではないと思います。
156
+
157
+ listenerを共通参照にするというのは、[[JavaScript] プライベートプロパティを作る方法.md](https://gist.github.com/think49/66c8a8a6589ec3aacc1689ee7d7fddb6)のStatic版のコードにするという事です。
158
+
159
+ インスタンス毎に独立した処理に出来ず、`removeEventListener` は全てのインスタンスに影響してしまいます。
160
+
161
+ イベント周りの都合を踏まえると、listenerは各々、別の参照であるべきと考えます。
162
+
163
+
164
+
77
165
  Re: lazex さん

3

typo

2018/04/14 08:10

投稿

think49
think49

スコア18164

test CHANGED
@@ -22,13 +22,13 @@
22
22
 
23
23
  Foo.prototype = {
24
24
 
25
- bar: function bar () {
25
+ bar: function bar () {
26
26
 
27
27
  document.addEventListener('click'. this, false);
28
28
 
29
29
  },
30
30
 
31
- piyo: piyo
31
+ piyo: piyo,
32
32
 
33
33
  handleEvent: function handleEvent (event) {
34
34
 

2

bind, アロー関数

2018/04/13 03:47

投稿

think49
think49

スコア18164

test CHANGED
@@ -1,3 +1,7 @@
1
+ ### handleEvent
2
+
3
+
4
+
1
5
  `handleEvent` を持つオブジェクトを指定してみては、どうでしょう。
2
6
 
3
7
 
@@ -11,3 +15,63 @@
11
15
  - [2.6. Interface EventTarget - DOM
12
16
 
13
17
  Living Standard](https://dom.spec.whatwg.org/#interface-eventtarget)
18
+
19
+
20
+
21
+ ```JavaScript
22
+
23
+ Foo.prototype = {
24
+
25
+ bar: function bar () {
26
+
27
+ document.addEventListener('click'. this, false);
28
+
29
+ },
30
+
31
+ piyo: piyo
32
+
33
+ handleEvent: function handleEvent (event) {
34
+
35
+ this.piyo();
36
+
37
+ }
38
+
39
+ };
40
+
41
+ ```
42
+
43
+
44
+
45
+ ### Function.prototype.bind
46
+
47
+
48
+
49
+ ```JavaScript
50
+
51
+ Foo.prototype.bar = function bar () {
52
+
53
+ document.addEventListener('click'. function handleClick (event) { this.method(); }.bind(this), false);
54
+
55
+ };
56
+
57
+ ```
58
+
59
+
60
+
61
+ ### アロー関数
62
+
63
+
64
+
65
+ ```JavaScript
66
+
67
+ Foo.prototype.bar = function bar () {
68
+
69
+ document.addEventListener('click', (event) = > this.method(), false);
70
+
71
+ };
72
+
73
+ ```
74
+
75
+
76
+
77
+ Re: lazex さん

1

Qiita

2018/04/13 03:46

投稿

think49
think49

スコア18164

test CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  - [JavaScript - javascriptのプログラム構造について(24798)|teratail](https://teratail.com/questions/24798)
6
6
 
7
+ - [addEventListenerで関数に引数を渡す - Qiita](https://qiita.com/rukiadia/items/03aaa8955c0f6576a5e7)
8
+
7
9
  - [EventTarget.addEventListener() - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener)
8
10
 
9
11
  - [2.6. Interface EventTarget - DOM