您好,登錄后才能下訂單哦!
如何用JavaScript編寫解釋器?針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。
用 js 來 編譯 js
看起來是個(gè)高大上的東西,實(shí)際原理其實(shí)很簡(jiǎn)單,無非就是利用 js 對(duì)象屬性可以用字符串表示
這個(gè)特性來實(shí)現(xiàn)的黑魔法罷了。
之所以看起來那么 深?yuàn)W
, 大概是由于網(wǎng)上現(xiàn)有的教程,都是動(dòng)不動(dòng)就先來個(gè) babylon / @babel/parser
先讓大家看個(gè)一大串的 AST
, 然后再貼出一大串的代碼,
直接遞歸 AST 處理所有類型的節(jié)點(diǎn). 最后新手就成功被嚇跑了。
先來看一下效果
上面有提到,js 有個(gè)特性是 對(duì)象屬性可以用字符串表示
,如 console.log 等價(jià)于 console['log'], 辣么根據(jù)這個(gè)特性,我們可以寫出一個(gè)兼容性極差,極其簡(jiǎn)陋的雛形
function callFunction(fun, arg) { this[fun](arg); } callFunction('alert', 'hello world'); // 如果你是在瀏覽器環(huán)境的話,應(yīng)該會(huì)彈出一個(gè)彈窗
既然是簡(jiǎn)易版的,肯定是問題一大堆,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 解釋器
的基本原理了,看不懂那只好怪我咯。
可以看出,上面為了方便, 我們把函數(shù)調(diào)用寫成了 callFunction('alert', 'hello world');
但是著看起來一點(diǎn)都不像是 js 解釋器
,
我們心里想要的解釋器至少應(yīng)該是長(zhǎng)這樣的 parse('alert("hello world")'')
, 那么我們來稍微改造一下, 在這里我們要引入 babel 了,
不過先不用擔(dān)心, 我們解析出來的語法樹(AST)也是很簡(jiǎn)單的。
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í)只是很小的一部分, 來稍微簡(jiǎn)化一下, 把暫時(shí)用不到的字段先去掉
{ "type": "Program", "body": [ { "type": "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "Identifier", "name": "alert" }, "arguments": [ { "type": "Literal", "value": "hello world!", } ] } } ], }
我們先大概瀏覽一遍 AST 里面的所有屬性名為 type
的數(shù)據(jù)
一共有 4 種類型, 那么接下來我們把這 4 種節(jié)點(diǎn)分別解析, 從最簡(jiǎn)單的開始
{ "type": "Literal", "value": "hello world!", }
針對(duì) Literal 的內(nèi)容, 我們需要的只有一個(gè) value 屬性, 直接返回即可.
if(node.type === 'Literal') { return node.value; }
是不是很簡(jiǎn)單?
{ "type": "Identifier", "name": "alert" },
Identifier 同樣也很簡(jiǎn)單, 它代表的就是我們已經(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)識(shí)符(Identifier)
{ "type": "ExpressionStatement", "expression": {...} }
這個(gè)其實(shí)也是超簡(jiǎn)單, 沒有什么實(shí)質(zhì)性的內(nèi)容, 真正的內(nèi)容都在 expression
屬性里,所以可以直接返回 expression 的內(nèi)容
if(node.type === 'ExpressionStatement') { return parseAstNode(node.expression); }
CallExpression 按字面的意思理解就是 函數(shù)調(diào)用表達(dá)式,這個(gè)稍微麻煩一點(diǎn)點(diǎn)
{ "type": "CallExpression", "callee": {...}, "arguments": [...] }
CallExpression 里面的有 2 個(gè)我們需要的字段:
說到這里,相信你已經(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); }
這里有一份簡(jiǎn)單的實(shí)現(xiàn), 可以跑通上面的流程, 但也僅僅可以跑通上面而已, 其他的特性都還沒實(shí)現(xiàn)。
https://github.com/noahlam/pr...
除了上面我介紹得這種最繁瑣得方式外,其實(shí) js 還有好幾種可以直接執(zhí)行字符串代碼得方式
const script = document.createElement("script"); script.innerText = 'alert("hello world!")'; document.body.appendChild(script);
eval('alert("hello world!")')
new Function('alert("hello world")')();
setTimeout('console.log("hello world")');
關(guān)于如何用JavaScript編寫解釋器問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。