DTPab

印刷やデザイン、アドビ製アプリやスクリプトなど、雑多な技術ブログ

実行する回数によって処理を変えたい

同僚から、初めて実行した時と2回目に実行した時とで、スクリプトの動作を変えたいがどうすればいいか?という質問をもらいました。
せっかくなので「実行する回数によって処理を変える方法」について(もちろんInDesignスクリプトです)説明したいと思います。

同僚の失敗

まずはうまくいかなかったコード。

#targetengine session

//実行する回数によって違う結果をalertする
var count = 1;
alert(count + "回目の実行です");
count++;

何回やっても「1回目の実行です」になります。#targetengineを指定しているので、処理が終了してもInDesignスクリプトの変数の値を覚えています。つまり、スクリプトが終了してもcountには2が入っている状態です。でも変数宣言が頭にあるので、何回実行してもcount = 1が最初に代入されてしまい、結果は「1回目」のままです。

変数の巻き上げを利用する

ほとんど同じコードですが、変数の巻き上げ(ホイスティング)を利用してこの問題を回避することができます。

#targetengine session

//実行する回数によって違う結果をalertする
if (!count) var count = 1; 
else count++;
alert(count + "回目の実行です");

最初のif文の条件式に注目してください。初めてスクリプトを実行した段階では、変数countはまだ宣言されていません。宣言されていませんが、スクリプトはエラーなく処理されます。このif文if (!count)の時点では、countにはundefined、つまり「未定義」という値が入っています*1
JavaScriptでは、変数をどこで宣言しても、スコープ*2の最初で宣言されるという仕様になっています。今回のコードでいえば、変数countはif文より後ろで宣言されていますが、スコープの最初、つまりスクリプトを実行した一番最初の段階でcountにはundefinedが与えられています。
よくJavaScriptの変数はスコープの先頭で定義せよと言われますが、それはこの巻き上げ(ホイスティング)のためです。

さてこのif文の条件式ですが、否定演算子!をつけることで変数countの評価(真偽)を反転させています。ifやwhile文で式を評価する際、厳密にはTruthyかFalsyかを判断することになります*3
したがって、最初に実行した段階では変数countにはundefinedが入っているので、このif文では真と評価され、var count = 1;が実行されます。その後alert文が実行され「1回目の実行です」となります。
もう一度実行すると(targetengineの指定によってInDesignスクリプトの変数を覚えているので)countには1が入っているため、最初のif文は偽と評価され、count++;が実行されるという仕組みです。

とまぁここまで書いておいて、これはあまりスマートな実装ではなのでお勧めしません…^^;;

論理演算子(論理OR)と関数を使う

こんな変数の巻き上げを利用するのではなく、やっぱり機能としてちゃんと実装したいですよね。ここでは論理ORと関数を使ったやり方を紹介します。

まず大事なのが、以前「二重配列によるソートでフレームを座標順に取得する」で紹介した論理演算子のひとつ、論理OR演算子||です。
if文などでお馴染みで、よく「A もしくは B」という条件式を「A || B」というふうに書くと思います。この演算子はやっぱり演算子であって、「もしくは」という感覚的な表現では馴染まないという話をこの記事で紹介しました。
この演算子の評価では、

  • Truthy1 || Truthy2 ではTruthy1の値が返る
  • Truthy || Falsy ではTruthyの値が返る
  • Falsy || Truthy ではTruthyの値が返る
  • Falsy1 || Falsy2 ではFalsy2の値が返る

という結果になります。これを覚えておいてください。

そしてこれを利用して関数でスクリプトの実行回数を管理します。まずはコードをご覧ください。

#targetengine session

var count = count || 0;

var countUp = function (){
    return function (){
        return count++;
    }
}();

alert (countUp() + "回目の実行です");

冒頭のvar count = count || 0;で早速、論理ORを使います。最初に実行した段階では変数countは宣言だけなのでundefinedが入りますが、論理OR演算子||でFalsyと判定され、右側にある0が代入されます。
続く関数では、その変数countをインクリメントする関数を定義しています。関数を定義したあとに即時実行していることに注目してください。これによって、変数countUpには中の関数(変数countをインクリメントして返すだけの処理)がセットされます。そのためcountUp関数は実行される度に変数countにアクセスし、数値を増加させます。
ここで、例によって#targetengineを指定しているので、InDesignは一旦スクリプトの処理が終わっても変数の値を覚えています。したがって改めてスクリプトが実行されると、最初の宣言文var count = count || 0;の論理ORの左側(変数count)に1が入ることになり、論理OR右側の0が代入されません(先のTruthy || Falsyの評価になる)。

これを三項演算子で応用すると、5回実行したら1回目の実行に戻るという処理も簡単に実装できます。

#targetengine session

var count = (count % 6 === 0)? 1: count || 0;

var countUp = function (){
    return function (){
        return count++;
    }
}();

alert (countUp() + "回目の実行です");

三項演算子が分かりにくいかもしれませんが、処理自体はシンプルです。剰余演算子%を使い、まずcount % 6で変数countが6の倍数かどうかを調べています。つまり、実行する度に変数countは1、2、3、4、5と増えますが、6になったときcount % 6が0になるので、6の倍数になると判断できるわけです。
そしてcount % 6 === 0が真のとき、三項演算子によって変数countには改めて1が代入される仕組みです(そうでなければcountの値が再代入される)。論理ORとその右の0の部分は従前と同じです。変数countundefinedになる1回目の実行のときだけ0を代入する役目を果たします。
今回は1→2→3→4→5→1→2…という処理ですが、剰余演算子%の後の数値を仮にnに変えれば、n回目の実行で1回目の処理に戻す、ということができます。

以降の実際のスクリプト処理をswitch文で実装したり、if文で実装したり、煮るなり焼くなり好きに実装してください^^/

*1:ExtendScriptの場合です。一般的なJavaScriptにおいてはこの限りではありません。詳細はvariable文(var宣言)について、例えばMDNなどをご参照ください。

*2:スコープについては詳細を割愛しますが、そのスクリプトや関数の中、と思ってください。こちらも詳細はWebや書籍に譲ります。

*3:Falsyとは式として評価されるとfalseに判定される値のこと。0・-0(数値の0)、null、false、NaN、undefined、""(空の文字列)がFalsyです。それ以外のJavaScriptの有効な値はTrueと評価されます(これらをTruthyといいます)。「開眼!JavaScript 言語仕様から学ぶJavaScriptの本質」(オライリー・ジャパン、2013)、147頁以下参照。