Melchior

バンクーバーで働くエンジニアの備忘録

Async HooksとAsync Resourcesの導入

概要

この記事はNode.js Advent Calendar 2017 15日目の記事です。

カナダから非同期で失礼します。

Async hooksとはNode.jsの非同期イベントをトレースすることができるネイティブライブラリです。まだ試験段階なのでAPIが変わる可能性がありますが、Async Hooksに入門したので、Async HooksとAsync Resouresの使い方、実際の使用例の考察をまとめました。

Node.jsのバージョンはv9.3.0を使用しています。

Async Hooksの基本的な使い方

まずは下記のコードを見てみます。このコードはAsync Resourcesが1秒ごとに生成され、破棄される様子を見ることができます。

const fs = require('fs');
const util = require('util');
const asyncHooks = require('async_hooks');

const hooks = {
  // Resourceが生成されるときに呼ばれる
  init(asyncId, type, triggerAsyncId, resource) {
    const obj = {
      asyncId,
      type,
      triggerAsyncId,
      resource
    };
    fs.writeSync(1, `init\n${util.format(obj)}\n`);
  },
  // Resourceのcallbackが呼ばれる直前に呼ばれる
  before(asyncId) {
    fs.writeSync(1, `before\t${util.format({ asyncId })}\n`);
  },
  // Resourceのcallbackが終了したときに呼ばれる
  after(asyncId) {
    fs.writeSync(1, `after\t${util.format({ asyncId })}\n`);
  },
  // Resourceが破棄されるときに呼ばれる
  destroy(asyncId) {
    fs.writeSync(1, `destroy\t${util.format({ asyncId })}\n`);
  },
  // Promiseのresolveが呼ばれるタイミングで呼ばれる
  promiseResolve(asyncId) {
    fs.writeSync(1, `promiseResolve\t${util.format({ asyncId })}\n`);
  }
};

// Hookの生成
const asyncHook = asyncHooks.createHook(hooks);

// Hookを有効にする
asyncHook.enable();

// 1秒おきにResourceの生成
(function start() {
  // 実行中のasyncId
  const eid = asyncHooks.executionAsyncId();
  // 呼び出し元のasyncId
  const tid = asyncHooks.triggerAsyncId();
  console.log(`start\teid: ${eid} tid: ${tid}`);
  setTimeout(start, 1000);
})();

// 10秒後にhookを無効にする
setTimeout(() => asyncHook.disable(), 10000);

createHookに任意の関数を渡すことで、非同期イベントの検知が簡単にできるようになります。特別難しい機能は無いので、それぞれの用語については下記の表にまとめました。

async_hooks

用語 説明
createHook Function Hookの作成
executionAsyncId Function 実行中のasyncIdの取得
triggerAsyncId Function 呼び出し元のasyncIdの取得

createHook

用語 説明
init Function Resoureが生成される時に呼ばれる
before Function Resourceがcallbackを呼ぶ直前で呼ばれる
after Function Resoureがcallbackを呼び終えた直後に呼ばれる
destroy Function Resoureが破棄される時に呼ばれる
promiseResolve Function PromiseのResourceでresolveが呼ばれる時に呼ばれる
asyncId number Async Resourceに割り当てられたid
triggetAsyncId number 呼び出し元のasyncId
enable Function Hookの有効化
disable Function Hookの無効化

⚠使用上の注意⚠

createHookの関数内でエラーが発生した際はuncaughtExceptionでキャッチすることはできず、スタックトレースを残してプロセスが終了されます。 またconsole.log, process.stdout.writeは非同期オペレーションなためAsync Resourceが生成されます。Hook内の各関数ではfs.writeSyncを使用してください。

スタックトレースを遡る

何か面白いことできないかなぁと探っていたところ、興味深いコードを見つけたので、これをベースにAsync Resouresの誕生から終焉までの軌跡を表示するようにしてみました。 実際のログはこちらです。コードは以下のとおりです。

const delay = util.promisify(setTimeout);
const DELAY = 1000;
const map = new Map();

function init(asyncId, type, triggerAsyncId, resource) {
  const obj = {
    asyncId,
    type,
    triggerAsyncId,
    resource
  };
  Error.captureStackTrace(obj, init);
  map.set(asyncId, [obj.stack, triggerAsyncId]);
  fs.writeSync(1, `init\n${util.format(obj)}\n`);
}

