JavaScript の即時実行関数

JavaScript の即時実行関数

この記事は他の言語でも読めます。

IIFE(Immediately Invoked Function Expression) は「即時実行関数」のことだ。文字どおり、宣言と同時に実行される関数を意味する。

ふつう、関数を作ってすぐに実行するなら次のように書ける。

function a() {
  console.log('hello world');
}
a();

もし関数を一度だけ呼べばよいのなら、a という変数を割り当てるのはもったいない。これを短く書けるのが IIFE パターンだ。

(function () {
  console.log('hello world');
})();

IIFE は宣言と同時に実行されるので、関数に名前を付けなくても(関数式)エラーにならない。そして関数の戻り値が変数に代入される。

var a = (function () {
  return 3;
})();
console.log(a); // 3;

IIFE を使うと、JavaScript で変数の 隠蔽(encapsulation) も実現できる。

/* b が変更されないことは保証されない。 */
var a = {
  b: 3;
};
a.b = 5;

/* b が変更されないことが保証される。 */
var a = (function () {
  // b は外部から変更できない。
  var b = 3;
	return {
    getB() {
      return b;
    }
  };
})();

console.log(a.getB()); // 3;
console.log(a.b); // undefined;

だからこそ、ブラウザ向けライブラリのほとんどは、この隠蔽を使うために (() ⇒ {})() という一見意味のなさそうなコードで包まれている。

IIFE はパフォーマンスの改善にも使える。次のコードを見てみよう。

function getSomeValue(a, b) {
	function squared(num) {
    return num * num;
  }
  function subtractOne(num) {
    return num - 1;
  }
  return squared(a) + subtractOne(b);
}

console.time('作業');
Array.from(new Array(10000000)).forEach(() => {
  getSomeValue(5, 5);
});
console.timeEnd('作業');
// 350ms;

このコードを実行すると、約 350ms(筆者の環境)かかる。問題を見てみると、このコードは getSomeValue 関数を呼ぶたびに squared、subtractOne 関数を定義し直している。

squared、subtractOne 関数をグローバルスコープに出す方法もあるが、ほかの場所から参照されたり書き換えられたりする余地ができるので危険だ。

こういうときにも IIFE が役に立つ。

var getSomeValue = (function () {
  /* この関数は一度だけ割り当てられ、外部から変更することもできない。 */
	function squared(num) {
    return num * num;
  }
  /* この関数は一度だけ割り当てられ、外部から変更することもできない。 */
  function subtractOne(num) {
    return num - 1;
  }
  /* 実際に呼び出されるのはこの部分だけだ。 */
  return function (a, b) {
    return squared(a) + subtractOne(b);
  }
})();

console.time('作業');
Array.from(new Array(10000000)).forEach(() => {
  getSomeValue(5, 5);
});
console.timeEnd('作業');
// 200ms;

JavaScript には、もともとクラスがない。JavaScript のクラス構文は、単なる糖衣構文(syntactic sugar)にすぎない。その証拠に、作成したクラスの型を出力すると次のようになる。

class A {};
console.log(typeof A); // function

JavaScript のクラスの内部は関数で実装されていて、IIFE も使われている。簡単なクラスを作ってみると次のようになる。

var Person = (function () {

  function Constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  Constructor.prototype.getName = function () {
    return this.name;
  }

  Constructor.prototype.getAge = function () {
    return this.age;
  }

  return Constructor;
})();

var person = new Person('winetree94', 20);

console.log(person.getName());
console.log(person.getAge());

参考資料

author profile
winetree94

ウェブフロントエンドエンジニア兼、自室の DevOps エンジニアです。
コツコツ書くブロガーになりたいのですが、そうするには怠けすぎています。