白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

未使用のラベル(unused label)と返ってこない値

こんにちは。
最近の太巻きはだんだん豪華になって太くなっていますが、切らずに無言で食べるにはもはや苦行なんじゃないかなと思います。

さて、罠を踏んだので備忘録。
わかるひとはタイトル見たら「あー、あれね」ってわかりそう。

先に結論

おそらくこういうコードを書いていると思うので、

const func = () => { hoge: "fuga" };

こうしましょうというお話です。

const func = () => ({ hoge: "fuga" });

なんでこんなことになるのかをちょっと見ていきます。

関数の宣言の仕方あれこれ

まずJavaScriptには関数定義の仕方がいろいろあるのでそれを見ていきます。

関数宣言

一番一般的なのがこれですかね。

function sum(a, b) {
    return a + b;
}

functionキーワードに続いて関数の名前を書くタイプです。TypeScriptだと型も書けます。

function sum(a: number, b number) : number {
    return a + b;
}
関数式

関数宣言は文ですが、関数は式にもできます。関数を第一級オブジェクトとして扱える言語だとよくあるやつですね。

const func1 = function sum(a, b) {
    return a + b;
};

ちなみに名前は省略できます。名前のない関数は無名関数とか匿名関数と言います。

const func2 = function (a, b) {
    return a + b;
};

というか式としては普通は省略する気がします。名前つけるとしたら再帰呼び出しするときとかですかね。自分の名前がわからないので。

console.log(func1.name);  // sum
console.log(func2.name);  // func2

ちなみに無名関数のまま即時実行もできて、こういう時は即時実行関数とか呼ばれる気がします。

const sum = (function (a, b) {
    return a + b;
})(1, 2);
アロー関数

いわゆるラムダ式とか呼ばれるあれです。ちなみにJavaのアロー演算子は「->」でJavaScriptは「=>」。

const sum = (a, b) => {
    return a + b;
};

返す値が1つの時にはカッコが省略できて、直接戻り値が書けます。
戻り値がなくて処理だけする場合には素直にカッコを書きましょう。

const sum = (a, b) => a + b;

引数が一つの時には引数のカッコも省略できますが、複数だったり引数なしの場合は省略できません。

const increment = a => a + 1;

オブジェクトの作り方あれこれ

次にオブジェクトの作り方を確認してみます。

Objectクラス

一番基本の形です。(まぁリテラル使うのであんまり使わないですが…)
ここでのtypeとかnameはプロパティになります。

const obj = new Object();
obj.type = "猫";
obj.name = "たま";
console.log(obj);  // { type: '猫', name: 'たま' }

ちなみにプリミティブ型はオブジェクト扱いできませんが、関数型はオブジェクトなのでプロパティが普通に使えます。
余談ですが、ES6まではclassという文法自体がJavaScriptになかったのでfunctionを使ってクラスの表現をしていたのでまぁそういうことです。

const num = 3;
num.prop = "さん";
console.log(num.prop);  // undefined

const func = () => {};
num.prop = "かんすう";
console.log(func.prop);  // かんすう
オブジェクトリテラル

実はnew Object()の代わりに{}を使うと簡単にオブジェクトが作れます。

const obj = {};
obj.type = "猫";
obj.name = "たま";
console.log(obj);  // { type: '猫', name: 'たま' }

で、そのまま初期化もできちゃいます。ので、だいたいこっちを使いますね。

const obj = {
    type: "犬",
    name: "ぽち"
};
console.log(obj); // { type: '犬', name: 'ぽち' }

TypeScriptでもclassのコンストラクタを使って初期化とかせずに、型推論やasを使ってtypeやinterfaceに変換するとかありがちです。

type Animal = { 
    type: "猫" | "犬"; 
    name: string; 
};
const cat: Animal = {
    type: "猫",
    name: "たま"
};
const dog = {
    type: "犬",
    name: "ぽち"
} as Animal;

ラベル

めったに使わない機能としてラベルというものがあります。
goto構文を普通に使うような時代のものなので、現代のプログラミングにおいては積極的に使うべきものではないとは思います。
ちなみにJavaScriptにgotoはないので、breakとcontinueに使えます。
なんかこんな感じ。正直使わないのでよくわからないです…。

let cnt = 0;
outer: for (let i = 0; i < 100; i++) {
    inner: for (let j = 0; j < 100; j++) {
        cnt++;
        if (cnt % 10 === 0) {
            continue outer;
        }
    }
}
console.log(cnt); // 1000

breakだとこう。

let cnt = 0;
outer: for (let i = 0; i < 100; i++) {
    inner: for (let j = 0; j < 100; j++) {
        cnt++;
        if (cnt % 10 === 0) {
            break outer;
        }
    }
}
console.log(cnt); // 10

こんなこともできる。うれしくないけど。

hoge: {
    console.log(1);
    break hoge;
    console.log(2); // ここは呼ばれない
}
console.log(3);

で、なんで?

長くなってしまいましたが最初のコードに戻ります。

const func = () => { hoge: "fuga" };

これはアロー関数を使ってオブジェクトリテラルを返そうとしているコードです。
返却したい値が一つだけなのでカッコとreturnを省略しました。

が、残念なことにオブジェクトリテラルのカッコとアロー関数のカッコが同じ「{}」を利用しています。
そしてややこしいことにラベルという機能があるせいで「hoge: "fuga"」がエラーにならずに解釈することができます。
(これ単品でのラベルってどうやって使うんでしょうか…?正直よくわかりません…。)

するとなんとhogeというラベルを定義した謎の関数ができあがり、当然これは何の戻り値も返しません。(undefined)
なので、よく見るとIDEが「未使用のラベル」とかいう不思議な警告を出すわけです。

const func = () => { 
    hoge:    // 特に意味のないラベル
        "fuga"  // 特に意味のない通りすがりの文字列
    // なにもreturnしていない
};

というわけでこれはオブジェクトリテラルだよってことを明示するためにカッコをつけてあげます。

const func = () => ({ hoge: "fuga" });

整理すると難しいね

なんか思ったより長くなってしまった。
JavaScriptって融通が利くのでこういう不思議なことがたまに起こる気がします。
なんか知らんけどカッコつけとこうじゃなくてちゃんと理解できると次回から間違えなくて済みそうです。

それでは。