function destroy(asyncId) {
  fs.writeSync(1, `destroy\tasyncId: ${asyncId}\n`);
  showStackTrace(asyncId);
  map.delete(asyncId);
}

function showStackTrace(asyncId) {
  const array = map.get(asyncId);
  if (!array) {
    return;
  }
  const [stack, tid] = array;
  fs.writeSync(1, `stack: \tasyncId: ${asyncId} triggetAsyncId: ${tid}\n${stack.replace(/(.*)\n/, '')}\n`);
  showStackTrace(tid);
}

asyncHooks.createHook({ init, destroy }).enable();

let promise = delay(DELAY);
for (let i = 0; i < 10; i++) {
  promise = promise.then(() => delay(DELAY));
}

スタックトレースを遡ることができるので、デバッグに使えるかもしれません。

メモリリークの検知

以下のコードはAsync Resousesが生成されたものの、リソースが破棄されない例です。mapのサイズが次第に大きくなっていくことが一目でわかるので、メモリリークの検知の早期発見に使えるかもしれません。

const map = new Map();

function init(asyncId) {
  map.set(asyncId, 1);
  fs.writeSync(1, `init\tasyncId: ${asyncId} mapSize: ${map.size}\n`);
}

function destroy(asyncId) {
  map.delete(asyncId);
  fs.writeSync(1, `destroy\tasyncId: ${asyncId} mapSize: ${map.size}\n`);
}

asyncHooks.createHook({ init, destroy }).enable();

const queue = [];
(function start() {
  new Promise(resolve => queue.push(resolve));
  setTimeout(start, 100);
})();

Async Resourcesの基本的な使い方

AsyncResourceクラスを使用することで、任意の非同期イベントを作成することが可能です。emitBeforeemitAfterはセットなので発火する際には必ず両方を呼ぶ必要があります。

const { AsyncResource } = require('async_hooks');

// 自動的にinitが呼ばれる
const asyncResource = new AsyncResource('Async');

// Callbackを呼ぶ直前に呼ぶ
asyncResource.emitBefore();

// Callbackが終了した直後に呼ぶ
asyncResource.emitAfter();

// AsyncResourceが破棄される時に呼ぶ
asyncResource.emitDestroy();

// AsyncResourceのインスタンスに割り当てられたasyncIdを返す
asyncResource.asyncId();

// 呼び出し元のasyncIdを返す
asyncResource.triggerAsyncId();

Async Resourcesの導入

こちらのBluebirdのPRによると、メモリリークの問題も解決し安定してきたようなので、実際にAigleのライブラリに適用してみようと思います。コミットはこちら

まず、Nodeのバージョンがv9.3.0以上推奨とのことでv9.3.0以上のみを有効にしました。

const node = typeof process !== 'undefined' && process.toString() === '[object process]';
const supportAsyncHook = node && (() => {
  const support = '9.3.0';
  const [smajor, sminor] = support.match(/\d+/g);
  const version = process.versions.node;
  const [vmajor, vminor] = version.match(/\d+/g);
  return vmajor > smajor || vmajor === smajor && vminor >= sminor;
})();

次にconstructorAsyncResourceを生成します。

class Aigle {
  constructor(executor) {
    ...
    this._resource = supportAsyncHook && new AsyncResource('PROMISE');
    ...
  }
}

今回はPromiseライブラリなので、Native Promiseと同じtypeを使用しました。 これでAigleインスタンスが生成されるときにinitが呼ばれるようになります。

次に、onFulfilledまたはonRejectedが呼ばれる直前にemitBeforeを、直後にemitAfterを追加します。 簡単な方法としては、

const original = onFulfilled;
onFulfilled = value => {
  this._resource.emitBefore();
  try {
    return original(value);
  } catch (e) {
    throw e;
  } finally {
    this._resource.emitBefore();
  }
};

以上のように追加するのが簡単ですが、パフォーマンスを考慮して別の関数にしました。callbackが呼ばれる直前・直後に追加すれば問題ありません。

AsyncResourceの問題点としてパフォーマンスが著しく低下するため、デフォルトではAsync Resourcesを無効にしています。有効にする方法は、

Aigle.config({ asyncResource: true });

で使用することができます。

パフォーマンスについては後日調査してまた記事を書こうと思います。

まとめ

