どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。
前回のおさらい
前回は、「関数のtoStringメソッドに依存するのは避けよう」をお届けしました。
今回は、「非標準のスタック調査プロパティを使うのは避けよう」 をお届けします。
テキスト第3章「関数の扱い」の項目29に対応しています。
今日のアジェンダ
- コールスタックとは
- コールスタックの可視化
- callerプロパティ
- いろいろな不都合
コールスタックとは
関数は、呼ばれると、「ただいま、この関数は呼び出され中です」ということをメモリに書き込みます。
そして関数を抜けると、メモリは解放されます。
関数がネストされていると、メモリ上に積み上げるように、書き込みます。
もちろん、function2を抜けると、メモリは解放され、function1の処理を続けます(function1に戻ってくるという理解が大切です)。
最終的には、function1も抜け、メモリは解放されます。
このように、メモリを積み上げるのは、元の場所にもどることが楽になるからです。
このような仕組みを、コールスタックと呼びます。
コールスタックの可視化
プログラミング言語の歴史をみると、コールスタックを可視化したい、というニーズは常にあったようです。
関数は複雑にネストされ、どこから呼ばれているのか、関数同士が呼んだり呼ばれたり、の管理が大変だからです。
現在実行している関数を調べるのは、arguments.calleeというプロパティです。
arguments.calleeプロパティはnameプロパティをもっており、関数名を取得できます。
function myFunction4_29_01() {
console.log(arguments.callee); //[Function: myFunction4_29_00]
console.log(arguments.callee.name); //myFunction4_29_00
}
これは、自分自身を呼び出す再帰関数に使えますが、関数名でも書けますので、あえてarguments.calleeプロパティを使うことはないというのがテキストの主張です。
アロー関数でも書けますが、可読性が著しく悪くなります。
function myFunction4_29_02() {
const factorial = function (n) {
return (n <= 1) ? 1 : (n * arguments.callee(n - 1));
};
//全ての整数の積、階乗です
console.log(factorial(1)); //1
console.log(factorial(2)); //2
console.log(factorial(3)); //6
console.log(factorial(4)); //24
console.log(factorial(5)); //120
const factorial2 = function (n) {
return (n <= 1) ? 1 : (n * factorial(n - 1));
};
//全ての整数の積、階乗です
console.log(factorial2(2)); //2
console.log(factorial2(5)); //120
//アロー関数も可(可読性が落ちる)
const factorial3 = n => (n <= 1) ? 1 : (n * factorial(n - 1));
console.log(factorial3(2)); //2
console.log(factorial3(5)); //120
}
callerプロパティ
一方、argumentsオブジェクトには、calleeプロパティの他にcallerプロパティも付与されます。
calleeプロパティが実行中の関数を取得するのに対し、callerプロパティは、自分が誰から呼び出されているのかを取得するプロパティです。
※現在は、arguments.callerは廃止されています。かわりにFunction.callerが動作します。
このプロパティは、廃止された arguments オブジェクトの arguments.caller プロパティを置き換えます。 引用元:Function.caller
function myFunction4_29_03() {
function func() {
console.log(arguments.callee); // [Function: func]
console.log(arguments.caller); //undefined
//Function.callerプロパティ
console.log(func.caller); //[Function: myFunction4_29_03]
console.log(func.caller.name); //myFunction4_29_03
}
func();
}
いろいろな不都合
テキストの写経です。無限ループしたり、いろいろ不都合があるようですが、よくわかりませんでした。
function myFunction4_29_03() {
function revealCaller() { return revealCaller.caller;}
function start() { return revealCaller(); }
console.log(start() === start); //true
}
function myFunction4_29_04() {
function getCallSack() {
const stack = [];
for (let f = getCallSack.caller; f; f = f.caller) {
stack.push(f);
}
return stack;
}
function f1() { return getCallSack(); }
function f2() { return f1(); }
const trace = f2();
console.log(trace);
/*
[ [Function: f1],
[Function: f2],
[Function: myFunction4_29_04],
[Function] ]
*/
}
function myFunction4_29_05() {
function getCallSack() {
const stack = [];
for (let f = getCallSack.caller; f; f = f.caller) {
stack.push(f);
}
return stack;
}
function f(n) { return n === 0 ? getCallSack() : f(n - 1); }
const trace = f(1); //無限ループ
}
function myFunction4_29_06() {
function f() {
// 'use strict'
return f.caller;
}
f(); //TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
}
実務で使うなら
もし、実務で使うなら、このようなコードからヒントを得るかもしれません。
function myFunction1() {
myFunction2();
}
function myFunction2() {
myFunction3();
console.log(myFunction2.caller.name); //myFunction1
console.log(arguments.callee.name); //myFunction2]
}
function myFunction3() {
console.log(myFunction3.caller.name); //myFunction2
console.log(arguments.callee.name); //myFunction3]
}
まとめ
以上で、「非標準のスタック調査プロパティを使うのは避けよう」をお届けしました。
デバッガとコールスタックを眺めるのも楽しかったですが、今回は割愛します。
次回は、4章に突入で、 「prototype、getPrototypeOf、__proto__の違いを理解する」、「__proto__よりもObject.getPrototypeOfが好ましい」 を2本まとめてお届けします。
このシリーズの目次
- [EffectiveJavaScript輪読会4]固定レシーバを持つメソッドを抽出するにはbindを使う
- [EffectiveJavaScript輪読会4]関数をカリー化するには、bindを使う
- [EffectiveJavaScript輪読会4]コードをカプセル化するには、文字列ではなくクロージャを使う
- [EffectiveJavaScript輪読会4]関数のtoStringメソッドに依存するのは避けよう
- [EffectiveJavaScript輪読会4]非標準のスタック調査プロパティを使うのは避けよう
- [EffectiveJavaScript輪読会4]prototype、getPrototypeOf、__proto__の違いを理解する
- [EffectiveJavaScript輪読会4]__proto__は決して変更しないこと