関数の場合の変数の巻き上げ問題について

  • 59
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

一見正しく動作しないと思われるコードを見かける事があります。
不幸にもそのようなコードを保守しなければならなくなった際に、慌てないように理解しておかなければなりません。

よくある「変数の巻き上げ問題」について

次のコードを実行した際に、ReferenceError: x is not definedの例外が発生します。

console.log(x);

当たり前ですね。
しかし、次のコードを実行した際に、undefinedが表示されます。

console.log(x);
var x = 1;

このことから、xはvarの記述の位置に限らず次のように解釈されています。

var x;
console.log(x);
x = 1;

この事はjsの問題としてたびたび話題にあがる 変数の巻き上げ の説明です。

関数にもある同じ問題

では次はどのように動作するでしょうか?

console.log(y());
var y = function x() {
    return 1;
};

TypeError: undefined is not a functionと例外は発生します。
これも変数の巻き上げと同じ解釈が働いているので当たり前の動作ですね。
これならどうでしょうか?

console.log(x());
var y = function x() {
    return 1;
};

やはりTypeError: x is not a functionと例外は発生します。
xは関数名ではありますが、変数ではないので参照する事もできません。

では次の場合はどのように動作するでしょうか?

console.log(x());
function x() {
    return 1;
}

実際に動作させると1が表示されると思います。
ということは次と同じとなります

var x = function x() {
    return 1;
}
console.log(x());

このことから次の事が分かります。

  • function x(){}と記述した場合は、xは変数になるうえに、先頭で宣言と定義が一緒にされたものと同じである。
  • var y = function x(){}とした場合は、xは変数にならないのでxを参照できない。
  • 関数を変数に代入するだけで動作が変わる!
  • varを使用しなくとも変数の巻き上げは存在するが、値がundefinedではない場合がある

投稿するほどのトピックではないと思うかもしれません。

しかし問題は次のようなコードに遭遇したときです。

var z = new Class1;

// ~~~ 間に多くの行 ~~~

function Class1 () {
};

前半部分だけみると、このClass1とはなんだ?ってなりがちです。
Class1が一般的な名称であればあるほど、読み込んでいる汎用ライブラリや言語仕様で定義されているのかな?と 勘違い してしまう可能性があります。
しかし、実際はソースコードの後ろのほうで定義されています。
(なぜかnode.js界隈では、この形式の記述が多くみられます。)

変数では気をつけていればすぐ分かる事もクラス(関数)だと分かりにくくなります。それはvarを使用しなくていい事とundefinedではない事によるものです。

jsにはクラスはありませんがここでは便宜的にクラスという名称を使用しています

保守や参考にしようとする人にとっては、上から順番にソースを読めば良いという(大抵の言語なら)決まっているルールから外れているため、無駄に時間を取られてしまいます。

しかし、関数も変数の巻き上げ問題がある事を、認識している必要があります

もし自分がモジュール開発の立場で、他人がコードをみる可能性がある場合は、関数は必ず変数に代入し使用する前に定義した方が良いでしょう。
これにより、js初心者に理解しにくい「不自然に見えるコード」ではなくなります。