Async Hooksを使って非同期イベントのトレースを簡単にすることができるようになりました、デバッグにとても便利そうなライブラリです。 またAsync Resourcesを使うことでカスタムな非同期イベントを生成できますが、パフォーマンスに難がありそうです。Immediateイベントは自動的に発行されるので特別Async Resourcesを使う必要はないのかなぁとも思いましたが、あまり詳しくないのでわかりません。もし詳しい方いたらぜひシェアしていただけたらうれしいです。

今後も最新の情報収集とパフォーマンスの調査を行っていきたいと思います。

ソース

Sidekiqのベストプラクティス

概要

Sidekiqのベストプラクティスに沿って、メッセージ機能を実装していきます。

0. Sidekiqとは

Sidekiqとはbackgroundでタスクを処理してくれるライブラリです。日付指定でセットして実行したりもできます、便利そうです。

ベストプラクティスに沿ってWorker用のテーブルを用意します。

# messages
class CreateMessages < ActiveRecord::Migration[5.1]
  def change
    create_table :messages do |t|
      t.integer "sender_id"
      t.integer "recipient_id"
      t.string "body"
      t.timestamps
    end
  end
end
# message_tasks
class CreateMessageTasks < ActiveRecord::Migration[5.1]
  def change
    create_table :message_tasks do |t|
      t.integer "sender_id"
      t.integer "recipient_id"
      t.string "body"
      t.timestamps
    end
  end
end

今回はミニマムのデモのため構造は全く同じですが、実際は送信するユーザが複数だったりプッシュ通知をしたりするかもしれません。

1. ジョブのパラメータを小さく・シンプルに

上記MessageTasksのテーブルを用意することで、引数をtask_idのみにすることができます。

class MessageWorker
  include Sidekiq::Worker

  def perform(task_id)
  end
end

また呼び出し側では以下のようになります。

task = MessageTask.create(sender_id: sender_id,
                          recipient_id: recipient_id,
                          body: body)

MessageWorker.perform_async(task.id)

2. 冪等性とトランザクション

perform内でエラーが発生した場合、自動リトライ機能が働きます。デフォルトでは25回のリトライ後(おおよそ21日後)そのジョブは削除されマニュアルで対応しないといけません。 それを防ぐためにもMessageTasksトランザクションを使用することで、より安全にデータを扱うことができます。

class MessageWorker
  include Sidekiq::Worker

  def perform(task_id)
    task = MessageTask.find(task_id)
    if task.nil?
      return
    end

    ActiveRecord::Base.transaction do
      Message.create(sender_id: task.sender_id,
                     recipient_id: task.recipient_id,
                     body: task.body)
      task.destroy
    end
  end
end

3. Concurrencyの設定

Concurrencyを設定することによりsidekiqの処理を並列化することが可能です。デフォルト値は25スレッドで推奨値は50未満。100以上設定した場合、安定性の問題があるとの。 config/database.ymlを変更することで変えることができます。この値は可変ではないので、用途に応じて変更する必要があるようです。

production:
  adapter: mysql2
  database: foo_production
  pool: 25

まとめ

Sidekiqを使うことで同期処理が必要がないタスクは非同期で処理させることができます。同期処理させる必要ないものは非同期にしてレスポンス速度を改善できそうですね。

リンク

ESLintのメソッドチェインのindentの設定について

概要

ESLintのメソッドチェイン(chain)のindentの設定に問題があり3 -> 4のアップグレードができなかったのですが、なんとか苦戦してなんとかアップグレードした話です。 もっといい方法知っている方いたら教えて欲しいです、ホントに。

問題のコード

例としてarrow functionのショートハンドなどは使用していません。実際のコードはもっと大きいものを想定してください。

さて。

元々のESLintの設定はこうでした。

rules:
  indent:
    - error
    - 2

Array編

最初のchainのfunctionに複数行にまたがるArrayを渡す際、以下のコードはLintエラーになります。

// 1 (Lintエラー)
Promise.all([
  1,
  2,
  3
])
.then(value => {
  console.log(value);
});

解決方法としては、

// 2
Promise.all([
  1,
  2,
  3
])
  .then(value => {
    console.log(value);
  });

// 3
Promise
  .all([
    1,
    2,
    3
  ])
  .then(value => {
    console.log(value);
  });

// 4 (Lintエラー)
Promise.all([
    1,
    2,
    3
  ])
  .then(value => {
    console.log(value);
  });

