l08084のブログ

プログラミングについて書きます

forEachループの途中で要素を削除した場合のメモ

はじめに

forEachでループを回している配列の要素をループの途中で削除した時に、(あくまで自分からみるとの話です)よくわからない挙動をしたので備忘録目的でまとめました。

developer.mozilla.org

ループの途中で要素を削除した場合についての、👆MDNのサイトで記載されている部分の引用です。

forEach によって処理される配列要素の範囲は、callback が最初に呼び出される前に設定されます。forEach の呼び出しが開始された後に追加された配列要素に対しては、callback は実行されません。既存の配列要素が変更または削除された場合、callback に渡される値は forEach がそれらを参照した時点での値になります。削除された配列要素を参照することはありません。

👆上記引用文ですが、渡された時点の配列要素の範囲でループを回すので、ループの途中で配列を追加したり、削除してもループ回数とループで渡す要素には影響ないよ、という意味だと自分は解釈しています。

検証プログラム

let eventA = {
    title: '遠征',
    start: '4/10'
}
let eventB = {
    title: '練習試合',
    start: '4/10'
}
let eventC = {
    title: '留学',
    start: '4/11'
}
let eventD = {
    title: '旅行',
    start: '4/10'
}
let eventList = [eventA, eventB, eventC, eventD];
let convertedList = [];

eventList.forEach((event, index, array) => {
    // startが同じeventの配列を作成する
    let filteredArray = eventList.filter((element) => {
        return (element.start === event.start);
    });

    // startが同じeventの件数をtitleに代入する
    event.title = filteredArray.length;

    // 件数をカウントしたeventをeventListから削除する
    eventList = eventList.filter((element) => {
        return (event.start !== element.start);
    });
    convertedList.push(event);
});

console.log(convertedList);
/*  実行結果
    Array(4) [Object, Object, Object, Object]
    length:4
    __proto__:Array(0) [, …]
    0:Object {title: 3, start: "4/10"}
    1:Object {title: 0, start: "4/10"}
    2:Object {title: 1, start: "4/11"}
    3:Object {title: 0, start: "4/10"}
*/

上記のソースコードですが、開始(start)が同じ日付のeventオブジェクトが複数件あった場合、一つにまとめて、まとめたeventオブジェクトの件名(title)に開始が同じ日付のeventの件数を設定するということをしています。 上記プログラムを動かす前は、開始(start)が4/10のイベントが3件、開始(start)が4/11のイベントが1件あるので、下記の通り表示される認識でしたが、

    Array(2) [Object, Object]
    length:4
    __proto__:Array(0) [, …]
    0:Object {title: 3, start: "4/10"}
    2:Object {title: 1, start: "4/11"}

実際には、次の通り表示されました。

    Array(4) [Object, Object, Object, Object]
    length:4
    __proto__:Array(0) [, …]
    0:Object {title: 3, start: "4/10"}
    1:Object {title: 0, start: "4/10"}
    2:Object {title: 1, start: "4/11"}
    3:Object {title: 0, start: "4/10"}

一度、開始(start)が同じだと判断してまとめたeventについては、次の部分で削除しているので、

// 件数をカウントしたeventをeventListから削除する
eventList = eventList.filter((element) => {
    return (event.start !== element.start);
});

titleが0のeventは出力されない認識でしたが、下記2つの要因が原因で上記の通り出力されたようです。

  • MDNのサイトに記載されている通り、ループの途中で要素を削除しても、ループの範囲とループで渡される要素には、影響がない

  • forEachで回しているeventList配列と、内部から参照しているeventList配列が別々の参照になっているため、forEachで回しているeventList配列では、要素が削除されていないが、内部から参照しているeventList配列では、要素が削除されいる扱いになっている(4回目のループでは、forEachで回しているeventList配列の要素数は4件だが、内部から参照しているeventList配列の要素数は2件になっていると思われる)

スコープについての理解が曖昧なせいか(?)原理がよくわからない…そもそもループの途中で要素を削除するようなプログラムを書くこと自体が間違っているのか?それともforEach以外を使うべきだったのか…という感じです。

少し変わった(?)配列の参照方法

return ['pig', 'fox', 'bird', 'fish', 'mouse'][3]; // fish

上記の文を見たとき一瞬意味がわからなかったんですが、単純に定義した配列に添字の3を指定して値を呼び出しているだけですね。 かっこいい書き方だと思ったので、今後自分のコードにも積極的に使っていく所存です。

// m以上n以下のランダムな数値を返す
function getRandom(m, n) {
    return m + Math.floor((n - m + 1) * Math.random());
}

// いずれかの動物を表す文字列をランダムに返す
function getRandomAnimal() {
    return ['pig', 'fox', 'bird', 'fish', 'mouse'][getRandom(0, 4)];
}

console.log(getRandomAnimal()); // ex. pig

