2017年3月14日火曜日

メソッド自身を上書きして次回呼出し時は初期化処理を省く

例えばメソッドの内部で正規表現を扱う場合、その正規表現のパターンが毎回同じならメソッドが呼ばれる度に正規表現の再コンパイルするのをやめて、以下のようにするとコンパイルは初期化時一回のみになるので次回呼出し以降の実行速度が向上します。

<job>
<script>
obj={
abc:function(){
// この部分は「obj」にオブジェクトが代入される前に実行される
var reg=/\d+/
// 下記functionが「abc」プロパティにセットされる
return function(str){return str.match(reg)}
}()
}
// 123
WScript.Echo(obj.abc('adqq123qr'))
</script>
</job>


しかし、だからといって初期化とメイン処理を分けられるものを何でも上記のようにしてしまうとローディングにかかる時間が伸びてしまいます。
フレームワーク級のオブジェクトの中身でこれを多用されると待ち時間が酷いことになります。初期化しても実際には呼ばれないメソッドなども多々あると思いますので、そういう意味でもムダです。

上記のような問題を解決するために「初回呼出し時に初期化して自身をメイン処理用functionで上書きする」ための関数を作成しました。
以下ソース
<job>
<script>
forIn = function(obj,fun){var name,res; for(name in obj){if(res=fun(name, obj[name])){return res}}}
isEqual=function(obj0, obj1){
var t, names0={}, names1={}, ret
forIn(obj0, function(name){names0[name] = true})
forIn(obj1, function(name){names1[name] = true})
while(1){
t = (new Date()).getTime()
if(!names0[t] && !names1[t]){ break }
}
obj0[t] = true
ret = obj1[t]
delete obj0[t]
return ret
}
obj = {
abc:function(){
// abc関数を初期化する
var ローカル関数=function(a){return a+1}
var main=function(num){ return ローカル関数(num) }
var thisF=arguments.callee, this_=this
forIn(this,function(name,v){return isEqual(v,thisF) ? (this_[name] = main) : 0})
return main.apply(this,arguments)
},
def:function(){
// abc関数を初期化する
var ローカル関数=function(a){return a+1}
var main=function(num){ return ローカル関数(num) }
var thisF=arguments.callee, this_=this
forIn(this,function(name,v){return isEqual(v,thisF) ? (this_[name] = main) : 0})
return main.apply(this,arguments)
},
ghi:function(){
// ghi関数を初期化する
var ローカル関数=function(a){return a+1}
var main=function(num){ return ローカル関数(num)+'-'+this.abc(num-1) }
var thisF=arguments.callee, this_=this
forIn(this,function(name,v){return isEqual(v,thisF) ? (this_[name] = main) : 0})
return main.apply(this,arguments)
},
jkl:function(){
// jkl関数を初期化する
var ローカル関数=function(a){return a+1}
var main=function(num){ return ローカル関数(num)+'-'+this.abc(num-1) }
return 自身を上書き(this, main, arguments)
}
}
自身を上書き=function(this_, fun初期化後, arg){
var 呼出し元=arguments.callee.caller
forIn(this_, function(name,v){ return v==呼出し元 ? (this_[name]=fun初期化後) : 0 })
return fun初期化後.apply(this_, arg)
}
arr = [
// true
obj.abc==obj.abc,
// false
obj.abc==obj.def,
// true
isEqual(obj.abc,obj.abc),
// function(){ // abc関数を初期化..
obj.abc,
// 1
obj.def(0),
// function(){ // abc関数を初期化..
obj.abc,
// function(num){..
obj.def,
// 1
obj.abc(0),
// function(num){..
obj.abc,
// function(num){..
obj.def,
// 2
obj.def(1),
// 3-2
obj.ghi(2),
// function(){ // jkl関数を初期化..
obj.jkl,
// 334-333
obj.jkl(333),
// function(num){..
obj.jkl
]
WScript.Echo(arr.join('\n\n------------------------\n\n'))
</script>
</job>




ちなみにこんな方法もあります。
<job>
<script>
obj={
abc:function(){
var arg=arguments, m=arg.callee
if(!m.func){
var reg=/\d+/
m.func = function(str){return str.match(reg)}
}
return m.func.apply(this, arg)
}
}
// 123
WScript.Echo(obj.abc('adqq123qr'))
// 123
WScript.Echo(obj.abc('adqq123qr'))
</script>
</job>
object作成時に初期化.wsf
これだとメソッドに限らず無名関数でも使えます。
ただし毎回if分岐したりapplyで1step挟んだりしている分余分に時間がかかる…と思います。(未検証)

0 件のコメント:

コメントを投稿