2は気持ち悪すぎるので不採用として(Lintエラーにして欲しいレベル)、4でLintエラーにするのであれば、もはや1を採用して欲しいです。 3はアリと考えていましたが、以下の例でとてもいただけないので不採用としました。

// 5 (気持ち悪い)
_
  .chain([
    1,
    2,
    3
  })
  .map(n => {
    return n * 2;
  })
  .value();

// 6 (短いときはこう書きたい)
Promise.all([1, 2, 3])
  .then(value => {
    console.log(value);
  });

Function編

Functionのほうがもっと厄介です。

// 7 (Lintエラー)
new Promise(resolve => {
  // do something
  setTimeout(resolve);
})
.then(value => {
  console.log(value);
});

// 8 (Lintエラー)
[1, 2, 3].map(n => {
  return n * 2;
})
.filter(n => {
  return n % 2;
});

解決方法はArrayの例と同じく、

// 9
new Promise(resolve => {
  // do something
  setTimeout(resolve);
})
  .then(value => {
    console.log(value);
  });


// 10
[1, 2, 3].map(n => {
  return n * 2;
})
  .filter(n => {
    return n % 2;
  });

一つの案としては変数を定義することですが、Functionの例で毎度変数を作らないといけなくなるため不採用にしました。

解決方法

IndentにMemberExpressionというオプションがあり、chain時のindentを定義できます。 上記の例を実現したいとき、chainのindentが0だったり2だったりするため、offにすることで回避することができました。

rules:
  indent:
    - error
    - 2
    - MemberExpression: off

まとめ

ESLintのバージョンをアップデートすることで、多くの機能が追加されているのでもっと厳しくチェックしていただけるようになりました。助かります、ありがとうございます。

がしかし、果たしてこれは良いコードなのか…という疑問も残ります。

例えば、

// 11
const obj = {};
const array = _.chain([
  1,
  2,
  3,
]).map(n => {
  return n * 2;
})
.filter(n => {
  return n % 2;
});
obj.a = 1;

この例の場合、arrayのchainがどこで終わりかわかりづらいため、その後に続く行が読みづらい可能性が高いです。

// 12
const obj = {};
const array = _
  .chain([
    1, 
    2, 
    3
  ])
  .map(n => {
    return n * 2;
  })
  .filter(n => {
    return n % 2;
  });
obj.a = 1;

そういう意味ではこう書いたほうが読みやすい可能性も…

リーダブルコードって難しいですね。

リンク

Perforce + Vim

概要

PerforceをVimで仲良くやっていくためのセットアップを考え中なのですが、特に良いライブラリもなく、はたまた需要もそこまで大きくなく… いったん作業ができるようvim-perforceとvimdiffのセットアップをしました。

vim-perforce

ドキュメントによるとP4info, P4edit, P4revertP4movetocl だけサポートしてるそうです。

f:id:suguru03:20171107092146p:plain

f:id:suguru03:20171107092629p:plain

f:id:suguru03:20171107092523p:plain

Vim上で手動でチェックアウトする必要があります。WebStormを使っていると自動チェックアウトができるそうです。 p4 deleteが一番の曲者なのでサポートして欲しいところではあります。

vimdiff

vimdiffのセットアップは一行足すだけでp4 diffvimに置き換わります。

$ export P4DIFF=vimdiff

f:id:suguru03:20171107102935p:plain

これはとても見やすく便利です。

まとめ

vim-perforceを改良したいところではありますが、いったんこの2つさえあれば作業は捗るかと思います。

もう少し効率よくするために、ローカル環境ではgitを用意して、gitの情報を元にPerforceのコマンドを自動化しています(謎)。 Perforceはあまりブランチを作成したり複数のことを同時にしたりすることが難しいので、gitでできることはgitに移行したいところです。

リンク

Perforceのセットアップ for Mac

はじめに

PerforceSubversionのようなバージョン管理システムでbinaryの管理に向いているそうです。今回はp4vというツールとp4というコマンドラインツールを導入したのでそのセットアップ方法について書きます。

p4vのセットアップ

こちらのリンクからmac用のリンクをダウンロードします。

設定はこちらのリンク に沿ってやるだけですが、管理者からServerのアドレスとuser名をいただき入力するだけです。

https://www.perforce.com/perforce/doc.current/manuals/p4v-gs/images/p4v-gs001.png

以上で設定は完了です。

p4vの使い方

