Heim >Web-Frontend >js-Tutorial >Schreiben Sie einen JS-Interpreter mit JavaScript

Schreiben Sie einen JS-Interpreter mit JavaScript

hzc
hzcnach vorne
2020-07-02 09:33:023023Durchsuche

Die Verwendung von js 编译 js scheint eine High-End-Sache zu sein, aber das eigentliche Prinzip ist eigentlich sehr einfach. Es ist nichts weiter als schwarze Magie, die durch die Verwendung der Funktion von js 对象属性可以用字符串表示 realisiert wird. Der Grund, warum
so aussieht 深奥, liegt wahrscheinlich darin, dass die vorhandenen Tutorials im Internet immer zuerst einen babylon / @babel/parser geben, allen eine große Liste von AST zeigen und dann eine große Liste davon veröffentlichen ,
Direkt rekursiver AST zur Verarbeitung aller Knotentypen Am Ende wurden Anfänger erfolgreich abgeschreckt.

Der Zweck des heutigen Schreibens dieses Artikels besteht darin, Ihnen ein js2js-Tutorial zu geben, das leicht zu verstehen ist und auch von denen verstanden werden kann, die js gerade erst gelernt haben.

Werfen wir zunächst einen Blick auf den Effekt

Schreiben Sie einen JS-Interpreter mit JavaScript

Ein einfachster Interpreter

Wie oben erwähnt, verfügt js über eine Funktion, die, zum Beispiel ist console.log äquivalent zu console['log'], also können wir basierend auf dieser Funktion einen extrem schlechten und groben Prototyp schreiben 对象属性可以用字符串表示

  function callFunction(fun, arg) {

    this[fun](arg);

  }

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

  // 如果你是在浏览器环境的话,应该会弹出一个弹窗
Da es sich um eine vereinfachte Version handelt, muss es so sein Es gibt viele Probleme bei der Syntax in js. Sehen wir uns an, wie die Zuweisung mit schwarzer Magie implementiert wird.

  function declareVarible(key, value) {

    this[key] = value;

  }

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

  // window.foo = 'bar'
Tipps: const kann mit Object.defineProperty;

implementiert werden

Wenn Sie den obigen Code verstehen, bedeutet das, dass Sie die Grundprinzipien von

bereits verstehen. Wenn Sie ihn nicht verstehen, können Sie mir nur die Schuld geben. js 解释器

Leicht verstärken

Wie Sie oben sehen können, haben wir der Einfachheit halber den Funktionsaufruf als

geschrieben, aber er sieht überhaupt nicht wie callFunction('alert', 'hello world'); aus, js 解释器 in In unserem Kopf sollte der gewünschte Interpreter mindestens so aussehen
, also nehmen wir eine kleine Änderung vor. Hier müssen wir Babel einführen, parse('alert("hello world")'') aber keine Sorge, der von uns analysierte Syntaxbaum (AST) ist auch sehr einfach von.

import babelParser from '@babel/parser';

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

const ast = babelParser.parse(code);
Der obige Code analysiert den folgenden Inhalt

{
  "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"
}
Der obige Inhalt scheint viel zu sein, aber was wir tatsächlich verwenden, ist eigentlich nur ein kleiner Teil. Vereinfachen wir ihn ein wenig und speichern wir ihn die Dinge, die vorerst nicht benötigt werden. Entfernen Sie die Felder, die zuerst eintreffen

{
  "type": "Program",
  "body": [
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "CallExpression",
        "callee": {
          "type": "Identifier",
          "name": "alert"
        },
        "arguments": [
          {
            "type": "Literal",
            "value": "hello world!",
          }
        ]
      }
    }
  ],
}
Lassen Sie uns zunächst alle Daten mit Attributnamen

im AST type

    ExpressionStatement durchsuchen
  1. CallExpression
  2. Identifier
  3. Literal
Es gibt insgesamt 4 Typen, dann werden wir diese 4 Knotentypen separat analysieren, beginnend mit das einfachste

Literal

{
    "type": "Literal",
    "value": "hello world!",
}

Für Literal-Inhalte benötigen wir lediglich ein Wertattribut, das direkt zurückgegeben werden kann.

