[EffectiveJavaScript輪読会4]非標準のスタック調査プロパティを使うのは避けよう

GAS

どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。

前回のおさらい

前回は、「関数のtoStringメソッドに依存するのは避けよう」をお届けしました。

[EffectiveJavaScript輪読会4]関数のtoStringメソッドに依存するのは避けよう
どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。前回のおさらい前回は、「コードをカプセル化するに...

今回は、「非標準のスタック調査プロパティを使うのは避けよう」 をお届けします。

テキスト第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本まとめてお届けします。

このシリーズの目次

タイトルとURLをコピーしました