日々のコンピュータ情報の集積と整理

Dr.ウーパのコンピュータ備忘録

2015年3月11日水曜日

JavaScript:1度のみ実行できる関数を作成する関数 - 2回目以降は空又は任意の処理を実行します

イントロダクション

JavaScript を使用してプログラムを書いていると、この処理は絶対に 1 度しか実施されないことを保証したいということがあります。

例えば、情報をサーバやストレージへ書き込むような処理を連続クリックから保護したいケースや、外部サーバから画像やスクリプトを取得する処理で(何らかのバグがあり)短時間に大量のリクエストを送ってしまうのを防ぎたいケースです。


そのようなケースでは、いろいろな対策が考えられますが、その1つとして1度しか実行できない関数を作成するというケースです。

では、実際にソースコードを見てみましょう。

JavaScript:1度のみ実行できる関数を作成する関数 - 2回目以降は空又は任意の処理を実行します

プログラムコード:

<script type="text/javascript">
<!--
    /*
    一度のみ実行できる関数を作成する

    一度のみ実行したい処理が記載された関数 へは引数を渡すことが可能です

    引数:
    func : 
    一度のみ実行したい処理が記載された関数
    
    notFirstProc : (省略可能)
    2回目以降に、関数が呼び出された場合に実行する処理
    省略された場合、runOnceFunction_NotFirstProcDefault関数の処理が実行されます

    戻り値:
    一度のみ実行できる関数

    */
    function createRunOnceFunction(func, notFirstProc) {

        var run_one_func = func;        // 一度のみ実行できる関数が呼び出されたときに、実際に呼び出される関数を保持

        // 一度のみ実行できる関数を作成し、返却
        return function () {
            
            // 2 回目以降に確実に、func が呼び出されないようにするために、
            // func 呼び出し前に 2 回目以降に関数が呼び出された場合に実行する関数と入れ替える
            var org_func = run_one_func;
            run_one_func = (notFirstProc === undefined ? 
                runOnceFunction_NotFirstProcDefault : notFirstProc);

            // 引数付きで、一度のみ実行したい処理 または、2 回目以降の処理を実行し、結果を返す
            return org_func.apply(this, arguments);
        }
    }

    /*
    2回目以降に、関数が呼び出された場合に実行する処理
    createRunOnceFunction の notFirstProc が省略された場合に使用されます
    */
    function runOnceFunction_NotFirstProcDefault() {}

//-->
</script>


特徴:

この 1度のみ実行できる関数を作成する関数の JavaScript コードの特徴は次のとおりです。

{関数の実行済み、関数の未実行を内部的に管理している}1度のみ実行できる関数を、関数の呼び出しだけで簡単に作成できる

関数を呼び出したのか、呼び出していないのかを管理する簡単な手法として、実行済み/未実行をフラグ(true/false)で管理する手法があります。

しかし、自分自身でそのような実行済み/未実行を管理するフラグを作成し、自分自身で判定式を記載すると、記載ミスや管理ミスによって、バグが生じる可能性があります。

その点、呼び出す関数自体に関数の実行済み、関数の未実行を管理する機能を付与し、その{1度のみ実行できる関数}を呼び出す以外に、その処理を呼び出す手段を設けなければ、システム的に関数が1度しか実行できないことを保証できます。


{1度のみ実行できる関数}は、createRunOnceFunction関数を呼び出して作成します。


2回目以降にその{1度のみ実行できる関数}が呼び出された場合に、任意の処理を実行できる

2回目以降にその{1度のみ実行できる関数}が呼び出された場合に、何もせず終了するという方法も取ることができますが、このコードでは、2回目以降にその{1度のみ実行できる関数}が呼び出された場合の処理を任意に記載できます。

2回目以降にその{1度のみ実行できる関数}が呼び出された場合の処理も、わざわざそのような処理を記載するのが面倒な場合には、{1度のみ実行できる関数}作成時にその処理を記載しなければ、デフォルトの runOnceFunction_NotFirstProcDefault メソッドが呼び出されます。(デフォルトで処理は空)


{1度のみ実行できる関数}から戻り値を受け取ったり、{1度のみ実行できる関数}へ引数を渡すことができる

{1度のみ実行できる関数}への引数・{1度のみ実行できる関数}からの戻り値を考慮しているので、{1度のみ実行できる関数}から戻り値を受け取ったり、{1度のみ実行できる関数}へ引数を渡すことができます。


使い方

次のようなコードを記載して使用することができます。

例:body タグ中に記載したコード

