はじまり
ぐああ〜、関数の名前が欲しいんだが、thisで取得できないのか? Pythonのselfみたく出来ると思ったんだが・・・
あ〜、テストクラスとか作ると欲しくなるよね。JavaScriptは、thisを使ったときの挙動が、呼び出し元でどう呼び出すかで変わるんだよねえ。
なんてこった・・・。解説頼む!
今回の問題点
今回の問題点は、以下のように関数を実行すると、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 }
=============================1
myFunction1(){
console.log(this);
console.log("=============================1");
console.log(this.value.toString());
console.log("///////////////////////////1");
console.log(getThisFuncName(this.value));
}
///////////////////////////1
myFunction1
{ value: [Function: myFunction2],
writable: true,
enumerable: false,
configurable: true }
=============================2
myFunction2(){
console.log(this);
console.log("=============================2");
console.log(this.value.toString());
console.log("///////////////////////////2");
console.log(getThisFuncName(this.value));
}
///////////////////////////2
myFunction2
descriptorObjに入っているのが、プロパティディスクリプタです。ー①
そして、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);
}
おしまい
ふう・・・、何とか取得できたな・・・
実行中の関数名って地味に使うけど、取得するのは少し面倒いよな。
少しでも、この記事が助けになれば嬉しいです。
嬉しいぞ!
参考
この方の記事に、更に詳しく”this”のことについて記載されていて、参考になりました。
以上になります!
コメント