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

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

ただいまの
回答率

90.61%

  • JavaScript

    15899questions

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

  • ECMAScript

    117questions

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

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

受付中

回答 7

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 840

d415uk35470

score 37

前提・実現したいこと

JavaScriptをECMAScript2015のconstを積極的に使いつつ、フラグをオンオフするような実装をしたい。

発生している問題・エラーメッセージ

実現したいことはそれなのですが、実際どう実装するのがよいのかよくわかりません。
定数とか再代入とかを特に気にしなければごく単純なコードです。

該当のソースコード

たとえばまず定数とか再代入とかを特に気にせずに一例を書きます。

var isTouching = false;

// 例えばprocessingのようにこの関数が繰り返し呼ばれるとします。
function loop(){
  if(isTouching===true) console.log('red');
  else console.log('blue');
}

// タッチ開始時にこの関数が呼ばれるとします。
function ontouchstart(){
  isTouching = true;
}

// タッチ終了時にこの関数が呼ばれるとします。
function ontouchend(){
  isTouching = false;
}

なんのことはないコードです。

ただ、「できるならすべてconstにしたほうがよい」と聞いて、できるだけconstを使うように書く練習をしているのですが、こういう時はどうするのが良いのだろう?とわからなくなったので質問しました。

補足情報

補足1. JavaScriptとはいいつつ再代入をできるだけせずにどうやるのかという一般的な話しでもあると思います。(ただし言語機能がそれぞれ違うのでできることできないことはあるとおもいます。)

補足2. もちろん「現実的にはそんなこと気にせずletを使いなさい」とかもあるとは思いますが、
今回の質問はいったん現実的にはというのをおいておいて、再代入禁止のルールだとどうするのが良いのか、知りたいです。
また、そうすることで、その再代入禁止のルールを守るのがこのケースでは大変なものなのかどうかを知りたいです。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • masaya_ohashi

    2017/09/22 08:07

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

    キャンセル

  • asahina1979

    2017/09/22 08:12

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

    キャンセル

  • d415uk35470

    2017/09/22 08:14

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

    キャンセル

  • d415uk35470

    2017/09/22 08:14

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

    キャンセル

回答 7

+7

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

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

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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+5

状態が変わるフラグな訳ですから普通にletで良いのでは?

私の過去質問も参考になると思います。
https://teratail.com/questions/89800
https://teratail.com/questions/91873

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/22 08:58

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

    キャンセル

  • 2017/09/22 09:01

    私の過去質問も参考になると思います。
    https://teratail.com/questions/89800
    https://teratail.com/questions/91873

    キャンセル

+4

挑戦してみました。

まずは質問のコードについてですが、<button id="btn">ボタン</button>というものだけ用意してボタンを押している間、離したらと言うことで、次のようなコードにしました。(タッチはタッチパネルが無いとテストできないので)

var intervalTime = 1000;
var btn = document.getElementById("btn");
var flag = false;
function loop() {
  if (flag === true)
    console.log('red');
  else
    console.log('blue');
}
function down() {
  flag = true;
}
function up() {
  flag = false;
}
setInterval(loop, intervalTime);
btn.addEventListener("mousedown", down);
btn.addEventListener("mouseup", up);

CodePenで動作を確認

上のコードを、varは使用禁止(ローカル変数はconstのみ)、変数及びプロパティへの再代入を禁止、ArrayやObject等を作成して変化させていくのも禁止、という条件のもとにES2015+で書き直したのがこちらです。

const intervalTime = 1000;
const btn = document.getElementById("btn");
const main = ({flag = false, startTime = Date.now()}) => {
  const clear = () => {
    clearTimeout(loopId);
    btn.removeEventListener("mousedown", down);
    btn.removeEventListener("mouseup", up);
  }
  const loop = () => {
    clear();
    if (flag === true)
      console.log('red');
    else
      console.log('blue');
    main({flag});
  }
  const down = () => {
    clear();
    main({flag: true, startTime});
  };
  const up = () => {
    clear();
    main({flag: false, startTime});
  };
  const loopId = setTimeout(loop, intervalTime + startTime - Date.now());
  btn.addEventListener("mousedown", down);
  btn.addEventListener("mouseup", up);
}
main({});

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/25 21:47

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

    キャンセル

  • 2017/09/25 22:12 編集

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

    キャンセル