if(node.type === 'Literal') {
    return node.value;
}
Ist es nicht ganz einfach?

Bezeichner

{
    "type": "Identifier",
    "name": "alert"
},

Bezeichner ist auch sehr einfach. Er stellt eine Variable dar, die wir bereits haben. Da es sich um eine vorhandene Variable handelt, ist ihr Wert Was.

if(node.type === 'Identifier') {
    return {
      name: node.name,
      value:this[node.name]
    };
}
Das obige

, das wir von alert erhalten, ist ein Zeichen, über node.name können wir auf den Bezeichner (Identifier) ​​​​this['xxxxx']ExpressionStatement

{
    "type": "ExpressionStatement",
    "expression": {...}
}

Das ist eigentlich ganz einfach, es gibt keinen inhaltlichen Inhalt, der eigentliche Inhalt liegt im Attribut

, sodass Sie den Inhalt des Ausdrucks

if(node.type === 'ExpressionStatement') {
    return parseAstNode(node.expression);
}
expression CallExpressionCallExpression bedeutet wörtlich ein Funktionsaufrufausdruck, was etwas problematischer ist

{
    "type": "CallExpression",
    "callee": {...},
    "arguments": [...]
}

CallExpression hat zwei Felder, die wir brauchen:

callee ist ein Verweis auf die Funktion, und der Inhalt darin ist ein Bezeichner, der mit der oben genannten Methode verarbeitet werden kann. Der Inhalt in den

    Argumenten ist das beim Aufruf übergebene Parameterarray. Was wir derzeit verarbeiten müssen, ist ein Literal, das gleiche wie oben Es gibt bereits Möglichkeiten, damit umzugehen.
  1. Nachdem ich das gesagt habe, glaube ich, dass Sie bereits wissen, wie es geht
  2. if(node.type === 'CallExpression') {
    
        // 函数
        const callee = 调用 Identifier 处理器
    
        // 参数
        const args = node.arguments.map(arg => {
          return 调用 Literal 处理器
        });
    
        callee(...args);
    }
Code

Hier ist eine einfache Implementierung, die kann ausgeführt werden Der obige Prozess kann nur ausgeführt werden, andere Funktionen wurden noch nicht implementiert.

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

Andere Implementierungsmethoden

Zusätzlich zu der umständlichsten Methode, die ich oben vorgestellt habe, tatsächlich js Es gibt mehrere Möglichkeiten, String-Code direkt auszuführen

Skript-DOM einfügen

      const script = document.createElement("script");
      script.innerText = 'alert("hello world!")';
      document.body.appendChild(script);
  1. eval
    eval('alert("hello world!")')
  1. neue Funktion
    new Function('alert("hello world")')();
  1. setTimeout-Familie
    setTimeout('console.log("hello world")');
  1. Diese werden jedoch im Miniprogramm rücksichtslos blockiert...
  2. Abschließend geben wir ein
Front-End-Learning-Gruppe für fortgeschrittene interne Kommunikation 685910553

(Front-End-Informationsaustausch), egal wo auf der Welt Sie sind,
egal wie viele Jahre Sie schon arbeiten, Sie sind herzlich willkommen, beizutreten! (Die Gruppe stellt regelmäßig kostenlose Lernbücher und Materialien zur Verfügung, die vom Gruppeninhaber gesammelt wurden, sowie zusammengestellte Interviewfragen und Antwortdokumente!) Wenn Sie Einwände gegen diesen Artikel haben, schreiben Sie bitte im Kommentarbereich des Schreiben Sie Ihre Kommentare.

Wenn Sie diesen Artikel interessant finden, teilen und leiten Sie ihn bitte weiter, oder Sie können ihm auch folgen, um Ihre Anerkennung und Ermutigung für unseren Artikel zu zeigen.

Ich hoffe, dass jeder auf dem Weg des Programmierens immer weiter vorankommen kann.

Empfohlenes Tutorial: „JS-Tutorial

Das obige ist der detaillierte Inhalt vonSchreiben Sie einen JS-Interpreter mit JavaScript. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:jianshu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen