1
1

More than 3 years have passed since last update.

ツクールのおまじないからの卒業(即時実行関数編)

Last updated at Posted at 2021-03-20

(03/24/2021 02:00追記)
複数のご指摘を元に、内容を全体的に修正・再構成しました

結論

今回は短いです。
まず結論。

「ES20xxでプラグインを記述する際は、即時実行関数の代わりにブロック{}で代用可能だよ!

とはいえ、即時関数をブロック{}に置き換える上で幾つか注意点もあるので
順に見ていきましょう。

そもそもなぜ即時実行関数を使っていたのか

varが関数スコープだったからです。
スコープについてはあまたある入門書、入門記事でも丁寧に説明されているのでここでは説明しません。
そもそもスコープがわからん、という方はこの辺が分かりやすいと思います。

で、本題に返って、なぜ今まで即時実行関数が使われていたのかというと、

varが関数スコープのため、スコープ汚染を防ぐためにコード全体を関数化する必要がある。
 →だけど、その場で即座に一回だけ呼び出す関数をわざわざ名前付き関数として作るのも不毛じゃね?
  →だったら、無名関数をその場で即実行してしまえばいいじゃない。
   →即時実行関数、爆誕

という考え方です。
var、すべてはおまえのせいだ!

現状は?

letconstしか使ってないですよね。・・・使ってないですよね?(念押し
今さらですがletおよびconstはブロックスコープです。

つまり、コード全体をブロック{}で囲めばそれで事足ります。

//今まで
(function () {
    /*なんやかんや*/
})();

//これから
{
    /*なんやかんや*/
}

スペースを抜いても17文字→2文字。スッキリ!わかりやすい!
また、varとの決別の意思表示にもなるかと思います。

注意点も幾つかあります

ただし即時実行関数を無条件にブロックに置き換えられるわけではなく、いくつかの注意すべき点もあります。

その1-strictモードの扱い

まずはmdnから引用します。

Strict モードはスクリプト全体または個別の関数に適用できます。
括弧 {} で括られるブロック構文には適用できません。
そのような場所に適用しようとしても何も起きません。

特に重要なのが『括弧 {} で括られるブロック構文には適用できません。』という部分。

{
    'use strict';
    /*なんやかんや*/
}

のように、strictモードの呼び出しをブロック内に記述すると、strictモードが適用されません。
これを回避するには以下のように'use strict'をブロックの外側に引っ張り出し、スクリプト全体に対して
strictモードを適用する必要があります。

'use strict';
{
    /*なんやかんや*/
}

その2-strictモードにおける即時実行関数とブロックの、thisの解釈の差異

即時実行関数内と、その代替であるブロック内で直接thisを使用した場合の挙動に違いがあります。

  • 即時実行関数内でthisを使用した場合
'use strict';
(function() {
    console.log(this);    //thisはundefinedとなる
    this.foo = 0;         //そのため、thisのプロパティにアクセスするとTypeErrorが起こる
})();
  • ブロック内でthisを使用した場合
'use strict';
{
    console.log(this);    //thisはグローバルオブジェクトを指す
    this.foo = 0;         //なので、グローバルスコープにfooプロパティが作られる形で処理が通ってしまう
}

即時実行関数内やブロック内で、意図的に直接thisを使用することはまず無いと思いますが、
誤って記述してしまった際にブロックだとエラーを吐かずに通してしまう分だけ少し気付きにくかったりします。

「そんなことしないよ」と思う人もいるかもしれませんが、一つ、ありがちな事例がありました。
ブロックとアロー関数の組合せです。
以下、頻出するプラグインコマンドの定義例において、thisを使用したものです。

'use strict';
{
    //プラグインコマンドの定義
    PluginManager.registerCommand(pluginName, "set", args => {
        textPictureText = String(args.text);
        this.foo = args.foo;
    });
}

アロー関数とthisの解釈については良く語られるテーマなので、ここでは説明を行いません。
(詳しくは こちら をご参照ください)
ともかく、function構文と異なり、アロー関数だと宣言時のthisが呼び出し時にもそのまま適用されます。
結果、この場合のthisはアロー関数を超えて、更にはブロックも超えてグローバルオブジェクトとなります
怖いですねぇ、恐ろしいですねぇ。
プラグインコマンドに限らず、コールバック関数の類だとうっかりやっちゃいそうです。

そういうことをしなければ問題ないのですが、うっかりした際に思いがけないハマりを起こす可能性はありますので、
「関数とブロックでthisの挙動が違う」
という認識は持っておいた方が良いでしょう。

最後に

今回の元ネタはこちらの速習 ECMAScript 2020という本でした。
ES5以前のECMAScriptとの差分のみを簡潔明瞭に、それでいて分かりやすく解説してくれており、たいへんオススメです。
また、より詳しくはこちらの記事も参考になるかと思います。

謝辞

今回、この記事を書くにあたって複数の方からご指摘、ご意見をいただきました。
正確を期するにあたり、たいへん参考になりました。
厚く御礼申し上げます。

1
1
4

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
1
1