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

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

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

ECMAScriptとは、JavaScript類の標準を定めるために作られたスクリプト言語です。

JavaScript

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

Q&A

7回答

1169閲覧

JavaScriptで再代入をできるだけしない方針の中で、フラグのオンオフの実装はどうするべきでしょうか。

d415uk35470

総合スコア45

ECMAScript

ECMAScriptとは、JavaScript類の標準を定めるために作られたスクリプト言語です。

JavaScript

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

0グッド

2クリップ

投稿2017/09/21 23:01

編集2022/01/12 10:55

###前提・実現したいこと

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ページで確認できます。

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

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

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

masaya_ohashi

2017/09/21 23:07

isTouchingというフラグを上げたり下げたりしていますが、loopではflagという謎の値を見ています。これはただの間違いですか?
退会済みユーザー

退会済みユーザー

2017/09/21 23:12

ie8(または互換)では動かないからconstは使わない。(
d415uk35470

2017/09/21 23:14

masaya_ohashiさま ご指摘ありがとうございます。ミスなので修正しました。
d415uk35470

2017/09/21 23:14

asahina1979さま アドバイスありがとうございます。
guest

回答7

0

JavaScriptという環境を考えれば、

  • オブジェクトをconstにして、中身を書き換える
  • DOMツリーに$().dataのような手法でぶら下げる

といった、トンチ的な解決策でも取らない限り、「状態を変数に保管せずに進める」というのは現実的ではありません

というのも、Haskellのような純粋関数型言語(変数の再代入どころか、破壊的な変更も存在しない)で状態を表現する場合、「『前の状態』を引数として、『次の状態』を返す」という方法を取って、コード上からは状態依存を追い出しているのです。

一方でJavaScriptの場合、イベントハンドラの実行時には、クロージャで生き残った変数以外リセットされてしまって、「前の状態を渡す」ということができないので、状態を引き継ぐにはどこかで(破壊的な方法で)保存しておいたものを読み取るほかないのです。

投稿2017/09/22 00:24

編集2017/09/22 00:24
maisumakun

総合スコア145062

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

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

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

というか、letにすべきでは?
guest

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);

CodePenで動作を確認

上のコードを、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
raccy

総合スコア21733

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

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

moredeep

2017/09/25 12:47

「クラスを作って、状態を一つのオブジェクトに持たせて」とは、状態を詰め込んだだけの意味を持たないクラスもどきではなく、意味のあるクラスを作るという意味ですよね?「クラス作ればいいんでしょ?」と言わんばかりの、ただグローバル変数として使いたいものを詰め込んだだけのクラスもどきを時々見かけてげんなりすることがあります。。。
raccy

2017/09/25 13:12 編集

> moredeepさん 意味があるクラスという解釈で問題ないです。全てconstにするためにクラスを作るのではなくて、状態を管理するためにクラスを作ると言うことです。「全てconstにする」という目的では無く、そういう設計をした結果、「全てconstになっていた」と言うべきでしょうか。ただ、もっと突き詰めていくと、状態を一括管理する何か、たとえばReduxのようなライブラリにいきついて、結局関数型プログラミングもどきになっていくのではないのかなとも思っていたりしています。
guest

0

ぶっちゃけconst単体だと不便なだけ。
関数型プログラミングとはなんぞやのジャンルの話になってくるよ。


表題の件はグローバルに近い概念の共通変数を使ってどうのこうのやってるから、
これの代替はObserverパターンを使うくらいしか無い。

Redux、MobX、EventEmitterあたりが参考になるかな。
でも実態は共通のグローバル変数(連想配列)を1個作って、ここに全ての副作用を凝縮するという解決法だから表題とやってる事は一緒。
JavaScriptである以上、グローバル変数を作らずスマートに解決する方法は多分ない。


関数型プログラミング言語は手続き型プログラミング言語とは違って、
単純に状態変数作らなくても綺麗に設計して作れますよってだけの話。

その関数型の考え方の一つである、参照透過があるかつ副作用のない関数を作るようにしたら手続き型でもバグが少なくプログラミング出来るじゃんすげー!!…という風に密かに盛り上がってる。
constで再代入禁止ってのは関数型の考えを手続き型へ輸入してくる際の制約。

参照透過で副作用のない関数を作る一貫で、constで再代入しませんよ宣言した変数を使っておくと、
後から見たエンジニアが「ああ、この関数の中身は関数型プログラミングの思想で作られているんだね、使い勝手が良いね」って思ってくれる。
これがメリット。

要するに関数型プログラミングとはなんぞやってのを勉強していかないと、
何故constを使うべきか、constを活用したコードが美しいか理解できないし説明できない。


JavaScript関数型プログラミング 複雑性を抑える発想と実践法を学ぶという書籍がとても勉強になった。
関数型プログラミングは中々難解だから積んじゃう可能性もあるから、「関数型プログラミング」みたいなワードでどんな考え方かざっと調べてみると捗ると思うよ。

投稿2017/09/22 01:11

編集2017/09/22 01:31
miyabi-sun

総合スコア21145

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

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

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
ozwk

総合スコア13512

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

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

maisumakun

2017/09/22 00:56

自分が例に出したHaskellでも、最終的には手続き型のCPUで実行されるわけなので、どこかで再代入する形への変換が行われている、という意味では同じものがありますね。
ozwk

2017/09/22 01:05

結局のところ再代入しないと嬉しいのは人間の都合ですからね 裏がどうなっているかはあまり関係ないかもしれないです。
guest

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

think49

総合スコア18156

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

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

0

おそらく「できるならすべてconstにしたほうがよい」という方針は、

  • 再代入をしないつもりの変数は、後で変更される可能性が無いと判るようにconstで定義すべきである

(後で中身が変化する心配を排除できるため、その方がコードが読みやすい)
(コーディングミスで内容を意図せず変えてしまうというバグを排除できる)

  • プログラム上で現れる変数の大半は、再代入をしないつもりの変数である

という意味です。

理想的なコーディングというものを考えた場合でも、isTouchingは再代入されることを意図した変数なのでletで書くべきだと思います。
(無理やり再代入禁止にする実装を書けたとしても、それは曲芸の類ではないかと)

投稿2017/09/22 09:41

編集2017/09/22 09:42
ku__ra__ge

総合スコア4524

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.51%

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

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

質問する

関連した質問