JavaScript を使用して js インタープリターを作成する

hzc
リリース: 2020-07-02 09:33:02
転載
2974 人が閲覧しました

js を使用して js をコンパイルする 高機能に見えますが、実際の原理は非常に単純です。js を使用することに他なりません オブジェクトのプロパティは文字列で表現できます この機能はまさに黒魔術で実装されています。
これが非常に深いように見える理由は、おそらく、インターネット上の既存のチュートリアルが常に babylon / @babel/parser で始まるからです。最初に皆さんに見てもらいましょう。長いリストAST のコード、およびコードの長いリスト、 を直接再帰的に AST してあらゆるタイプのノードを処理します。
そこで今日この記事を書く目的は、jsを学び始めたばかりの人でも理解できる、分かりやすいjs2jsのチュートリアルを提供することです。

最初に効果を見てみましょう

JavaScript を使用して js インタープリターを作成する最も単純なインタプリタ

前述したように、js には特徴があります

Object属性は文字列で表現できます

, たとえば、console.log は console['log'] と同等です。この機能に基づいて、互換性の低い非常に粗雑なプロトタイプを作成できます。<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">  function callFunction(fun, arg) {     this[fun](arg);   }   callFunction('alert', 'hello world');   // 如果你是在浏览器环境的话,应该会弹出一个弹窗</pre><div class="contentsignin">ログイン後にコピー</div></div> は単純なバージョンです。問題はたくさんあるはずです。js の構文は関数呼び出しだけではありません。黒魔術を使用して代入がどのように実装されるかを見てみましょう。

  function declareVarible(key, value) {

    this[key] = value;

  }

  declareVarible.call(window, 'foo', 'bar');

  // window.foo = 'bar'
ログイン後にコピー

ヒント: const オブジェクトを使用できますこれを実現する .defineProperty;

上記のコードを理解できれば、
js インタープリター

の基本原理はすでに理解していることになります。あなたは私を責めなければなりません。 少し強化してください

ご覧のとおり、上記では便宜上、関数呼び出しを

callFunction('alert', 'hello world');

として記述しましたが、見てください、これは js インタープリターのようには見えません ,私たちが必要とするインタープリターは少なくとも次のようになるはずです parse('alert("hello world")'')
それでは、少し修正を加えてみましょう。ここで babel を導入する必要があります。しかし、まだ心配する必要はありません。解析する構文ツリー (AST) も非常に単純です。

import babelParser from '@babel/parser';

const code = 'alert("hello world!")';

const ast = babelParser.parse(code);
ログイン後にコピー

上記のコードは次のコンテンツを解析します
{
  "type": "Program",
  "start": 0,
  "end": 21,
  "body": [
    {
      "type": "ExpressionStatement",
      "start": 0,
      "end": 21,
      "expression": {
        "type": "CallExpression",
        "start": 0,
        "end": 21,
        "callee": {
          "type": "Identifier",
          "start": 0,
          "end": 5,
          "name": "alert"
        },
        "arguments": [
          {
            "type": "Literal",
            "start": 6,
            "end": 20,
            "value": "hello world!",
            "raw": "\"hello world!\""
          }
        ]
      }
    }
  ],
  "sourceType": "module"
}
ログイン後にコピー

上記のコンテンツはたくさんあるように見えますが、実際に使用するのは実際にはほんの一部です。最初に到達したフィールドを削除します

{
  "type": "Program",
  "body": [
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "CallExpression",
        "callee": {
          "type": "Identifier",
          "name": "alert"
        },
        "arguments": [
          {
            "type": "Literal",
            "value": "hello world!",
          }
        ]
      }
    }
  ],
}
ログイン後にコピー

まず、AST

ExpressionStatement の属性名

type
    を持つすべてのデータを参照してみましょう。
  1. CallExpression
  2. Identifier
  3. Literal
  4. 全部で 4 種類あります。これら 4 種類のノードを最も単純なものから順に個別に分析します。

Literal

{
    "type": "Literal",
    "value": "hello world!",
}
ログイン後にコピー

リテラル コンテンツの場合、必要なのは直接返すことができる value 属性だけです。

if(node.type === 'Literal') {
    return node.value;
}
ログイン後にコピー

非常に単純ではないでしょうか。

Identifier

{
    "type": "Identifier",
    "name": "alert"
},
ログイン後にコピー

Identifier も非常に単純です。すでに存在する変数を表します。変数名は、node.name です。既存の変数なので、その値は何ですか?

if(node.type === 'Identifier') {
    return {
      name: node.name,
      value:this[node.name]
    };
}
ログイン後にコピー

node.name

から取得した上記の alert は文字であり、this['xxxxx'] を通じてアクセスできます。現在のスコープ内の識別子 (Identifier) に変換します (ここがウィンドウです)ExpressionStatement

{
    "type": "ExpressionStatement",
    "expression": {...}
}
ログイン後にコピー

これは実際には非常に単純です。実質的な内容はなく、実際の内容はすべて含まれています

expression

属性を使用するため、expression の内容を直接返すことができます<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">if(node.type === 'ExpressionStatement') {     return parseAstNode(node.expression); }</pre><div class="contentsignin">ログイン後にコピー</div></div>CallExpression

CallExpression は文字通り関数呼び出し式を意味しますが、これは少し面倒です

{
    "type": "CallExpression",
    "callee": {...},
    "arguments": [...]
}
ログイン後にコピー

CallExpression には必要なフィールドが 2 つあります。

callee は関数への参照で、内部のコンテンツは識別子であり、上記のメソッドを使用して処理できます。
  1. 引数内の内容は、呼び出し時に渡されるパラメータ配列です。現在処理する必要があるのはリテラルであり、上記の処理メソッドがすでにあります。 it
  2. if(node.type === 'CallExpression') {
    
        // 函数
        const callee = 调用 Identifier 处理器
    
        // 参数
        const args = node.arguments.map(arg => {
          return 调用 Literal 处理器
        });
    
        callee(...args);
    }
    ログイン後にコピー
  3. Code
ここでは、上記のプロセスを通じて実行できる簡単な実装を示しますが、実行できるのは上記のプロセスのみであり、他の機能はまだ実装されていません。

https://github.com/noahlam/pr...

その他の実装方法

上で紹介した最も面倒な方法に加えて、実際には、js文字列コードを直接実行するにはいくつかの方法があります

##挿入スクリプト DOM

  const script = document.createElement("script");
  script.innerText = 'alert("hello world!")';
  document.body.appendChild(script);
ログイン後にコピー

#eval

eval('alert("hello world!")')
ログイン後にコピー
  1. new 関数
new Function('alert("hello world")')();
ログイン後にコピー
  1. setTimeout Family
setTimeout('console.log("hello world")');
ログイン後にコピー
    しかし、これらはすべてミニ プログラムで容赦なくブロックされます...
  1. 最後に、
  2. を推奨します。フロントエンドラーニングアドバンストインターナルコミュニケーショングループ 685910553
(フロントエンド情報共有) 地球上のどこにいても、
    勤続年数に関係なく、ぜひご参加ください。 (グループは、グループのオーナーが集めた無料の学習本や資料、インタビューの質問と回答をまとめた資料を定期的に提供します。)
  1. この記事に異議がある場合は、コメント欄に書き込んでください。あなたのコメントを記事にしてください。

この記事が興味深いと思われた場合は、共有して転送してください。また、記事をフォローして、記事に対する認識と励ましを表明することもできます。

皆さんがプログラミングの道をどんどん進んでいけることを願っています。

推奨チュートリアル: 「JS チュートリアル

以上がJavaScript を使用して js インタープリターを作成するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:jianshu.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!