###前提・実現したいこと
JavaScriptをECMAScript2015のconstを積極的に使いつつ、フラグをオンオフするような実装をしたい。
###発生している問題・エラーメッセージ
実現したいことはそれなのですが、実際どう実装するのがよいのかよくわかりません。
定数とか再代入とかを特に気にしなければごく単純なコードです。
###該当のソースコード
たとえばまず定数とか再代入とかを特に気にせずに一例を書きます。
javascript
1var isTouching = false; 2 3// 例えばprocessingのようにこの関数が繰り返し呼ばれるとします。 4function loop(){ 5 if(isTouching===true) console.log('red'); 6 else console.log('blue'); 7} 8 9// タッチ開始時にこの関数が呼ばれるとします。 10function ontouchstart(){ 11 isTouching = true; 12} 13 14// タッチ終了時にこの関数が呼ばれるとします。 15function ontouchend(){ 16 isTouching = false; 17}
なんのことはないコードです。
ただ、「できるならすべてconstにしたほうがよい」と聞いて、できるだけconstを使うように書く練習をしているのですが、こういう時はどうするのが良いのだろう?とわからなくなったので質問しました。
###補足情報
補足1. JavaScriptとはいいつつ再代入をできるだけせずにどうやるのかという一般的な話しでもあると思います。(ただし言語機能がそれぞれ違うのでできることできないことはあるとおもいます。)
補足2. もちろん「現実的にはそんなこと気にせずletを使いなさい」とかもあるとは思いますが、
今回の質問はいったん現実的にはというのをおいておいて、再代入禁止のルールだとどうするのが良いのか、知りたいです。
また、そうすることで、その再代入禁止のルールを守るのがこのケースでは大変なものなのかどうかを知りたいです。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2017/09/21 23:12
2017/09/21 23:14
2017/09/21 23:14
回答7件
0
JavaScriptという環境を考えれば、
- オブジェクトを
const
にして、中身を書き換える - DOMツリーに
$().data
のような手法でぶら下げる
といった、トンチ的な解決策でも取らない限り、「状態を変数に保管せずに進める」というのは現実的ではありません。
というのも、Haskellのような純粋関数型言語(変数の再代入どころか、破壊的な変更も存在しない)で状態を表現する場合、「『前の状態』を引数として、『次の状態』を返す」という方法を取って、コード上からは状態依存を追い出しているのです。
一方でJavaScriptの場合、イベントハンドラの実行時には、クロージャで生き残った変数以外リセットされてしまって、「前の状態を渡す」ということができないので、状態を引き継ぐにはどこかで(破壊的な方法で)保存しておいたものを読み取るほかないのです。
投稿2017/09/22 00:24
編集2017/09/22 00:24総合スコア145121
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
状態が変わるフラグな訳ですから普通にletで良いのでは?
私の過去質問も参考になると思います。
https://teratail.com/questions/89800
https://teratail.com/questions/91873
投稿2017/09/21 23:56
編集2017/09/22 00:02退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2017/09/21 23:58
退会済みユーザー
2017/09/22 00:01
0
挑戦してみました。
まずは質問のコードについてですが、<button id="btn">ボタン</button>
というものだけ用意してボタンを押している間、離したらと言うことで、次のようなコードにしました。(タッチはタッチパネルが無いとテストできないので)
JavaScript
1var intervalTime = 1000; 2var btn = document.getElementById("btn"); 3var flag = false; 4function loop() { 5 if (flag === true) 6 console.log('red'); 7 else 8 console.log('blue'); 9} 10function down() { 11 flag = true; 12} 13function up() { 14 flag = false; 15} 16setInterval(loop, intervalTime); 17btn.addEventListener("mousedown", down); 18btn.addEventListener("mouseup", up);
上のコードを、var
は使用禁止(ローカル変数はconst
のみ)、変数及びプロパティへの再代入を禁止、ArrayやObject等を作成して変化させていくのも禁止、という条件のもとにES2015+で書き直したのがこちらです。
JavaScript
1const intervalTime = 1000; 2const btn = document.getElementById("btn"); 3const main = ({flag = false, startTime = Date.now()}) => { 4 const clear = () => { 5 clearTimeout(loopId); 6 btn.removeEventListener("mousedown", down); 7 btn.removeEventListener("mouseup", up); 8 } 9 const loop = () => { 10 clear(); 11 if (flag === true) 12 console.log('red'); 13 else 14 console.log('blue'); 15 main({flag}); 16 } 17 const down = () => { 18 clear(); 19 main({flag: true, startTime}); 20 }; 21 const up = () => { 22 clear(); 23 main({flag: false, startTime}); 24 }; 25 const loopId = setTimeout(loop, intervalTime + startTime - Date.now()); 26 btn.addEventListener("mousedown", down); 27 btn.addEventListener("mouseup", up); 28} 29main({});
CodePenで動作を確認
※ IEだとたぶん動かないです。最新ブラウザで見てください。
さて、これは何をしているのかというと、状態を再帰で渡すということをしています。再代入してはいけない、オブジェクトも変化してはいけないとなった場合、全てから見える何らかのフラグを作って、それをみんなで見に行くと言うことはできません。そこで、どうするかなのですが、フラグそのものは何個でも作成できます。つまり、フラグが入った状態をその都度作成し、次のイベントの待つ処理へと渡すというのがこの処理です。
Q: これって関数型プログラミングなの?
A: 関数型プログラミングの考え方のひとつです。ただ、このコードが純粋な関数型プログラミングというわけではありません。
Q: 純粋では無いと言うことは、このコードに副作用は含まれているということ?
A: はい、含まれています。cosole.log()
など副作用がある関数を使っているからです。完全に副作用をなくすには副作用がある部分をモナドで包み込むしかありません。しかし、JavaScriptはモナド操作が簡単にできるように作られていません。本当に純粋さを求めるなら、PureScript等を用いた方が良いでしょう。
Q: これ以外の方法は無いのですか?
もうひとつ有効な方法はFRPを使用することです。ただこちらもJavaScriptで標準で用意されているわけでは無いため、RxJS等のライブラリを使用する必要があるでしょう。
Q: とても面倒そうに思えます。本当にこんなことをしているのですか?
たぶん、**素のJavaScriptでは誰もしていません。**と言っても、状態を渡していくという考え自体が駄目というわけではありません。例えば、Reactのstateも(新たなstateを次々に渡していくという)大まかな考え方は一緒です。stateをまとめて管理できるようにしたのがReduxですから、同じく、状態を渡していっているだけとも言えます。つまり、関数型プログラミングを意識しているライブラリの中には、このような考え方を採用して作っていると言うことです。そして、それらのライブラリを使う場合は、それほど面倒なことをしなくてもすむようになっています。
Q: 結局どうしたら良いの?
まずもって、JavaScriptは関数型プログラミングがしやすい言語というわけではありません。部分的に関数型プログラミングの考え方を取り入れてはいますが、全てにおいて簡単にできるという話ではないのです。特に、こういった状態変化についても関数型プログラミングで実装したいのであれば、RxJSのようなライブラリやPureScriptのようなaltJSが必要になるでしょう。
Q: つまり、const
にしろとか言っているのは駄目って事?
そうではありません。関数型プログラミングにしない場合でもconst
を用いた方が良いでしょう。私なら、クラスを作って、状態を一つのオブジェクトに持たせて、それを変化させるようにします。変化可能なデータが独立して一個一個存在すると、どこでどのような副作用が発生するのか、制御が難しくなるからです。
QA部分は特にあやしいので、ツッコミをお待ちしております。
投稿2017/09/22 11:42
編集2017/09/22 11:45総合スコア21733
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/09/25 12:47
2017/09/25 13:12 編集
0
ぶっちゃけconst単体だと不便なだけ。
関数型プログラミングとはなんぞやのジャンルの話になってくるよ。
表題の件はグローバルに近い概念の共通変数を使ってどうのこうのやってるから、
これの代替はObserverパターンを使うくらいしか無い。
Redux、MobX、EventEmitterあたりが参考になるかな。
でも実態は共通のグローバル変数(連想配列)を1個作って、ここに全ての副作用を凝縮するという解決法だから表題とやってる事は一緒。
JavaScriptである以上、グローバル変数を作らずスマートに解決する方法は多分ない。
関数型プログラミング言語は手続き型プログラミング言語とは違って、
単純に状態変数作らなくても綺麗に設計して作れますよってだけの話。
その関数型の考え方の一つである、参照透過があるかつ副作用のない関数を作るようにしたら手続き型でもバグが少なくプログラミング出来るじゃんすげー!!…という風に密かに盛り上がってる。
constで再代入禁止ってのは関数型の考えを手続き型へ輸入してくる際の制約。
参照透過で副作用のない関数を作る一貫で、constで再代入しませんよ宣言した変数を使っておくと、
後から見たエンジニアが「ああ、この関数の中身は関数型プログラミングの思想で作られているんだね、使い勝手が良いね」って思ってくれる。
これがメリット。
要するに関数型プログラミングとはなんぞやってのを勉強していかないと、
何故constを使うべきか、constを活用したコードが美しいか理解できないし説明できない。
JavaScript関数型プログラミング 複雑性を抑える発想と実践法を学ぶという書籍がとても勉強になった。
関数型プログラミングは中々難解だから積んじゃう可能性もあるから、「関数型プログラミング」みたいなワードでどんな考え方かざっと調べてみると捗ると思うよ。
投稿2017/09/22 01:11
編集2017/09/22 01:31総合スコア21158
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
無理やり考えた上に試してませんが、
RxJSを使って、
タッチしたらtrue、終わったらfalseを流すストリームと
loop()が走るごとにユニークな値(別に前回と違いさえすればユニークである必要はないが)
を流すストリームをCombineLatestして
loop()のストリームの方をキーにしてdistinctUntilChangedしたストリームを購読すれば、
ただ関数チェーンを一回実行しているようにしか見えないので
見た目再代入していないコードが書けます。
RxJS内で再代入が起こっているとか言うツッコミは無しで。
js
1touch -T-----F-----T-----F---------------> 2loop ----1-----2-----3-----4----5----6---> 3 combineLatest 4 ----T1-F1-F2-T2-T3-F3-F4---F5---F6--> 5 distinctUntilChanged(x=>x.loop) 6 ----T1----F2----T3----F4---F5---F6--> 7 map(x=>x.touch ? 'red' : 'blue') 8 ----R-----B-----R-----B----B----B---> 9このBの連続がいらないならtouchをloopでsample()した方がすっきり 10
(シンタックスなしだと見づらかったのでjs指定しました)
投稿2017/09/22 00:39
編集2017/09/22 00:40総合スコア13512
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/09/22 00:56
2017/09/22 01:05
0
フラグ変数
まず、フラグ変数は出来る限り使いません。
フラグとは何らかの対象の状態を表すものですが、その状態を表すオブジェクトのプロパティ等で密接に結びついている実装がより有用だからです。
質問文の書き方ですと、スコープ上に存在する全ての関数でフラグが共有されるわけですが、isTouching
はなにがしかのDOMノードに結び付いた状態なので、ノードに結び付くべき情報であると考えます。
一番簡単なのは、touch される対象となる要素ノードの data-*
属性に埋め込むことです。
JavaScript
1document.querySelector('#target').setAttribute('data-touching', 'true');
もう一つは、WeakMap
に埋め込む方法です。
JavaScript
1var touchingMap = new WeakMap([[document.querySelector('#target'), true]])
もしくは、独自のクラスを作って抽象化します。
JavaScript
1class Foo { 2 constructor (element) { 3 this.target = element; 4 this.isTouching = false; 5 } 6}
handleEvent
addEventListener
の第二引数で指定する listener にはオブジェクトを指定し、データを共有する事が出来ます。
JavaScript
1var listener = { 2 isTouching: false, 3 handleEvent: function handleEvent (event) { 4 switch (event.type) { 5 case 'touchstart': 6 this.isTouching = true; 7 break; 8 case 'touchend': 9 this.isTouching = false; 10 break; 11 } 12 } 13} 14 15addEventListener('touchstart', listener, false); 16addEventListener('touchend', listener, false);
同じlistenerオブジェクトを複数のイベントハンドラとして定義する事は可能なので、イベントハンドラ間でデータの共有が出来るわけですが、listener オブジェクトを他に渡せば、更に共有範囲は広がります。
これは class と組み合わせる事で実に上手く機能します。
class 内に handleEvent
プロパティが存在したなら、this
値をそのまま listener オブジェクトに指定する事でイベントハンドラと class 内のプロパティを共有できます。
Re: terutaka-kondo さん
投稿2017/09/22 14:04
総合スコア18156
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
おそらく「できるならすべてconstにしたほうがよい」という方針は、
- 再代入をしないつもりの変数は、後で変更される可能性が無いと判るようにconstで定義すべきである
(後で中身が変化する心配を排除できるため、その方がコードが読みやすい)
(コーディングミスで内容を意図せず変えてしまうというバグを排除できる)
- プログラム上で現れる変数の大半は、再代入をしないつもりの変数である
という意味です。
理想的なコーディングというものを考えた場合でも、isTouchingは再代入されることを意図した変数なのでletで書くべきだと思います。
(無理やり再代入禁止にする実装を書けたとしても、それは曲芸の類ではないかと)
投稿2017/09/22 09:41
編集2017/09/22 09:42総合スコア4524
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。