ログイン後、始めに差分を取得するためルートディレクトリで右クリックを押しGet Latest Revisionを取得します。 全てのファイルはRead Onlyになっており編集権限がありません。もし編集したい場合はファイルを右クリックでCheck outする必要があります。チェックアウトをすると編集が可能になり、その情報は他のユーザにシェアがされます。編集後、右クリックを押しsubmitすることで他のユーザに変更がシェアされます。 もし差分を見たい場合は、右クリックでDiff Against Have Revisionを押すとp4vdiffが起動し差分を見ることが可能です。

p4のセットアップ

p4vだとマウス操作が多いため、やはりCLIでどうにかしたいところです。そのためp4をインストールします。リンクはこちら

次にパスなどの情報をセットします。 私は.zshrcGithub上で管理しているため、secretな情報を隠すために別ファイルを用意しました。

# ~/.secret.sh
export P4PATH=<server-url>
export P4USER=<user-name>
export P4CLIENT=<workspace-name>
p4 login
# ~/.zshrc
if [ -f $HOME/.secret.sh ]; then
  source $HOME/.secret.sh
fi

上記の.secret.sh.zshrcで読み込み、terminal起動時にp4にログインをしています。これで準備は完了です。

編集したいときは、p4 editを使用することでチェックアウトが可能になります。また他にもいくつかコマンドがあるので試してください。

まとめ

私はgitばかり触っているゆとりですが、少しずつ慣れていこうかなと思っています。 次回はp4 + vimあたりについて書こうと思います。

リンク

Ruby on Railsのセットアップ

はじめに

Rubyデビューしたので初心に戻ってその勉強記録を記して行きたいと思います。

手順は

です。

rbenvのインストール

rbenvは最も有名なバージョン管理ツールらしいので、流行りに乗ってインストールしたいと思います。

…のつもりが、長いことmacを使っていると自ずとrbenvがインストールされていることもあるようで、gem installが通らなかったりしたのでuninstallから始めます。

手順はこちらです。

uninstall? · Issue #148 · rbenv/rbenv · GitHub

$ rm -rf ~/.rbenv
$ brew uninstall rbenv

と、.zshrcからrbenvなる記述を削除しました。

それでは気を取り直してインストールします。

$ brew install rbenv

後は.zshrcに追記します。

$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.zshrc
$ echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.zshrc

Rubyのセットアップ

次にrubyをインストールします。

$ rbenv install 2.4.1

最新版をインストールしたい方はrbenv install -lで最新版をチェックしてください。

rbenv rehash
rbenv global 2.4.1

以上で完了です。

nokogiriのインストール

ここで少しハマったのがmacではnokogiriのインストールをmacのインストレーションに沿ってやらないといけないようなので、別途インストールします。

$ gem update --system
$ xcode-select --install
$ gem install nokogiri

これはmac OS Xというところに記載されています。

Railsのインストール

$ gem install rails

コマンドはこちらだけですね、とても簡単。

アプリの起動

適当なディレクトリに行き、以下のコマンドを叩きます。 チュートリアルに沿って、

$ rails new hello_app
$ cd hello_app
$ rails server

これでhttp://localhost:3000/にアクセスすると以下の画面が表示されます。

f:id:suguru03:20170908081122p:plain

まとめ

とても簡単にRailsサーバが立ち上がりました。

Yay! I'm on Rails!

はい。せっかくなので何かサーバ建てたいですね。

リンク

classの継承についての考察

概要

ES6の継承が遅すぎると話題なので検証・最適化をしました。

どれくらい遅いのか

f:id:suguru03:20170428145927p:plain

こちらベンチマークによると、少しずつ改善は見られるものの、2.3-17倍程度遅い様子です。

実際に検証しました。

  • Node v6.10.1
  • benchmark.js v2.1.4

継承

// classes
function ParentES5(value) {
  this._value = value;
}
function ES5(value) {
  ParentES5.call(this, value);
}
util.inherits(ES5, ParentES5);

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
}
class ES6 extends ParentES6 {
  constructor(value) {
    super(value);
  }
}

// functions
const funcs = {
  'es5': function() {
    return new ES5(1);
  },
  'es6': function() {
    return new ES6(1);
  }
};
**** benchmark.js ****
[1] "es5" 0.000029ms [1.00]
[2] "es6" 0.000045ms [1.56]

単純にclassを使うだけでも1.5倍遅いようです。

super classメソッドの呼び出し

