JS Tips 関数定義式と巻き上げ(Hoisting)

JS Tips からHoisting(巻き上げ)の紹介

今回もJS Tips の紹介。

ネタはHoisting(巻き上げ)、というJavaScriptの言語仕様についてのTipsだ。
この仕様が理解できれば、以下の(a)と(b)のコードの違いがわかるようになる。

// (a)
function f(x) { return x; }
// (b)
var f = function(x) { return x; }

宣言と定義の違い

変数の巻き上げについてのTipsを読む前に宣言と定義の違いについて知っている必要がある。
以下が宣言と定義の例だ。

var a; // 宣言

a = 'hello'; // 定義

簡単だ。
こんなふうに宣言と定義を同時に行うことだってできる。

var a = 'hello'; // 宣言と定義を同時に行う

こんなことは普段意識することなくコードを書いているのでいざ宣言と定義の違いと言われても「え、なんだっけ」ってなりがちだ。

ついでに関数の宣言と定義についても見てみよう。

関数の宣言と定義

先ほどの(a)と(b) の例に戻ろう。

// 関数宣言文
// (a)
function f(x) { return x; }
// 関数定義式
// (b)
var f = function(x) { return x; }

前者が関数宣言文後者が関数定義式と呼ばれる。 (b)は宣言した変数に関数を定義している。これが変数の宣言と定義を同時に行うのと同じようなものだ。
(a)は関数を宣言している。で、これは変数の宣言と同じ…ではないな。なんだろうこれうまく説明できないな
まあ、これが関数定義式だということで話を進める。

さて、JavaScriptという言語だが、この関数宣言文と関数定義式の違いが重要な言語なのだ。
それがhoisting = (宣言の)巻き上げという概念だ。

以下の例を見てみよう。

function someFunc(){
    foo(); // -> foo :関数宣言前でも実行可
    bar(); // -> TypeError: undefined is not a function :実行不可   

    function foo(){
        console.log('foo');
    }

    var bar = function(){
        console.log('bar');
    };

    bar(); // -> bar :関数式後なので実行可
}

someFunc();

このコードは 関数宣言と関数式の違い - Qiita から引用させていただいた。

someFunc内の2つの関数を別のソースコードにわけてみよう。

// sample1
function someFunc(){
    foo(); // -> foo :関数宣言前でも実行可

    // 関数の定義をしている!
    function foo(){
        console.log('foo');
    }

    foo(); // -> もちろん宣言後でも実行可能
}
// sample2
function someFunc(){
    bar(); // -> TypeError: undefined is not a function :実行不可   

    // 関数の宣言と定義をしている!
    var bar = function(){
        console.log('bar');
    };

    bar(); // -> bar :関数式後なので実行可
}

sample1 とsample2 のコードは以下のコードに等しい。

// sample1a
function someFunc(){
    // 関数の定義をしている!
    function foo(){
        console.log('foo');
    }
    foo(); // -> foo :関数宣言前でも実行可

    foo(); // -> もちろん宣言後でも実行可能
}
// sample2a
function someFunc(){
    // 宣言だけ巻き上げられる!
    var bar;
    bar(); // -> TypeError: undefined is not a function :実行不可   

    // 定義は巻き上げられない!
    bar = function(){
        console.log('bar');
    };

    bar(); // -> bar :関数式後なので実行可
}

ここまで前置き。 JSのライブラリ読むときにこの辺わかってるとすっきり読める、はず。

ここからJS Tipsの翻訳。

#11 - Hoisting(巻き上げ)

変数の巻き上げ(hoisting)について理解することは関数のスコープを制御する上で役に立つだろう。変数の宣言と関数の定義はその先頭に巻き上げられると覚えればいい。変数の定義は、たとえ同じ行に変数の宣言と定義を同時に行っても先頭に巻き上げられることはない。 ある変数が宣言されていて定義による変数の割り当てが行われていれば、システムはその変数の存在を知ることができる。

function doTheThing() {
  // ReferenceError: notDeclared is not defined
  console.log(notDeclared); // 宣言されていない

  // Outputs: undefined
  console.log(definedLater); // スコープ外で宣言のみされている
  var definedLater;

  definedLater = 'I am defined!'
  // Outputs: 'I am defined!'
  console.log(definedLater) // スコープ内で宣言と定義が行われている

  // Outputs: undefined
  console.log(definedSimulateneously); // スコープ外で宣言と定義が行われている
  var definedSimulateneously = 'I am defined!'
  // Outputs: 'I am defined!'
  console.log(definedSimulateneously) // スコープ内で宣言と定義が行われている

  // Outputs: 'I did it!'
  doSomethingElse(); // スコープ内に定義されている関数

  function doSomethingElse(){
    console.log('I did it!');
  }

  // TypeError: undefined is not a function
  functionVar(); // スコープ内に定義されていない変数(中身は関数)

  var functionVar = function(){
    console.log('I did it!');
  }
}

コードを読みやすくするために、変数の宣言は関数スコープの先頭で行おう。その変数のスコープがどこから始まるのかが明確になる。 変数の定義は変数が必要となる前に行う。 Define your functions at the bottom of your scope to keep them out of your way.(訳せなかった。関数の定義はなるべくやらないようにしようってこと?)

Hoisting – JS Tips – A JS tip per day!

JSTips関連の記事

JavaScript 第6版