文字列から動的に関数を呼び出したいケースとは?

関数を文字列から動的に関数を呼び出したいケースってどんな時でしょうか? 例えばHTML GET/POSTメソッドのパラメーター値によって呼び出す関数を変えたい場合などが想定されます。

以下の例では、「http://mydomain.com/ApplyFuncXAgent.xsp?func=callMe」というような呼び出しに対してクライアントサイドJavascript(CSjS)でコールする関数を変更するというコードになります。

var strFunc = location.search.split('func=')[1];
switch(location.search.split('func=')[1]){
case "func1": func1(); break;
case "func2": func2(); break;
case "callMe": callMe(); break;
default: break;
}

これを簡略化して文字列から動的に関数を呼び出したい場合、CSJSでは以下のような書き方ができます。

var strFunc = location.search.split('func=')[1];
var funcObj = window[strFunc];
if (typeof funcObj === "function") funcObj();

windowオブジェクトに登録されている関数オブジェクトを取得し関数としてコールしています。しかしながらXPagesのサーバーサイドJavascript(SSJS)ではwindowオブジェクトがありませんので別の方法で関数を呼び出してやる必要があります。

(1)windowの代わりにthisを使用する方法


var funcObj = this[strFunc];

(2)eval()を利用する方法


eval(strFunc+"()");

(1),(2)どちらでも同じ結果を得ることができます。

パラメーターを渡したい場合はcall()もしくはapply()を使用

SSJSでも文字列から直接関数として呼び出す方法は分かりましたが、次はその関数に引数を与えたい場合にどうするか。この方法はCSJSと同じになります。

例えば、「http://mydomain.com/ApplyFuncXAgent.xsp?func=callMe&args=aa,bb」というようにURLパラメータに[args=aa,bb]を追加して引数も渡す方法を想定します。この場合、以下のコードのようにcall()、もしくはapply()を利用してやることでパラメータを渡すことが可能になります。

var argsAry = @Explode(args, ",");
var funcObj = this[func];
return funcObj.call(this, argsAry[0], argsAry[1]);

apply()の場合はcall()の時の引数をカンマ区切りで指定するのではなく、第2引数にArrayとして指定してやります。

var argsAry = @Explode(args, ",");
var funcObj = this[func];
return funcObj.apply(this, argsAry);

XAgentと組み合わせたサンプル

今回自分が必要であったシチュエーションはAJAXによる非同期通信でビューの値をJSON形式で取得するための汎用的なロジックを作りたかったからでした。以下のサンプルコードはXAgentになっており関数名と引数値をURLパラメータで渡すことによって呼び出し関数を変えて目的のJSONの値を返すようになっています。
想定しているURLの呼び出しは「http://mydomain.com/hoge.nsf/ApplyFuncXAgent.xsp?func=testFunc&args=aa,bb」などです。

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
	<xp:this.resources>
		<xp:script src="/xpCommon.jss" clientSide="false"></xp:script>
	</xp:this.resources>
	<xp:this.afterRenderResponse><![CDATA[#{javascript:var externalContext = facesContext.getExternalContext();
var writer = facesContext.getResponseWriter();
var response = externalContext.getResponse();

// Set context type etc
response.setContentType("application/json");
response.setHeader("Cache-Control", "no-cache");

// read parameters
var func = context.getUrlParameter("func");
var args = context.getUrlParameter("args");

var retTxt = callFuncByString(func, args);
writer.write(retTxt);
writer.endDocument();}]]></xp:this.afterRenderResponse>
</xp:view>

上記XAgentから呼び出されているcallFuncByString()を含んだxpCommon.jssのサンプルコードです

function callFuncByString(func, args){
	try{
		var argsAry = @Explode(args, ",");
		var funcObj = this[func];
		return funcObj.call(this, argsAry[0], argsAry[1]);
	}catch(e){
		print(e);
	}
}

function testFunc(arg1, arg2){
	return "[{\"arg1\":\""+arg1+"\", \"arg2\":\""+arg2+"\"}]";
}

複数のビューのJSON値を非同期通信で取得する毎にXAgentを用意しなくて済むといのがこの汎用化コードの利点ですが、関数を動的に呼び出すことそのものはデバッグがしづらくなるという欠点もあるため使用には慎重になったほうがいいケースもあると思います。
※セキュリティーの観点からこのXAgentをそのまま使うとSSJSの標準関数を含む幾多の関数を呼び出すことが可能になるので危険です。実際には機能制限をするなどして意図した関数以外を呼び出されないように注意を払ってください。

About ktatsuki

ケートリック株式会社 代表をしています。 が、根っからのエンジニア脳です。 IBM Notes/Dominoの開発を得意としますが、 C++ / Java / PHP / Javascript などの言語を使ってWEBアプリ、iPhone / Android アプリ開発などをしたりします。 XPagesの仕事をしているとテンションが通常の1.25倍ぐらい高くなります。 I am owner of KTrick Co., Ltd. and Notes/Domino developer. IBM Champion for 2015 - 2017. I am interested in web application development and preferred languages are Notes/Domino, C++ / Java / PHP / Javascript.

2 Comments

  1. Sven Hasselbach

    This is a extremely dangerous concept and should be avoided. Executing remote code without checking the parameters for their validity as in your XAgent example is a security nightmare, especially in combination with SSJS.

    At least you should add a warning to your post.

    1. ktatsuki

      Hi Hasselbach, Thank you for your comment and pointing out the security purpose. I actually added about serviceability comment at the end of my post. However I should have add a warning comment about security purpose more. Thank you.

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です