function ParentES5(value) {
  this._value = value;
}
ParentES5.prototype.get = function() {
  return this._value;
};
function ES5(value) {
  ParentES5.call(this, value);
}
util.inherits(ES5, ParentES5);

ES5.prototype.get = function() {
  return ParentES5.prototype.get.call(this) * 2;
};

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
  get() {
    return this._value;
  }
}
class ES6 extends ParentES6 {
  constructor(value) {
    super(value);
  }
  get() {
    return super.get() * 2;
  }
}
const es5 = new ES5(1);
const es6 = new ES6(1);

// functions
const funcs = {
  'es5': function() {
    return es5.get();
  },
  'es6': function() {
    return es6.get();
  }
}
**** benchmark.js ****
[1] "es5" 0.000021ms [1.00]
[2] "es6" 0.000078ms [3.79]

super classメソッドの呼び出しは非常に遅いので避けたいところです。

考察

super classメソッドの呼び出し

function ParentES5(value) {
  this._value = value;
}
ParentES5.prototype.get = function() {
  return this._value;
};
function ES5(value) {
  ParentES5.call(this, value);
}
util.inherits(ES5, ParentES5);

ES5.prototype.get = function() {
  return ParentES5.prototype.get.call(this) * 2;
};

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
  get() {
    return this._value;
  }
}
class ES6 extends ParentES6 {
  constructor(value) {
    super(value);
  }
  get() {
    return ParentES6.prototype.get.call(this) * 2;
  }
}
const es5 = new ES5(1);
const es6 = new ES6(1);

// functions
const funcs = {
  'es5': function() {
    return es5.get();
  },
  'es6': function() {
    return es6.get();
  }
}
**** benchmark.js ****
[1] "es6" 0.000020ms [1.00]
[2] "es5" 0.000020ms [1.02]

super.get()の代わりにsuper classメソッドをcallすることでes5と同等のパフォーマンスが得られました。これは使えそうです。

こちらのパターンも同じような結果が得られます。

function ParentES5(value) {
  this._value = value;
}
function getES5() {
  return this._value;
};
function ES5(value) {
  ParentES5.call(this, value);
}
util.inherits(ES5, ParentES5);

ES5.prototype.get = function() {
  return getES5.call(this) * 2;
};

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
}
function getES6() {
  return this._value;
};
class ES6 extends ParentES6 {
  constructor(value) {
    super(value);
  }
  get() {
    return getES6.call(this) * 2;
  }
}
const es5 = new ES5(1);
const es6 = new ES6(1);

// functions
const funcs = {
  'es5': function() {
    return es5.get();
  },
  'es6': function() {
    return es6.get();
  }
}
**** benchmark.js ****
[1] "es5" 0.000023ms [1.00]
[2] "es6" 0.000023ms [1.00]

こちらもなかなか使いどころがありそうです。

継承

ParentES6.prototype.constructorへのアクセスは禁止されているため、classをextendsしsuperを呼び出す必要があります。

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
}

class ES6 extends ParentES6 {
  constructor(value) {
    super(value);
  }
}
// エラー
class ES6 {
  constructor(value) {
    ParentES6.prototype.constructor.call(this, value); 
  }
}
// TypeError: Class constructor ParentES6 cannot be invoked without 'new'

今のところたどり着いた最速の継承はこちらです。

function ParentES5(value) {
  this._value = value;
}
function ES5(value) {
  ParentES5.call(this, value);
}
util.inherits(ES5, ParentES5);

class ParentES6 {
  constructor(value) {
    this._value = value;
  }
  get() {
    return this._value;
  }
}
function ES6(value) {
  this._value = value;
}
ES6.prototype = ParentES6.prototype;

const es5 = new ES5(1);
const es6 = new ES6(1);
console.log(es6.get()); // 1
console.log(es6 instanceof ParentES6); // true

// functions
const funcs = {
  'es5': function() {
    return es5.get();
  },
  'es6': function() {
    return es6.get();
  }
}
[1] "es6" 0.000020ms [1.00]
[2] "es5" 0.000026ms [1.33]

es5の継承よりも高速な継承が可能となりました。もう少し検証が必要ですが、instanceofがtrueとみなされるのでこちらは意外と使えるかもしれません。 こちらの案はthamminさんがベースを提供してくれました。

まとめ

superクラスの呼び出しを避ける考察でした。ns,μsレベルのチューニングなので普通にsuperを呼んでいただければと思います。

JavaScriptっておもしろい。

リンク