やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

JSでクラスを文字列から動的生成する3つの方法

JSでクラスを文字列から動的生成する3つの方法

 eval(),Function(),import()

成果物

方法

  1. eval()
  2. Function()
  3. import()

コード

eval()

function getClass1(className) {
  return eval(className);
}

Function()

function getClass2(className){
  return Function(`return (${className})`)();
}

import()

async function getClass3(className) {
    const module = await import(`./parser/${className}.js`);
    return module.default;
}

使い分け

 ES Moduleを使うならimport()、使わないならFunction()で実装する。eval()はどちらでも使える。

コード ES Module
import() 使う
Function() 使わない
eval() 使う/使わない

問題

コード 問題
eval() セキュリティ問題あり(インジェクション攻撃を受けうる)。ES Module仕様時にエラーで使えない。
Function() ES Module仕様時にエラーで使えない。
import() パス指定が必要(呼出ファイルからの相対パスで指定せねばならない)。Promise(またはasync,await)の形で受け取って実装せねばならない。

 これからの時代はimport()を使うべき。だが一気にコードが面倒になる。また、webpackでのビルドができないなどの問題にぶつかる。実行環境が少なくなってしまう。

通化

 ES Moduleを使う/使わないによってコードを使い分けねばならない。そんなの嫌だ。なんとか共通化できないか。

eval()は共通化できる

 さすがeval()先生。無敵。なんでもできる。セキュリティ? 知らんな。かまわず実行しちまうんだぜ。

import()は共通化できない

 import()はES Moduleを使うときでないと使えない。undefinedが返される。newするときに型エラーで怒られる。

Uncaught (in promise) TypeError: cls3 is not a constructor

Function()は共通化できない

 import()はES Moduleを使うときでないと使えない。ならFunction()はES Moduleを使う/使わないに限らずどちらの場面でも使えるのでは? と思うだろうが、使えない。

 Function()はES Moduleのとき未定義エラーになる。動的生成したいクラスが未定義であると怒られる。

Uncaught (in promise) ReferenceError: Human is not defined

コスト

 どの方法を使うのが一番楽か。ES Moduleを使わずFunctionにするのが一番楽なのでは?

コード インジェクション攻撃対策 ES Module HTML<script> export,import default Promise(async,await)
import() 使う 1つ(type="module" 全クラス 要判断 要判断
Function() 使わない 全部網羅 不要 不要 不要
eval() どちらでも どちらでも どちらでも どちらでも どちらでも

 コストはファイル数に依存する。ここでは5ファイルと仮定する。たとえばESModuleを使わないときはファイル数が多いほどHTMLで<script>を網羅するコストが高い。依存順を考えねばならないのもコスト増大の要因である。かといってESModuleを使ったときもコストが跳ね上がる。全クラスファイルに対してexport,import,default,Promise(async,await)を適切に判断して実装せねばならないから。

 インジェクション攻撃対策のコストはファイル数に依存しない。どこまで許すか。どう実装するか。それによってコストが大きく変わる。それを考えねばならないだけでも多大なコスト&リスクである。固定値にはできないため5と仮定する。

 eval()以外はESModuleを使う/使わないどちらか一方のときのみしか使えない。もし変更になったら多大な修正をせねばならない。そのリスクをコスト3と仮定する。

項目 コスト
ES Module使う/使わない変更可能性 3
インジェクション攻撃対策 5
HTML<script> 5
export,import 5
default 5
Promise(async,await) 5
コード インジェクション攻撃対策 ES Module HTML<script> export,import default Promise(async,await)
import() 0 使う 3 1 5 5 5
Function() 0 使わない 3 5 0 0 0
eval() 5 使う 0 1 5 5 5
eval() 5 使わない 0 5 0 0 0
コード コスト
import() 19
Function() 8
eval() ESM 21
eval() 非ESM 10

 ランキングは以下。

ESM コード コスト
Function() 8
eval() 10
import() 19
eval() 21

 ESModuleを使わないFunction()がもっとも低コスト。

 さらにここでは考慮していないが、ESMを使うと実行環境も限定されてしまう。ESMは開発コストだけでなく再現性が低いというリスクがある。よって開発・実行どちらの面をとってもESMを使わないほうがよいという結論になってしまった。

 import,exportとかイチイチ書かなくてもコードから自動で解析してくれたら違っただろうに。あと、import()を使ったら必然的にPromise(async,await)を使わねばならないというのもキツイ。async,awaitに至っては芋づる式に呼び出し元までそれを付与せねばならないから辛すぎる。

対象環境

$ uname -a
Linux raspberrypi 5.4.83-v7l+ #1379 SMP Mon Dec 14 13:11:54 GMT 2020 armv7l GNU/Linux