国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

手摸手教你用 js 寫一個 js 解釋器

hss01248 / 761人閱讀

摘要:手摸手教你用寫一個解釋器用來編譯看起來是個高大上的東西,實際原理其實很簡單,無非就是利用對象屬性可以用字符串表示這個特性來實現的黑魔法罷了。

手摸手教你用 js 寫一個 js 解釋器

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

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

先來看一下效果

一個最簡單的解釋器

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

  function callFunction(fun, arg) {

    this[fun](arg);

  }

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

  // 如果你是在瀏覽器環境的話,應該會彈出一個彈窗

既然是簡易版的,肯定是問題一大堆,js 里面得語法不僅僅是函數調用,我們看看賦值是如何用黑魔法實現的

  function declareVarible(key, value) {

    this[key] = value;

  }

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

  // window.foo = "bar"
Tips: const 可以利用 Object.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 里面的所有屬性名為 type 的數據

ExpressionStatement

CallExpression

Identifier

Literal

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

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

針對 Literal 的內容, 我們需要的只有一個 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]
    };
}

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

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

這個其實也是超簡單, 沒有什么實質性的內容, 真正的內容都在 expression 屬性里,所以可以直接返回 expression 的內容

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

CallExpression 按字面的意思理解就是 函數調用表達式,這個稍微麻煩一點點

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

CallExpression 里面的有 2 個我們需要的字段:

callee 是 函數的引用, 里面的內容是一個 Identifier, 可以用上面的方法處理.

arguments 里面的內容是調用時傳的參數數組, 我們目前需要處理的是一個 Literal, 同樣上面已經有處理方法了.

說到這里,相信你已經知道怎么做了

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

    // 函數
    const callee = 調用 Identifier 處理器

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

    callee(...args);
}
代碼

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

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

其他實現方式

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

插入 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")");

不過這些在小程序里面都被無情得封殺了...

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/103751.html

相關文章

  • 手摸手教你用canvas實現給圖片添加平鋪水印

    摘要:最近項目中遇到一個需求,需要把一張圖片加上平鋪的水印類似這樣的效果首先想到的是用完成這種功能,因為我之前也沒有接觸過,所以做這個功能的時候,就是一步一步的摸索中學習,過程還是挺的,接下來跟我一步步來實現這個功能以及發現一些的坑吧。 最近項目中遇到一個需求,需要把一張圖片加上平鋪的水印 類似這樣的效果showImg(https://segmentfault.com/img/remote/...

    崔曉明 評論0 收藏0
  • 摸手,帶你封裝一個vue component

    摘要:靈活性和針對性。所以我覺得大部分組件還是自己封裝來的更為方便和靈活一些。動手開干接下來我們一起手摸手教改造包裝一個插件,只要幾分鐘就可以封裝一個專屬于你的。 項目地址:vue-countTo配套完整后臺demo地址:vue-element-admin系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎篇)系類文章二:手摸手,帶你用vue擼后臺 系列二(登錄權限篇)系類文章三:手摸手,帶...

    pkhope 評論0 收藏0
  • 前端之從零開始系列

    摘要:只有動手,你才能真的理解作者的構思的巧妙只有動手,你才能真正掌握一門技術持續更新中項目地址求求求源碼系列跟一起學如何寫函數庫中高級前端面試手寫代碼無敵秘籍如何用不到行代碼寫一款屬于自己的類庫原理講解實現一個對象遵循規范實戰手摸手,帶你用擼 Do it yourself!!! 只有動手,你才能真的理解作者的構思的巧妙 只有動手,你才能真正掌握一門技術 持續更新中…… 項目地址 https...

    Youngdze 評論0 收藏0

發表評論

0條評論

hss01248

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<