LoginSignup
2
3

More than 1 year has passed since last update.

【巻き上げ、ホイスティング】Java Scriptでまだ関数を定義していないのに、先に呼び出せることが不思議だったので調べてみることに。

Posted at

まず、タイトルがクソすぎる。
なんか良いタイトルあればお願いいたします。

Hoisting (巻き上げ、ホイスティング)について語ります。

いきなりですが皆さんに問題

コード1

var a ={};
a.abc = test;
a.abc();
function test(){
  alert("test")
}

コード2

var a ={};
a.abc = test; 
a.abc();
test=function(){alert("test1");} 

この二つのコードどちらかがエラーを起こし、
どちらかはしっかりと動作します。

シンキングタイム、、、、、

答えはコード1がちゃんと動き、コード2はUncaught ReferenceError: test is not defined
とエラーが起こる

んー、なんか不思議。

要するに、無名関数は関数を呼び出す前に関数宣言をする必要がある。
無名関数の場合は先に関数宣言(どういった関数かを定義)
しないと、先に関数を呼び出してもエラーになります。

関数宣言してから、呼び出す方が自然な気がするが、なぜ
定義してない関数を呼び出してもうまく動作するのかが気になる。

改めて疑問点

https://teratail.com/questions/349716#
ここにも同じことを質問させていただきました。

catName("Chloe");

function catName(name) {
  console.log("My cat's name is " + name);
}

例えばこのコード、
自分の予想としては、定義してない関数を先に呼び出してるので


catName("Chloe");

を実行したら
undifined method catName
とかになると思ったのですが、

実行結果は、"My cat's name is Chloe"
と、コードは動作します。

なぜか

公式のリファレンスの説明では
https://developer.mozilla.org/ja/docs/Glossary/Hoisting

コード内で関数を書く前に、関数呼び出しを最初に書いても、コードは動作します。これは JavaScript でコンテキスト実行が動作するためです。

https://wp-p.info/tpl_rep.php?cat=js-intermediate&fl=r9
の説明では

これはなぜかというとJavaScriptでは『常に宣言はスコープの先頭で行われたことにする』という処理を裏側で行うせいだったりします。JavaScriptでのスコープは関数の中以外では存在しませんので、つまり『常に宣言は関数の中の先頭で行われたことにされる』ということになります。これが『ホイスティング』と呼ばれる現象です。

コンテキスト実行とは。。。??
とひよっこの自分は思ったので、調べてみると

まず「コンテキスト」と言う概念があるらしい

コンテキストとは?

https://teratail.com/questions/53738
同じことを疑問に思ってる方がいたので見てみることに。

「ソーステキスト内に書かれたコードを実行可能な状態にする事」を実行コンテキストに入る(entering an execution context)といいます。

グローバルコンテキスト
→ グローバルスコープ
→ functionの定義に囲われていない空間

関数コンテキスト
→ 関数スコープ
→ functionの定義に囲われている空間

mpywさんのベストアンサーから引用。

◯◯コンテキストで色々な意味があります。
これだけ抑えてればとりあえず良いでしょう。

なぜかに話を戻すと、、、

コード内で関数を書く前に、関数呼び出しを最初に書いても、コードは動作します。これは JavaScript でコンテキスト実行が動作するためです。

簡単に言うと、関数宣言してない関数を先に呼び出してもコードが動作するような状態に勝手にjsがしてくれるらしい。
これを「巻き上げ」「ホイスティング」と言う。

なぜ匿名(無名)関数 は巻き上げされないのか、逆になぜ巻き上げ機能があるのか

しかし、上から順番に処理が流れていくようなソースコードが好き、という方もいらっしゃいます。
宣言文を全て上にもっていってしまうとソースコードを読み進めるにあたって、変数の内容を確認するために関数の最初に戻って確認したらまた読み進めて〜、という感じで行ったり来たりになってしまいがちになります。なので必要になった時に宣言や代入式が記述してあるほうが読みやすい、と感じる方ですね。私もそうです。

これを読んでも正直関数を巻き上げする理由がよく分からない。。。。
分かる人いれば教えてください。。。。。

だって俺がもしプログラミング言語の作者だったら、
上から順に読んでいって、定義されていない関数を呼び出すなら
速攻でエラーを出すように設計するし、、、。。。

ちなみに変数の巻き上げは理解できました。

変数で巻き上げ起こる理由はこれです。
https://note.affi-sapo-sv.com/js-hoisting.php

巻き上げがおこらないと、コードの途中で変数の参照先が変わります。

例えば次のようなコードで巻き上げがおこらないとしたら、どうでしょうか?


let x = 100; // (a)



function a(){



    console.log( x ); // (1) : (a) を参照



    let x = 200; // (b)



    console.log( x ); // (2): : (b) を参照
}

コードの途中で関数外部の変数から、関数内部の変数に参照先が変わっていますね。

この程度のコードなら問題ありませんがもっと長いものになると、いつ参照先が変わったのかを把握するのが難しくなります。
その結果バグを生み出す原因にもなります。

無名関数が巻き上げされない理由は,,,
よく分からなかったです。


hoge();

var hoge = function(){
    console.log("hoge");
};

これを実行させてみると、「『hoge』を関数として実行できないよ!」とちゃんとエラーになります。

このように関数式での関数の定義は代入処理が行われた時に初めて定義されますので巻き上げ処理は行われません。恐らく、大多数の方の想像通りの動きをしているのはこちらの関数式の方だと思います。

まとめ

関数式の『無名関数』(匿名関数)は巻き上げが起こらず、
関数宣言の場合は、まだ定義してない関数を先に呼び出しても、
後々定義されているなら動く。

と言った仕様らしい。

関数宣言

おまけ

関数式とか、関数宣言とか、無名関数とか色々ごっちゃになって分かりずらかった。
https://wp-p.info/tpl_rep.php?cat=js-intermediate&fl=r10

関数宣言は以下のようなパターンで、


function hoge(){
    // ここに処理
};

関数式は以下のようなパターンになります。

var hoge = function(){
    // ここに処理
};

そして関数式の場合、functionの後に関数名がないから匿名関数(無名関数)と言うらしい。
関数式の中には

var hoge = function hoge2(){
    // ここに処理
};

ともちろん名前のついた関数式でもok

関数式は、普通の名前付き関数と、無名関数がある

参考サイト

2
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3