+3

無理やり考えた上に試してませんが、

RxJSを使って、

タッチしたらtrue、終わったらfalseを流すストリームと
loop()が走るごとにユニークな値(別に前回と違いさえすればユニークである必要はないが)
を流すストリームをCombineLatestして
loop()のストリームの方をキーにしてdistinctUntilChangedしたストリームを購読すれば、
ただ関数チェーンを一回実行しているようにしか見えないので
見た目再代入していないコードが書けます。

RxJS内で再代入が起こっているとか言うツッコミは無しで。

touch -T-----F-----T-----F--------------->
loop  ----1-----2-----3-----4----5----6--->
               combineLatest
      ----T1-F1-F2-T2-T3-F3-F4---F5---F6-->
               distinctUntilChanged(x=>x.loop)
      ----T1----F2----T3----F4---F5---F6-->
               map(x=>x.touch ? 'red' : 'blue')
      ----R-----B-----R-----B----B----B--->
このBの連続がいらないならtouchをloopでsample()した方がすっきり


(シンタックスなしだと見づらかったのでjs指定しました)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/22 09:56

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

    キャンセル

  • 2017/09/22 10:05

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

    キャンセル

+3

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


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

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


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

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

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

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


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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+2

 フラグ変数

まず、フラグ変数は出来る限り使いません。
フラグとは何らかの対象の状態を表すものですが、その状態を表すオブジェクトのプロパティ等で密接に結びついている実装がより有用だからです。

質問文の書き方ですと、スコープ上に存在する全ての関数でフラグが共有されるわけですが、isTouching はなにがしかのDOMノードに結び付いた状態なので、ノードに結び付くべき情報であると考えます。
一番簡単なのは、touch される対象となる要素ノードの data-* 属性に埋め込むことです。

document.querySelector('#target').setAttribute('data-touching', 'true');

もう一つは、WeakMap に埋め込む方法です。

var touchingMap = new WeakMap([[document.querySelector('#target'), true]])

もしくは、独自のクラスを作って抽象化します。

class Foo {
  constructor (element) {
    this.target = element;
    this.isTouching = false;
  }
}

 handleEvent

addEventListener の第二引数で指定する listener にはオブジェクトを指定し、データを共有する事が出来ます。

var listener = {
  isTouching: false,
  handleEvent: function handleEvent (event) {
    switch (event.type) {
      case 'touchstart':
        this.isTouching = true;
        break;
      case 'touchend':
        this.isTouching = false;
        break;
    }
  }
}

addEventListener('touchstart', listener, false);
addEventListener('touchend', listener, false);

同じlistenerオブジェクトを複数のイベントハンドラとして定義する事は可能なので、イベントハンドラ間でデータの共有が出来るわけですが、listener オブジェクトを他に渡せば、更に共有範囲は広がります。

これは class と組み合わせる事で実に上手く機能します。
class 内に handleEvent プロパティが存在したなら、this 値をそのまま listener オブジェクトに指定する事でイベントハンドラと class 内のプロパティを共有できます。

Re: terutaka-kondo さん

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

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

  • 再代入をしないつもりの変数は、後で変更される可能性が無いと判るようにconstで定義すべきである
    (後で中身が変化する心配を排除できるため、その方がコードが読みやすい)
    (コーディングミスで内容を意図せず変えてしまうというバグを排除できる)
  • プログラム上で現れる変数の大半は、再代入をしないつもりの変数である

という意味です。

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 90.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • JavaScript

    15899questions

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

  • ECMAScript

    117questions

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