【JavaScript】実行中の関数自身の関数名を取得する
はじまり



今回の問題点
今回の問題点は、以下のように関数を実行すると、thisが関数自身ではなく他の関数を指してしまう事象です。(thisがundefinedになってしまうこともありますよね・・・。)
function getThisFuncName(func){ const funcValue = func.toString(); const initialOfFuncStatement = "function "; let funcName = ""; if(funcValue.indexOf(initialOfFuncStatement, 0) !== 0){ funcName = funcValue.substring(0, funcValue.indexOf("(", 0)); }else{ funcName = funcValue.substring(initialOfFuncStatement.length, funcValue.indexOf("(", 0)); } return funcName;}
function myFunction(){ console.log(this); console.log("============================="); console.log(this.toString()); console.log("///////////////////////////"); console.log(getThisFuncName(this));}
function main(){ myFunction();}ミスった出力:
{ myTestFunc: [Function: myTestFunc], ... (略) ... }=============================[object Object]///////////////////////////今回のソース①
そんなthisが指すところが定まらない問題を解決するために、今回使用したソースはこんな感じになります。
function getThisFuncName(func){ const funcValue = func.toString(); const initialOfFuncStatement = "function "; let funcName = ""; if(funcValue.indexOf(initialOfFuncStatement, 0) !== 0){ funcName = funcValue.substring(0, funcValue.indexOf("(", 0)); }else{ funcName = funcValue.substring(initialOfFuncStatement.length, funcValue.indexOf("(", 0)); } return funcName;}
function myFunction(){ console.log(this); console.log("============================="); console.log(this.toString()); console.log("///////////////////////////"); console.log(getThisFuncName(this));}
function main(){ const bindFunc = myFunction.bind(myFunction); bindFunc();}出力:
[Function: myFunction]=============================function myFunction(){ console.log(this); console.log("============================="); console.log(this.toString()); console.log("///////////////////////////"); console.log(getThisFuncName(this));}///////////////////////////myFunctionまず、main()処理を実行します。
func.bind(obj)で、funcのthisにobjをbindさせるというイメージです。
そのため、今回は、myFunctionのthisに自身をバインドさせたいので、以下のように書くことで、thisがmyFunctionになった関数bindFuncを宣言できます。
function main(){ const bindFunc = myFunction.bind(myFunction); bindFunc();}そして、bindFuncを実行すると、myFunctionを実行しながらも、thisがundefinedとかにならずにmyFunctionが入っています。
function myFunction(){ console.log(this); console.log("============================="); console.log(this.toString()); console.log("///////////////////////////"); console.log(getThisFuncName(this));}補足:bind以外のバインド方法
bind以外にもthisにオブジェクトをバインドさせる方法が2つあります。
callとapplyなのですが、引数を渡さないのであれば、関数名以外の書き方は変わりません。引数を渡さない場合は、callがスプレッド形式で、applyがリスト形式で引数を渡します。
call
function getThisFuncName(func){ const funcValue = func.toString(); const initialOfFuncStatement = "function "; let funcName = ""; if(funcValue.indexOf(initialOfFuncStatement, 0) !== 0){ funcName = funcValue.substring(0, funcValue.indexOf("(", 0)); }else{ funcName = funcValue.substring(initialOfFuncStatement.length, funcValue.indexOf("(", 0)); } return funcName;}
function myFunction(){ console.log(this); console.log("============================="); console.log(this.toString()); console.log("///////////////////////////"); console.log(getThisFuncName(this));}
function main(){ myFunction.call(myFunction);}apply
function getThisFuncName(func){ const funcValue = func.toString(); const initialOfFuncStatement = "function "; let funcName = ""; if(funcValue.indexOf(initialOfFuncStatement, 0) !== 0){ funcName = funcValue.substring(0, funcValue.indexOf("(", 0)); }else{ funcName = funcValue.substring(initialOfFuncStatement.length, funcValue.indexOf("(", 0)); } return funcName;}
function myFunction(){ console.log(this); console.log("============================="); console.log(this.toString()); console.log("///////////////////////////"); console.log(getThisFuncName(this));}
function main(){ myFunction.apply(myFunction);}今回使用したソース②:classから取得
少し余談を話したところで、次に、class内の関数(メソッド)から取得してみます。
書き方はクラスを宣言するところ以外は特に変わりません。
class MyClass{ myFunction(){ console.log(this); console.log("============================="); console.log(this.toString()); console.log("///////////////////////////"); console.log(getThisFuncName(this)); }}
function main(){ const myClass = new MyClass(); const bindFunc = myClass.myFunction.bind(myClass.myFunction); bindFunc();}出力:
[Function: myFunction]=============================function myFunction(){ console.log(this); console.log("============================="); console.log(this.toString()); console.log("///////////////////////////"); console.log(getThisFuncName(this));}///////////////////////////myFunction今回使用したソース③:プロパティディスクリプタから取得
僕が今回の記事を書こうとした時にハマったところがここで、クラスオブジェクトが持っているメソッドを一気に取得して、それらのメソッドを実行した都度、メソッド名を取得してみたいと思います。
最終形はこれです。メソッド名を一式取得できています。
function removeItemsByValues(array, values){ if(!Array.isArray(array)){ throw new TypeError("array must be array type."); } if(!Array.isArray(values)){ throw new TypeError("values must be array type."); } let index = -1; for(let i = 0; i < values.length; i++){ index = array.indexOf(values[i]); if(index !== -1){ array.splice(index, 1); } } return array;}
function getThisFuncName(func){ const funcValue = func.toString(); const initialOfFuncStatement = "function "; let funcName = ""; if(funcValue.indexOf(initialOfFuncStatement, 0) !== 0){ funcName = funcValue.substring(0, funcValue.indexOf("(", 0)); }else{ funcName = funcValue.substring(initialOfFuncStatement.length, funcValue.indexOf("(", 0)); } return funcName;}
class MyClass{ myFunction1(){ console.log(this); console.log("=============================1"); console.log(this.toString()); console.log("///////////////////////////1"); console.log(getThisFuncName(this.value)); } myFunction2(){ console.log(this); console.log("=============================2"); console.log(this.toString()); console.log("///////////////////////////2"); console.log(getThisFuncName(this.value)); }}
function descriptExec(execClass){ let descriptorObj = Object.getOwnPropertyDescriptors(execClass.prototype); let descriptorKeys = Object.keys(descriptorObj); descriptorKeys = removeItemsByValues(descriptorKeys, ["constructor"]); for(let i = 0; i < descriptorKeys.length; i++){ descriptorObj[descriptorKeys[i]].value(); }}
function main(){ descriptExec(MyClass);}出力:
{ value: [Function: myFunction1], writable: true, enumerable: false, configurable: true }=============================1myFunction1(){ console.log(this); console.log("=============================1"); console.log(this.value.toString()); console.log("///////////////////////////1"); console.log(getThisFuncName(this.value)); }///////////////////////////1myFunction1{ value: [Function: myFunction2], writable: true, enumerable: false, configurable: true }=============================2myFunction2(){ console.log(this); console.log("=============================2"); console.log(this.value.toString()); console.log("///////////////////////////2"); console.log(getThisFuncName(this.value)); }///////////////////////////2myFunction2descriptorObjに入っているのが、プロパティディスクリプタです。ー①
そして、descriptorKeysにconstructorを含めたメソッドを全て取得します。今回は、MyClassが持っているメソッドを全て取得します。ー②
この処理で、MyClassはnewとかでインスタンス化しておらず、constructorを呼び出すのはマズいのでconstructorは呼び出す処理からremoveItemsByValues()で除外します。ー③
そうしたら、for文で1つずつ関数を実行していきます。ー④
function removeItemsByValues(array, values){ if(!Array.isArray(array)){ throw new TypeError("array must be array type."); } if(!Array.isArray(values)){ throw new TypeError("values must be array type."); } let index = -1; for(let i = 0; i < values.length; i++){ index = array.indexOf(values[i]); if(index !== -1){ array.splice(index, 1); } } return array;}
class MyClass{ myFunction1(){ console.log(this); console.log("=============================1"); console.log(this.value.toString()); console.log("///////////////////////////1"); console.log(getThisFuncName(this.value)); } myFunction2(){ console.log(this); console.log("=============================2"); console.log(this.value.toString()); console.log("///////////////////////////2"); console.log(getThisFuncName(this.value)); }}
function descriptExec(execClass){ let descriptorObj = Object.getOwnPropertyDescriptors(execClass.prototype); // ー① let descriptorKeys = Object.keys(descriptorObj); // ー② descriptorKeys = removeItemsByValues(descriptorKeys, ["constructor"]); // ー③ for(let i = 0; i < descriptorKeys.length; i++){ descriptorObj[descriptorKeys[i]].value(); // ー④ }}
function main(){ descriptExec(MyClass);}すると、注目して欲しい点が2つありまして、
1つ目は、呼び出し場所でthisにメソッドをバインディングする必要がない点です。 今まで、main()処理(今回だと実行場所はdescriptExec())でbindしていたのですが、今回はバインドしていません。
2つ目は、メソッド名をするための記述です。今まで、this.toString()で関数・メソッドを取得していましたが、今回はthis.value.toString()で取得しています。 このthisがどうバインディングされたのかは正直のところ分かりませんが、プロパティディスクリプタから関数を呼び出すと挙動が何か変わっています。
class MyClass{ myFunction1(){ console.log(this); console.log("=============================1"); console.log(this.value.toString()); // ← this.toString()じゃない。 console.log("///////////////////////////1"); console.log(getThisFuncName(this.value)); } myFunction2(){ console.log(this); console.log("=============================2"); console.log(this.value.toString()); // ← this.toString()じゃない。 console.log("///////////////////////////2"); console.log(getThisFuncName(this.value)); }}
function descriptExec(execClass){ let descriptorObj = Object.getOwnPropertyDescriptors(execClass.prototype); let descriptorKeys = Object.keys(descriptorObj); descriptorKeys = removeItemsByValues(descriptorKeys, ["constructor"]); for(let i = 0; i < descriptorKeys.length; i++){ descriptorObj[descriptorKeys[i]].value(); }}
function main(){ descriptExec(MyClass);}おしまい



参考


以上になります!
記事を共有
この記事が役に立ったなら、ぜひ他の人と共有してください!