parseIntとparseFloatは数字以外の情報を全て無視するって知らなかった

知りませんでした。まあ、初めの文字が数値以外だった場合は、NaNになってしまうんですが。

const weight = '60kg';
const height = '172.5cm';

console.log(parseInt(weight, 10)); // 60
console.log(parseFloat(height, 10)); // 172.5

ブラウザの開発者ツールによるAndroid/iOSアプリのデバッグ方法

はじめに

ブラウザの開発者ツールによるAndroid/iOSモバイルアプリのデバッグ方法について書きます。

開発環境

  • ブラウザ: Google Chrome (バージョン 60.0.3)、Safari (バージョン 9.1.3)
  • OS: OS X EI Capitan (バージョン 10.11.6)

Android

  1. デバッグしたいアプリを起動した状態でAndroid端末をPCに接続
  2. Chromeの開発者ツールを開く
  3. 開発者ツールのメニューから[More tools] -> [Remote devices]を選択

f:id:l08084:20170902231132p:plain

4.Remote devicesタブから、デバッグしたいアプリを選択

f:id:l08084:20170902231652p:plain

f:id:l08084:20170902231706p:plain

iOS

  1. デバッグしたいアプリを起動した状態でiOS端末をPCに接続
  2. Safariのメニューから[開発] を選択
  3. デバッグ対象のアプリを選択する(下のキャプチャーでは、アプリを起動していないので何も表示されない)

f:id:l08084:20170902233226p:plain

なんて検索していいかわからなかったJavaScriptの記法メモ

はじめに

JavaScriptのコードを見ていて、この書き方どういう意味かわからないけど、なんてキーワードで検索していいかわからない…みたいなことがあったので、検索しづらいと思ったイディオムをまとめた。

検索に困った文法

ショートサーキット

a || baが真であれば、bの評価がスキップされるという特性があるため、下記の例のような簡略化した値の代入を実施できる。

let fuga;
let hoge = fuga || 45; // ショートサーキット。fugaがfalseであれば、45が代入される
console.log(hoge); // fugaがundefinedなので45が表示される
fuga = 5;
hoge = fuga || 45; // ショートサーキット。fugaがfalseであれば、45が代入される
console.log(hoge); // fugaに値が代入されていたため、今度は5が表示される

スプレッド構文

スプレッド構文を使うと、1つの配列を複数に展開することができるため、ループを使用せずに、ある配列を他の配列に含めるといった処理が可能になる。

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
arr2.push(...arr1); // スプレッド構文。配列arr1の全要素を配列arr2の末尾に追加する
console.log(arr2); // [ 4, 5, 6, 1, 2, 3 ]と出力される

Angular + Typescriptをブラウザでデバッグしたい

はじめに

Angularプロジェクトをブラウザでデバッグする際に、TypescriptファイルにBreakPointを設定する方法がわからなかったので調べた。

環境

  • Angular: 2.4.7
  • angular-cli: 1.0.0-beta.28.3
  • webpack: 2.2.0
  • Google Chrome: 53.0.2785.116
  • 各種設定ファイルは、Angular CLIでプロジェクト作成後、デフォルト設定のまま
  • エディタはVisual Studio Code(1.7.2)を使用

デバッグ方法

  1. Angular CLIで雛形プロジェクトを作成した後、ng serveコマンドで開発ビルド&実行する
  2. Chromeのdeveloper toolsを起動する
  3. Sourcesタブを選択 f:id:l08084:20170213222308p:plain

webpack://配下にTypescriptファイルが配置されているのでBreakPointを設定することができる。

画像の例では、src/app/app.component.ts ファイルにBreakPointを設定している。

なぜ私のAngular CLIはカレントディレクトリにプロジェクトを作成してくれないのか?

Angularプロジェクトの雛形作成に、generator-angular2-typescriptを使っていたのですが、最近Angular CLIに移行しました。

表題の件

Angular CLIでは、ng new [プロジェクト名]コマンドでプロジェクトの雛形をカレントディレクトに作成してくれるんですが、なぜかカレントディレクトリではなくホームディレクトにしかプロジェクトを作成してくれない!という事態が発生しました。

開発環境

  • OS:OS X EI Capitan(10.11.6)
  • angular-cli: 1.0.0-beta.28.3
  • node: 6.2.1

解決

ググって出てきた下記stackoverflowの記事のおかげで解決しました。

stackoverflow.com

Do you have a package.json file in your home/user directory? I experienced this issue, then I removed package.json and that stopped it.

ホームディレクトリに置いてあるpackage.jsonが原因じゃない?package.jsonを削除すれば、カレントディレクトリにプロジェクトが作成されると思うよ〜みたいなことを言っていますね。

案の定ホームディレクトリにいつ作ったか覚えてないpackage.jsonが置いてあったので、削除したら無事カレントディレクトリにプロジェクトが作成されるようになりました。