ループの中でAjaxを行う時の注意点

[`evernote` not found]
[`livedoor` not found]
[`yahoo` not found]

JavaScriptのfor文内でAjaxを使った時に、少し予想と異なる挙動となったのでそのメモ。

まずは下記のように、順序リストをHTML中に指定。

その後、1.txt、2.txt、3.txtファイルの中の記述をそれぞれのファイル名の数値に対応するliタグの中に入れるという処理を行うことに(それぞれのテキストファイルは、『1です。』『2です。』『3です。』と書いてあります)。

というわけで、書いてみたのが以下のようなコード。

for文でカウンタ変数iを1~3までの整数をとるようにし、AjaxのURLを『i + ‘.txt’』(つまり、iが1なら”1.txt”)とし、Ajaxが成功した場合はolタグ内のi番目のliタグ内に取得したデータをいれるという流れとなっています。

実行サンプル:ループの中でのAjax

実行結果画像:
ajax-test1

予想では、それぞれのファイルの中身はファイル名に対応するliタグに入るはずが、すべて4番目のliタグ内に入ってしまっています。

これは考えてみたら当たり前のことで、Ajaxが成功した時に実行する関数は、for文を抜けた状態、つまりiが4となっているため4番目のliタグ内にAjaxで受け取ったデータが入ったわけです。このあたりは、JavaScriptのクロージャをちゃんと理解できていたら分かるとは思うのですが、ちょっと複雑なので自分も深くまでは理解できていません(参考:クロージャ – JavaScript | MDN)。

というわけで、先ほどのループ中でのAjaxの解決方法について。上記の場合、ループ用の変数のiと、Ajaxの成功時の関数の中のiが同じだったので起こった問題です。ということは、クロージャを一つ追加してループ用のiとAjaxを呼び出す時のiを別々のものと考えたいいのではないかと思って書いてみたのが以下。

ajaxの実行箇所を引数にiをつけた無名関数内にいれるようにしました。これで、ループ用のiとAjax内でのi(無名関数中でのi)は別々のものという扱いになりました。

実行サンプル:ループの中でのAjax

実行結果画像:
ajax-test2

うまくいったようです。

ちなみに、どちらも変数名をiとしてしまったので、ちょっと分かりにくかったかもしれないので少し変更すると、上記は下記のように書いたのと同じことです(無名関数内のiをjに変更)。

クロージャはうっかりするとハマりそうですね(もちろん、悪い状況という意味での『ハマる』)。この機能はメモリリークが起きるんじゃないかと思わせますし、実際調べてみるとそうらしいのですが、慣れれば便利そうです。

2015年9月5日追記:
コメントでいただいたとおり、今回の実装は順番の保証はしていません。
1. 2です。
2. 1です。
3. 3です。
と可能性もあります。

JavaScriptでフリックキーボードを作ってみた(再挑戦)

[`evernote` not found]
[`livedoor` not found]
[`yahoo` not found]

前に諦めたけど、再挑戦(前:HTMLでフリックキーボードを作ろうとして、mouseupとtouchendの挙動の違いを知った | while(isプログラマ))。

先日、HTML5デザイン 仕事のネタ帳という本を読んでいたら『document.elementFromPoint』という記述がありました。どうやら、引数に座標を指定することにより、その座標位置にある要素を返してくれるようです(参考:document.elementFromPoint – DOM | MDN)。上記の本では座標をウェブページ座標(pageX,pageY)としていたのですが、どうもクライアント座標(clientX,clientY)が正しそうです。ただ、ブラウザによって挙動が違うようなので、間違いというわけではないかもしれません(参考:要素が画面上に見えているかどうかを調べる – by edvakf in hatena)。

というわけで以下、作ってみたフリックキーボードのJavaScript部分とサンプルページのリンク

サンプルページ:フリックキーボード

23行目で、取得した要素が、touchendが発動した要素の自分自身を含む兄弟要素か確かめてます。濁点と半濁点の処理はちゃんとやってませんが、フリックキーボードらしくなってるかと。使いドコロとしては、例えば、スマホサイトにおいて、絶対ひらがなしか入力させたくない場面なんかにいいかもしれません。そんな例がどれぐらいあるか分かりませんが。

HTML5デザイン 仕事のネタ帳 CSS3+JavaScript+CSSフレームワークと活用するプロのテクニック
HTML5デザイン 仕事のネタ帳 CSS3+JavaScript+CSSフレームワークと活用するプロのテクニック

JavaScriptで複数ランダム値取得関数とビンゴゲームを作ってみた

[`evernote` not found]
[`livedoor` not found]
[`yahoo` not found]

ある意味、前回の続き(配列から重複なくランダムに複数の値を得る方法 | while(isプログラマ))。

配列から指定の数分、ランダムで値を取得する関数を作ってみました。

第一引数にオプションを、第二引数にランダムの値を得る元の配列をいれます。

第一引数のオプションの説明は下記のとおりです。

  • size:取得するランダム値の数。初期値は1
  • min:配列を指定しない場合に取得するランダム値の最小値。初期値は1
  • max:配列を指定しない場合に取得するランダム値の最大値。初期値は100
  • step:配列を指定しない場合に取得するランダム値取得用の数列の値の差。初期値は1

例:

ランダムの値を配列から複数取得するための関数ですが、sizeを0として、第二引数に長さが0の配列をいれると等差数列の配列を取得するということもできます。

