用JavaScript寫一個(gè)js解釋器

用 js 來編譯 js看起來是個(gè)高大上的東西,實(shí)際原理其實(shí)很簡單,無非就是利用 js對象屬性可以用字符串表示這個(gè)特性來實(shí)現(xiàn)的黑魔法罷了。
之所以看起來那么深奧, 大概是由于網(wǎng)上現(xiàn)有的教程,都是動不動就先來個(gè)babylon / @babel/parser先讓大家看個(gè)一大串的AST, 然后再貼出一大串的代碼,
直接遞歸 AST 處理所有類型的節(jié)點(diǎn). 最后新手就成功被嚇跑了。

成都創(chuàng)新互聯(lián)專注于博湖網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供博湖營銷型網(wǎng)站建設(shè),博湖網(wǎng)站制作、博湖網(wǎng)頁設(shè)計(jì)、博湖網(wǎng)站官網(wǎng)定制、重慶小程序開發(fā)服務(wù),打造博湖網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供博湖網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。

那么今天我寫這篇的目的,就是給大家一個(gè)淺顯易懂,連剛學(xué) js 的人都能看懂的 js2js 教程。

先來看一下效果

一個(gè)最簡單的解釋器

上面有提到,js 有個(gè)特性是對象屬性可以用字符串表示,如 console.log 等價(jià)于 console['log'], 辣么根據(jù)這個(gè)特性,我們可以寫出一個(gè)兼容性極差,極其簡陋的雛形

function callFunction(fun, arg) {

    this[fun](arg);

  }

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

  // 如果你是在瀏覽器環(huán)境的話,應(yīng)該會彈出一個(gè)彈窗

既然是簡易版的,肯定是問題一大堆,js 里面得語法不僅僅是函數(shù)調(diào)用,我們看看賦值是如何用黑魔法實(shí)現(xiàn)的

function declareVarible(key, value) {

    this[key] = value;

  }

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

  // window.foo = 'bar'

Tips: const 可以利用 Object.defineProperty 實(shí)現(xiàn);

如果上面的代碼能看懂,說明你已經(jīng)懂得了js 解釋器的基本原理了,看不懂那只好怪我咯。

稍微加強(qiáng)一下

可以看出,上面為了方便, 我們把函數(shù)調(diào)用寫成了callFunction('alert', 'hello world');但是著看起來一點(diǎn)都不像是js 解釋器,
我們心里想要的解釋器至少應(yīng)該是長這樣的parse('alert("hello world")''), 那么我們來稍微改造一下, 在這里我們要引入 babel 了,
不過先不用擔(dān)心, 我們解析出來的語法樹(AST)也是很簡單的。

import babelParser from '@babel/parser';

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

const ast = babelParser.parse(code);

以上代碼, 解析出如下內(nèi)容

{
  "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"
}

上面的內(nèi)容看起來很多,但是我們實(shí)際有用到到其實(shí)只是很小的一部分, 來稍微簡化一下, 把暫時(shí)用不到的字段先去掉

{
  "type": "Program",
  "body": [
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "CallExpression",
        "callee": {
          "type": "Identifier",
          "name": "alert"
        },
        "arguments": [
          {
            "type": "Literal",
            "value": "hello world!",
          }
        ]
      }
    }
  ],
}

我們先大概瀏覽一遍 AST 里面的所有屬性名為type的數(shù)據(jù)

ExpressionStatementCallExpressionIdentifierLiteral

一共有 4 種類型, 那么接下來我們把這 4 種節(jié)點(diǎn)分別解析, 從最簡單的開始

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

針對 Literal 的內(nèi)容, 我們需要的只有一個(gè) value 屬性, 直接返回即可.

if(node.type === 'Literal') {
    return node.value;
}

是不是很簡單?

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

Identifier 同樣也很簡單, 它代表的就是我們已經(jīng)存在的一個(gè)變量, 變量名是node.name, 既然是已經(jīng)存在的變量, 那么它的值是什么呢?

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

上面的alert我們從node.name里面拿到的是一個(gè)字符, 通過this['xxxxx']可以訪問到當(dāng)前作用域(這里是 window)里面的這個(gè)標(biāo)識符(Identifier)

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

這個(gè)其實(shí)也是超簡單, 沒有什么實(shí)質(zhì)性的內(nèi)容, 真正的內(nèi)容都在expression屬性里,所以可以直接返回 expression 的內(nèi)容

if(node.type === 'ExpressionStatement') {
    return parseAstNode(node.expression);
}
CallExpression

CallExpression 按字面的意思理解就是 函數(shù)調(diào)用表達(dá)式,這個(gè)稍微麻煩一點(diǎn)點(diǎn)

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

CallExpression 里面的有 2 個(gè)我們需要的字段:

callee 是 函數(shù)的引用, 里面的內(nèi)容是一個(gè) Identifier, 可以用上面的方法處理.arguments 里面的內(nèi)容是調(diào)用時(shí)傳的參數(shù)數(shù)組, 我們目前需要處理的是一個(gè) Literal, 同樣上面已經(jīng)有處理方法了.

說到這里,相信你已經(jīng)知道怎么做了

if(node.type === 'CallExpression') {

    // 函數(shù)
    const callee = 調(diào)用 Identifier 處理器

    // 參數(shù)
    const args = node.arguments.map(arg => {
      return 調(diào)用 Literal 處理器
    });

    callee(...args);
}
代碼

這里有一份簡單的實(shí)現(xiàn), 可以跑通上面的流程, 但也僅僅可以跑通上面而已, 其他的特性都還沒實(shí)現(xiàn)。

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

其他實(shí)現(xiàn)方式

除了上面我介紹得這種最繁瑣得方式外,其實(shí) js 還有好幾種可以直接執(zhí)行字符串代碼得方式

插入 script DOM
const script = document.createElement("script");
  script.innerText = 'alert("hello world!")';
  document.body.appendChild(script);
eval
eval('alert("hello world!")')
new Function
new Function('alert("hello world")')();
setTimeout 家族
setTimeout('console.log("hello world")');

不過這些在小程序里面都被無情得封殺了...
最后,給大家推薦一個(gè)前端學(xué)習(xí)進(jìn)階內(nèi)推交流群685910553(前端資料分享),不管你在地球哪個(gè)方位,
不管你參加工作幾年都?xì)g迎你的入駐!(群內(nèi)會定期免費(fèi)提供一些群主收藏的免費(fèi)學(xué)習(xí)書籍資料以及整理好的面試題和答案文檔?。?/p>

如果您對這個(gè)文章有任何異議,那么請?jiān)谖恼略u論處寫上你的評論。

如果您覺得這個(gè)文章有意思,那么請分享并轉(zhuǎn)發(fā),或者也可以關(guān)注一下表示您對我們文章的認(rèn)可與鼓勵。

愿大家都能在編程這條路,越走越遠(yuǎn)。

推薦教程:《JS教程》

本文名稱:用JavaScript寫一個(gè)js解釋器
鏈接URL:http://www.muchs.cn/article42/cjjhec.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設(shè)、Google外貿(mào)建站、標(biāo)簽優(yōu)化、網(wǎng)站策劃、微信小程序

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司