JS.next

JavaScriptの最新実装情報を追うブログ

末尾呼び出し最適化が実装された

概要

ある関数Aから別の関数Bを呼び出すとき、処理系は後で戻って来れるように一旦Aの状態を保存し、関数Bの処理に入る。
これが問題になるのは再帰の時で、数万回程度の再帰でスタックが一杯になり、エラーとなってしまう。
しかし、もし関数B呼び出しの際に、関数Aに戻ってきて処理を続ける必要のない形で呼びだされていれば、
状態の保存を省略して関数Bに移行する最適化が可能であり、ES2015でその詳細が定義されることとなった。


具体的には、strictモードの関数で、「 return fn() 」という形での呼び出しについて最適化が有効になる。


最適化が効く例:

function fn( n ) {
  'use strict'
  if ( n <= 0 ) {
    return 'done!'
  }
  return fn( n - 1 )  
    // この関数がする処理はこれ以上ない
}

fn( 1e6 )  // "done!" 


最適化が効かない例:

function fn( n ) {
  'use strict'
  if ( n <= 0 ) {
    return 'done!'
  }
  fn( n - 1 )
    // returnがないと、undefinedを返すという処理が残ってしまう
}

fn( 1e6 )  // RangeError


最適化が効く例:

(()=>{
'use strict'

let fn = n => n <= 0 ? 'done!' : fn( n - 1 )

console.log( fn( 1e6 ) )  // "done!"

})()

// アロー関数の中や、関数呼び出しが式に含まれている場合でも、
// 呼び出し元に戻ってくる必要がない形であれば最適化は効く。


最適化が効かない例:

function fn( n ) {
  'use strict'
  if ( n <= 0 ) {
    return 'done!'
  }
  try {
    return fn( n - 1 )
  } finally {

  }
}

fn( 1e6 )  // RangeError

// fn関数が例外を吐けばfinally節に飛ぶ可能性があるので最適化は効かない。


(※新たなパターンが実装され次第追記予定)


関連記事

明示的な末尾呼び出し最適化構文が実装された


実装されるバージョン

V8 4.10.69