質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

2回答

1144閲覧

すべてのプロパティでセッターが呼ばれたときに処理を追加したい

_._

総合スコア32

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2020/10/09 12:42

すべてのプロパティでセッターが呼ばれたときに処理を追加したい

すべてのプロパティでセッターが呼ばれたときに処理を追加したいのですが、addEventListenerを経由するとうまくいきません。

javascript

1const Target = class { 2 constructor(element) { 3 this._element = element 4 element.addEventListener('focus', this) 5 element.addEventListener('blur', this) 6 } 7 get element() { 8 return this._element 9 } 10 get active() { 11 return !!this._active 12 } 13 set active(bool) { 14 this._active = bool 15 } 16 handleEvent({ type }) { 17 console.log(type + ' before', this.active) 18 this.active = type === 'focus' 19 console.log(type + ' after', this.active) 20 } 21} 22 23const handler = { 24 get(target, key) { 25 return target[ key ] 26 }, 27 set(target, key, value) { 28 console.log('YES!', { target, key, value }) //何らかの処理 29 target[ key ] = value 30 return true 31 } 32} 33 34const element = document.body.appendChild(document.createElement('textarea')) 35 36const proxiedTarget = new Proxy(new Target(element), handler) 37 38proxiedTarget.active = !proxiedTarget.active //OK 39 40proxiedTarget.active = !proxiedTarget.active //OK 41 42proxiedTarget.handleEvent({ type: 'focus' }) //OK //addEventListener用ですが確認のため直接実行 43 44//クリック等で要素をフォーカス・ブラーさせてaddEventListener経由でhandleEventが実行されるとhandler.setが反応しない><

上記の例ではプロパティは「active」一つだけですが実際は(継承元も含めて)たくさんあるのでProxyで一括処理できないかなと思いました。

が、handleEventメソッドを直接呼ぶ場合はメソッド内の「this.active = type === 'focus'」に反応して期待通りにhandlerの処理が行われるのですが、
addEventListener経由で呼ばれる場合はProxyにラップされていないのかhandlerの処理が行われません。
addEventListener経由でもトラップするにはどうすればいいですか?

あと、

javascript

1const proxiedTarget = new Proxy(new Target(element), handler)

ではなく

javascript

1const ProxiedTarget = new Proxy(Target, handler) 2const proxiedTarget = new ProxiedTarget(element)

みたいにできますか?(Proxyのターゲットとしてインスタンスじゃなくクラスを渡しても実現可能ですか?)

やりたいことがもっと短いコードでかんたんに実現できるならProxyじゃなくても何でもいいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

think49

2020/10/11 04:17

想像力を要求される質問な気がしますが、 Q1. _acitveのような _ で始まるプロパティはプライベートプロパティの代替ですか Q2. focus,blurイベントハンドラで _active プロパティの真偽値が反転するように、mouseover/mouseoutなどの対応するイベントハンドラ同士でトグル処理するのが目的ですか
_._

2020/10/11 09:02

サンプルのコードを削りすぎていますか?すみません。 A1. はいそんなかんじです。じっさいは「handler.set()」内で「if (key[ 0 ] !== '_')」等として選別します。 A2. focusとblurの値の反転以外は、ほかにはkeydownでキーコードを記録したり(厳密には違いますが)mousemoveで座標(とかドラッグ中かどうか等)を記録したりです。 Proxyにはぜんぜんこだわっていません。ほかに思いつかなかっただけでどんな方法でもいいです。よろしくおねがいします。
think49

2020/10/11 23:12 編集

> 上記の例ではプロパティは「active」一つだけですが実際は(継承元も含めて)たくさんあるのでProxyで一括処理できないかなと思いました。 イベントタイプに対応するデータ(key/value)が多量にあるという意味なら、new Proxyを使用すれば、addEventListener処理は楽になりますが、イベント発火時に多量のデータを検索するコストを払うことになり、コストを先に払うか、後に払うかだけの違いになっている気がします。 結局、new Proxy版とそうでない版を作って、パフォーマンスを比較してみる以外にないのではないでしょうか。
_._

2020/10/11 11:57

大量にあるのはクラスのプロパティで、 (書き込みもできる(getだけじゃなくsetもできる)プロパティは親クラスからの継承も含めて今のところ10数個) これらのプロパティすべての書き込みを監視したいのですが、この書き込みもできるプロパティの一部が、 外部からだけじゃなくaddEventListener経由で内部からも変更される仕組みで、このaddEventListener経由のばあいだけがうまくいかなかったので 方向としてはProxyが正解であとはaddEventListenerの箇所だけうまくやれば・・・と思ってました。 Proxyは処理が重くなるんですか。
think49

2020/10/11 12:06

10数個程度なら、プロパティを列挙して初期化するだけで良いのでは…。 それほど多いとは思いません。
_._

2020/10/11 12:28

どうもありがとうございます。その方向で進めたいと思います。
guest

回答2

0

ベストアンサー

要件

鍵となる動作は

  • 特定のイベントが発火時、特定のkey/value形式のデータを書き込む

これだけだと思います。
new Proxy を使用することで一部の動作を後回しに出来ますが、結局は上記紐付けデータをどこかに保存しておかなければなりません。
紐付けは new Array もしくは new Map をを使用すれば問題ないように思います。

コード

gistにコードを、Plunkerにデモを置きました。

Re: . さん

投稿2020/10/11 10:46

編集2020/10/11 13:53
think49

総合スコア18166

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

_._

2020/10/11 11:23

もしかしたらまた説明不足になってしまってますか・・・すみません。 active等の各種プロパティは外部からも変更できる状態にしたいです。 各種プロパティの外部からの変更に反応しつつ、addEventListener経由の内部からの変更にも対応したいのですがその部分でうまくいかないのが現状です。
think49

2020/10/11 11:55 編集

「そのぐらいは自分で対応して下さい」といいたいところですが、大した手間ではなかったので更新しました(v1.0.1)。 ※自分で調べた形跡もなく、「要望だけを伝える」のは基本的に作業依頼と見なしています。 getterをご存知であれば、読み取り専用にする方法はご自身で思いつきそうな気がしますが、全く見当が付かなかったのでしょうか。
think49

2020/10/11 12:04

あれ…、「書き換え可能」ですか? v1.0.0は書き換え可能だったはずですが…。
_._

2020/10/11 12:08

すみません。おもいのほか複雑だったのでコードを読み切れていないまま 意図が伝わっていないのではと思いはやく返信しないとと焦ってコメントしてしまいました。 ここまで入り組んだ問題なんですね。数コード追加で実現するようなもっと単純な話かと思っていました。 デモコード1.0.1ありがとうございます。 自分がわかるかんたんなクラスの仕組みとはちがうのでまだ理解が追いついていません。
_._

2020/10/11 12:09

入れ違いすみません。コードの内容を理解しないまま慌てて返信しています。本当にすみません。
think49

2020/10/11 13:56

第三引数で「writable: true/false」を指定できるようにしました。 -> fired-event-1.0.2.js
AkitoshiManabe

2020/10/11 17:52

> 自分がわかるかんたんなクラスの仕組みとはちがうので 普通はメタプログラミングせずに事足りる事例が多いので、JavaScriptの抽象化を熟知していないとProxyの活用は難しいかもしれません。 Proxyの取っ掛かりとしては 「MDN メタプログラミング」もあります。 https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Meta_programming RE: _._ さん
think49

2020/10/11 23:35

後で気になった点。 new Fooのプロパティ(key/value)をnew FiredEventに持ってくるには、次のように書きますが、 const foo = new Foo; const fe = new FiredEvent(element, new Map([['focus', Object.entries(foo)]])); 一方で書き換えた値がもう一方に反映されるわけではありません。 foo.bar = 'replaced'; console.log(foo.bar === fe.data.get('bar')); false 両者の同期をとる動作を必要としている場合、どちらかにgetter/setter(or Proxy)を設けて、両値を書き換えてやる必要があります。
_._

2020/10/12 00:36

> ESに存在しないprivate propertyをWeakMapで代替する方法を使っていますので、そこが珍しく感じられるのかもしれません。 class Foo { #private constructor() {} } みたいなのをbabelに通したらWeekMapを使ったコードになりました。 なんとなくですがやっていることがわかりました。ありがとうございます。
guest

0

すみません。これでいけている気がします。

(本題ではありませんが誤解を生んでいた擬似的なプライベートフィールドも書き直しました。)

javascript

1const Target = class { 2 #element 3 #active 4 constructor(element) { 5 this.#element = element 6 element.addEventListener('focus', this) 7 element.addEventListener('blur', this) 8 } 9 get element() { 10 return this.#element 11 } 12 get active() { 13 return !!this.#active 14 } 15 set active(bool) { 16 this.#active = bool 17 } 18 handleEvent({ type }) { 19 console.log(type + ' before', this.active) 20 this.active = type === 'focus' 21 console.log(type + ' after', this.active) 22 } 23} 24 25const handler = { 26 get(target, key, receiver) { 27 return target[ key ] 28 }, 29 set(target, key, value, receiver) { 30 console.log('YES!', { target, key, value, receiver }) //何らかの処理 31 target[ key ] = value 32 return true 33 } 34} 35 36const element = document.body.appendChild(document.createElement('textarea')) 37 38const target = new Target(element) 39const proxiedTarget = new Proxy(target, handler) 40target.handleEvent = target.handleEvent.bind(proxiedTarget) //追加! 41 42proxiedTarget.active = !proxiedTarget.active 43 44proxiedTarget.active = !proxiedTarget.active 45 46proxiedTarget.handleEvent({ type: 'focus' })

投稿2020/10/12 00:34

編集2020/10/16 10:29
_._

総合スコア32

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問