最後の結果が想定と違う結果になってしまったのは、JavaScriptが浮動小数点型で計算しているためです。小数点の演算を扱うとたまに遭遇します(参考:参考:Javascriptで小数を含む計算を行うとき、計算結果の小数点2位以下を丸める。 | Scenee’s blog)。

さて、関数を作ってみただけではなんなのでその関数の実用的な(かどうかは分かりませんが)サンプルを作ってみました。
JavaScriptでビンゴゲーム

ビンゴゲームです(本来、一人で楽しむためのものではないですが・・・)。
例えばビンゴシートのそれぞれの列を埋めるのに今回作成した関数を使っています。例えば、最も左の列をランダムな値で埋めるのは以下のようにやっています。

writeBingoSeatは第一引数にランダム値を入れる列を表すjQueryオブジェクトを入れて第二引数に数値配列を入れるとビンゴシートに数値を埋めていくメソッドです。第二引数には最小値が1、最大値が15の範囲の整数を5つ取得した配列を入れています。

他には、ビンゴの番号を選ぶのにも今回作成した関数を使っています。例えば以下の様な感じ。

詳しく知りたい方はページのソースを見てみてください。

こういうのは本来オブジェクト指向で書いたほうがいいんでしょうけどね・・・。まだまだオブジェクト指向はよく分かってません・・・。上司がJavaScriptをオブジェクト指向で書くようになったので、自分もそろそろ本格的にオブジェクト指向を勉強したほうがよさそうなんですが・・・。

配列から重複なくランダムに複数の値を得る方法

[`evernote` not found]
[`livedoor` not found]
[`yahoo` not found]

カテゴリーはJavaScriptでコードもJavaScriptですが、どちらかというとアルゴリズム寄りの話です。たいがいのプログラミング言語では同様の方法で実装できる方法だと思います。

配列からランダムに複数の値を得たいということがあると思うのですが、普通にランダム関数でランダムの値を取得して、その配列のインデックスの値を取得しても重複してしまいます。
例えば下記のようなソースコード。

上記のコードは、arrという名前の配列からランダムに値を5つ取得してrandArrという配列に格納していくというプログラムですが、コンソールの結果は下記のようになりました。

randArrの中の値は重複せずに入っていることもありますが、かなりの確率で重複してしまいます。

じゃあ、重複せずにランダムで複数の値を取得するにはどうすればいいか考えてみると、最初に配列をシャッフルしてシャッフルした後の最初の5つを取得すれば重複せずに取得できます。ただ、全体をシャッフルしなきゃいけないので無駄が多そうです。なお、余談ですが、ランダムシャッフルはFisher-Yates法がよさそうです(参考:svartalfheim.jp – 配列を少ない仕事量でシャッフルするFisher-Yates法)。

ところで、よくよく考えたら複数の値を取得する配列が変更可能であれば、ランダムに取得した値はなくして大丈夫なわけです。ということは、ランダムで得た値の箇所をランダム値の上限のインデックス(1回目は配列の最後の要素)で置き換え、次に取得するランダム値の範囲を1減らせばランダムで取得する範囲はすでに取得した値以外になるわけです。
例えば、下記のようなサンプルコード。

コンソールには下記のように表示されました。

元々の配列がかなり変更することになりますが、重複なく値を取得できています。ループ回数も取得したい値の数(上記の例では5)だけですみました。

上記はJavaScript以外のプログラミング言語のことも考えて実装したアルゴリズムです。実は、JavaScriptだと配列の指定の箇所を取り除くspliceというメソッドがあるので、それを使うと上記のプログラムは下記のように実装できます(参考:Array.splice – JavaScript | MDN)。

コンソールの結果は下記のようになりました。

こちらだと元々の値を取得する配列は要素が減るだけなので、スッキリしています。もし、ランダム値を取得する順番をとわないのであれば、ランダムに値を減らした元々の配列に入っている値をランダムに取得した値と考えることもできそうです(例えば、100個の値が入っている配列からランダムに90個取得する場合、ループは90回でなく10回でいい)。

2014/05/13 23:39追記:spliceメソッドの戻り値は配列なので、0番目の値を入れるように指定しました。

jQueryで作成した要素をランダムに並び替えて表示する方法

[`evernote` not found]
[`livedoor` not found]
[`yahoo` not found]

jQueryで作成した複数の要素をランダムに並び替えて表示したいという状況に遭遇したのでいろいろ工夫してやってみました。

具体的にいうと、クイズコンテンツを作ろうとしたのですが、100問あるクイズはランダムに表示するようにしたいと思いました。最初にとりあえず100問分の要素を作成したうえでそれをランダムに並び替えて・・・。と思ったのですが、それだとかなり効率が悪そうな気がしました(そもそも、うまい実装方法が思いつかない)。

ただ、よくよく考えたら、まず最初に作ったのを一つおき、2つ目はその前後2つのうちどちらかランダムで挿入、3つ目は最初か1つ目の要素と2つ目の要素の間か最後の3つのうちどれかにランダムで挿入、n個目は挿入できるn個の場所のどれかにランダムで挿入・・・というようにしたらランダムで並び替えたことになるんじゃないかと思って実装してみました。
以下、実装サンプルです。久々にjsfiddleを使ってみました。

ただたんに、0~100の数値が書かれたdiv要素をランダムに並び替えて表示するプログラムです。多分、これで問題ないはずです。何度か試しましたが、偏ってなさそうでした。