<script type="text/javascript">
<!--

    /*
    一度のみ実行できる関数を作成する

    一度のみ実行したい処理が記載された関数 へは引数を渡すことが可能です

    引数:
    func : 
    一度のみ実行したい処理が記載された関数
    
    notFirstProc : (省略可能)
    2回目以降に、関数が呼び出された場合に実行する処理
    省略された場合、runOnceFunction_NotFirstProcDefault関数の処理が実行されます

    戻り値:
    一度のみ実行できる関数

    */
    function createRunOnceFunction(func, notFirstProc) {

        var run_one_func = func;        // 一度のみ実行できる関数が呼び出されたときに、実際に呼び出される関数を保持

        // 一度のみ実行できる関数を作成し、返却
        return function () {
            
            // 2 回目以降に確実に、func が呼び出されないようにするために、
            // func 呼び出し前に 2 回目以降に関数が呼び出された場合に実行する関数と入れ替える
            var org_func = run_one_func;
            run_one_func = (notFirstProc === undefined ? 
                runOnceFunction_NotFirstProcDefault : notFirstProc);

            // 引数付きで、一度のみ実行したい処理 または、2 回目以降の処理を実行し、結果を返す
            return org_func.apply(this, arguments);
        }
    }

    /*
    2回目以降に、関数が呼び出された場合に実行する処理
    createRunOnceFunction の notFirstProc が省略された場合に使用されます
    */
    function runOnceFunction_NotFirstProcDefault() {}


    var writeOnce = createRunOnceFunction(function () {
        document.write("1回だけ表示される文字列<br />");
    });


    writeOnce();
    writeOnce();
    writeOnce();
    
    document.write("<hr />");

    writeOnce = createRunOnceFunction(function () {
        document.write("1回だけ表示される文字列<br />");
    }, function () {
        document.write("2回目以降です。<br />");
    });

    writeOnce();
    writeOnce();
    writeOnce();

    document.write("<hr />");

//-->
</script>


実行結果

1回だけ表示される文字列

1回だけ表示される文字列
2回目以降です。
2回目以降です。


解説

{1度のみ実行できる関数}の作成
{1度のみ実行できる関数}を作成する場合には、次のように記載します。

    var writeOnce = createRunOnceFunction(function () {
        document.write("1回だけ表示される文字列<br />");
    });


createRunOnceFunction は引数に渡された関数を、{1度のみ実行できる関数}として返却します。

1度のみ実行したい処理は、コード中で関数などで名前を付けてしまうと、意図せず呼び出されてしまうため、次のように無名関数として記載しています。

function () {
        document.write("1回だけ表示される文字列<br />");
    });

このように、無名関数を使用して処理を記載することで、その中に記載された処理は、{1度のみ実行できる関数}を使用する以外に呼び出せなくします。


{1度のみ実行できる関数}の実行
{1度のみ実行できる関数}を実行する場合には、次のように{1度のみ実行できる関数}を作成した時に使用した変数名を用いて、次のように呼び出します。

    writeOnce();
    writeOnce();
    writeOnce();

ここでは、3 回呼び出していますが、実行結果を見ると最初の 1 回目の呼び出し以外では、1度のみ実行したい処理は動作していません。


{1度のみ実行できる関数}の作成 - 2回目以降の処理を指定する
2回目以降の処理を指定して、{1度のみ実行できる関数}を作成するには、次のように createRunOnceFunction の第2引数に、2回目以降の処理を記載します。

    var writeOnce = createRunOnceFunction(function () {
        document.write("1回だけ表示される文字列<br />");
    }, function () {
        document.write("2回目以降です。<br />");
    });

その結果、次のような 3 回{1度のみ実行できる関数}を実行した場合には、最初の1回目とそれ以外とで、別々の処理を実行することができます。

呼び出し処理:
    writeOnce();
    writeOnce();
    writeOnce();

実行結果:
1回だけ表示される文字列
2回目以降です。
2回目以降です。


注意:

プログラム上や一般ユーザの画面上からは、そのプログラムを1度しか実行できないことを保証できても、開発者用ツールを使用することで、その1度しか実行したくないプログラムを実行されてしまう可能性があります。

そのような開発者用ツールによるプログラムの実行に対しては、無力です。

そのようなケースで1度しか実行されないことを保証するには、サーバ側でその1度しか実行したくない処理を実行するなどといった対処が必要になります。


参考文献

JavaScript - 最初の一回だけ実行される関数を生成する関数 - Qiita
http://qiita.com/jwhaco/items/631bb3ef2a34064b0d86

[JavaScript]関数に、一度しか実行しない初期化処理を組み込む
http://nanoappli.com/blog/archives/1418

JavaScript のスコープチェーンとクロージャを理解する - tacamy.blog
http://tacamy.hatenablog.com/entry/2012/12/31/005951

applyとcallの使い方を丁寧に説明してみる - あと味
http://taiju.hatenablog.com/entry/20100515/1273903873

Function.prototype.apply() - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

arguments - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments

関数リテラル(匿名関数/無名関数) - 関数 - JavaScript入門
http://www.ajaxtower.jp/js/function/index5.html


動作確認環境

上記のプログラムは、以下の環境で動作することを確認しました。

OS : Windows Vista 64bit

ブラウザ:
Chrome : バージョン 40.0.2214.115 m
Firefox : バージョン 36.0.1
Internet Explorer : バージョン 9.0.8112.16421


まとめ

このように、JavaScript で 2重実行を防ぎたい処理を記載する場合には、{1度のみ実行できる関数}を作成する関数をあらかじめ作成しておくと便利です。


関連記事







関連記事

関連記事を読み込み中...

同じラベルの記事を読み込み中...