😅

今更ながらJavaScriptのホイスティングを説明してみる

2022/01/22に公開
3

Vue.jsのソースコードを読んでいた際、関数宣言エクスポートされているisReactive内で、isReactive以降で関数宣言エクスポートされているisReadonlyが実行されていることに違和感を持ちました(例1参照)。

//例1
export function isReactive(value: unknown): boolean {
  if (isReadonly(value)) {     // isReadonlyって宣言の前に実行されてる??
    return isReactive((value as Target)[ReactiveFlags.RAW])
  }
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}

export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}

恥ずかしながら、関数宣言は関数の実行前に記載されなければならないと思っていました。調べてみるとホイスティング(巻き上げ)という現象が関係しているようで、今回はホイスティングについて整理してみます!

※👇読んでいたVue.jsのソースコード
https://github.com/vuejs/core

ホイスティング(巻き上げ)とは

コンテキスト内で宣言した変数や関数の定義をコード実行前にメモリに配置すること。

イメージ的には、変数や関数の宣言はスコープの最初に行われ、その他の処理は基本的に記載順に実行されると理解しています。

具体例

ホイスティングによって以下のような挙動が発生します。

変数の話

var を使用した場合

varを利用して変数宣言をする場合、まずはじめに変数testのメモリを確保し、undefinedで初期化されます。その為、変数宣言より前に変数を呼び出すと、undefinedが出力されます(例2参照)。

//例2
console.log(test)  // undefinedが出力される
var test = 0
console.log(test) //0が出力される

letconst を使用した場合

しかし、letconstを利用して変数宣言をした場合、変数testのメモリは確保されるものの、undefinedでの初期化が行われません。その為、変数宣言より前に変数を呼び出すとエラーとなります(例3参照)。

//例3
console.log(test)  // エラー「Cannot access 'test' before initialization」
let test = 0
console.log(test) //0が出力される

関数の話

そもそも関数の定義方法

関数の定義方法は、大きく分けると①関数宣言、②関数式の2パターンが存在します(例4参照)。

//例4
//関数宣言
function example(){}

//関数式
var example = function(){} // let example = function(){} こんなんも

関数宣言の場合

関数宣言を使用する場合、関数exampleは、その実行前にメモリ上に配置されます。その為、下記のようにexampleを、実行より下で関数宣言しても、exampleは実行されます(例5)。

//例5
example(); //テストが出力される
function example(){
        console.log('テスト'); 
    }

関数式の場合

関数式の場合は、例2〜例3で記載した変数と同じような挙動が発生します。

varを使用した関数式の場合、exampleを実行しようとしても、undefinedで初期化されているため、関数ではないと怒られます(例2、例6.1参照)。letを使用した関数式では、example2が初期化されていない為、定義される前に呼び出すとエラーとなります(例3、例6.2参照)。

尚、無名関数の省略記法であるアロー関数で関数式を利用した場合も、これらのようなエラーが発生します。

// 例6.1 varの関数式
example()  //undefinedなので、「example is not a function」と怒られる
var example = function(){
        console.log('テスト'); 
    }

//例6.2 letの関数式
example2() //エラー「Cannot access 'example2' before initialization」
let example2 = function(){
        console.log('テスト2'); 
    }

最後に

今回の整理で、Vue.jsのソースコードで抱いた疑問は解消されました!雰囲気でJavaScript(とTypeScriopt)を書いてしまっているので、今年は疑問に思ったことは放置せず、理解度をあげて、自分の言葉で語れるようになりたいと思います。
※もし、間違い等があればご指摘頂けますと、非常に嬉しいです。

参考

Hoisting (巻き上げ、ホイスティング) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN

関数宣言 vs 関数式 | ES2015+ - Qiita

Discussion

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

勉強になりました!

typo があります。

本文中のundefiendundefinedではないでしょうか?

oreo2990oreo2990

typoしておりました、、、
修正しました!ご指摘ありがとうございます!!