如何通過AST樹去獲取JS函數(shù)參數(shù)名

寫在最前

最近項目有個需求枢步,獲取函數(shù)參數(shù)名沉删,聽起來很簡單,但有了ES6醉途,參數(shù)和函數(shù)寫法千奇百怪矾瑰,在github上大概看了幾個庫,基本上都是正則隘擎,
對通用的寫法能夠覆蓋脯倚,稍微越過邊界赴涵,往往無法正確匹配霍狰。

于是就有了使用AST去進行覆蓋查找的想法。

概念

抽象語法樹(abstract syntax tree或者縮寫為AST)缸濒,或者語法樹(syntax tree)宝惰,是源代碼的抽象語法結構的樹狀表現(xiàn)形式植榕。


為什么要用AST

通過AST,我們可以對代碼進行查找尼夺,看起來好像正則表達式也可以做到尊残,那么為什么要用AST而不用正則?

就說從函數(shù)獲取參數(shù)名淤堵,夸張點寝衫,如果有以下表達式:

function x(a=5,b="a",c=function(x=1,y){console.log(x=function(i=8,j){})},d={x:1,y:2,z:'x=6'},e=x=>7,f=['3=5','x.1','y,2',1],g=(x,y)=>{let z=(i,j=6)=>{}},h){}

參數(shù)是[a,b,c,d,e,f,g,h]

你確定還想用正則去匹配參數(shù)名稱嗎...

AST是從代碼的意義去編輯,而正則只能從代碼的字面去編輯拐邪。

以上夸張的函數(shù)慰毅,使用AST去分析,可以很輕松獲取它的參數(shù)名扎阶。


Esprima

我們使用esprima汹胃,一個可以將Javascript代碼解析成抽象樹的庫。

首先我們需要安裝它:

npm install esprima

接著調用:

const esprima=require('require'')

接下來就是分析的時候了东臀。


一個簡單的AST例子

先來個簡單的例子:
function a(b){}

通過esprima解析后着饥,生成結構圖如下:

{
    "type": "Program",
    "body": [
        {   // 這個type表示這是一個函數(shù)表達式
            "type": "FunctionDeclaration",
            "id": {
                "type": "Identifier",
                "name": "a"
            },
            "params": [
                {
                    // 參數(shù)數(shù)組內的Identifier代表參數(shù)
                    "type": "Identifier",
                    "name": "b"
                }
            ],
            "body": {
                "type": "BlockStatement",
                "body": []
            },
            "generator": false,
            "expression": false,
            "async": false
        }
    ],
    "sourceType": "script"
}

思路:

  1. FunctionDeclaration說明是一個函數(shù)表達式,進入params屬性惰赋。
  2. 判斷params中每一個的type是否為Identifier宰掉,在params屬性下的Identifier就代表是參數(shù)。
  3. 找出name屬性的值,結果為['b']仇穗。

根據(jù)以上思路,我們可以寫出一個簡單的獲取參數(shù)的方法了枝冀。

function getParams(fn){
  // 此處分析的代碼必須是字符串
  let astEsprima=esprima.parseScript(fn.toString())
  let funcParams = []
  let node = astEsprima.body[0]
  // 找到type舞丛,進入params屬性
  if (node.type === "FunctionDeclaration") funcParams = node.params
  let validParam=[]
  funcParams.forEach(obj=>{
    if(obj.type==="Identifier")
      validParam.push(obj.name)
  })
  return validParam
}

測試一番球切,獲取結果["b"],慶祝收工鸵钝。

好吧,別高興太早了怠堪,要知道函數(shù)的創(chuàng)建方法不下10種,而參數(shù)寫法又有好幾種...

以下是一部分的函數(shù)創(chuàng)建方法和參數(shù)寫法

function a(x){}

// 注意:第二條和第三條在AST中意義不同
let a=function(x=1){}

a=function(...x){}

let a=([x]=[1])=>{}

async function a(x){}

function *a(x){}

class a{
constructor(x){}
}

new Function ('x','console.log(x)')

(function(){return function(x){}})()

eval("(function(){return function(a,b){}})()")

有什么想法?如果你有發(fā)出"我K"的想法申屹,那說明我這個裝逼還算成功- -...

其實只需要分幾種情況(很多寫法的type都是一致的),就可以完全滲入到以上所有的參數(shù)對象內部杆煞,再進行參數(shù)獲取就是循環(huán)+判斷解決的事了。

由于篇幅問題蚌斩,這里不一一分析,只是將AST分析樹所用的type和一些注意點。

函數(shù)結構

變量聲明語句和表達式語句

上面注釋中let a=function(x=1){}a=function(...x){}是兩種意義碌补。

其中let a=function(x=1){}指的是變量聲明語句,

對應的type是VariableDeclaration,需要進入它的初始值init就可以獲取到函數(shù)所在的語法對象囊骤,它的type是FunctionExpression函數(shù)表達式,再去params中查找即可。

變量聲明語句:

├──VariableDeclaration....init
        ├──FunctionExpression.params

a=function(...x){}是表達式語句,

對應的type是ExpressionStatement斥赋,需要進入它的表達式expression獲取到表達式內部,這時我們要進入賦值表達式(type為AssignmentExpression)的右邊(right屬性)疑故,
獲取函數(shù)所在的語法對象管钳,它的type同樣也是FunctionExpression函數(shù)表達式葫隙。

表達式語句:

├──ExpressionStatement.expression
        ├──AssignmentExpression.right
                ├──FunctionExpression.params

class聲明和Function構造函數(shù)

class聲明對應的type有ClassDeclaration(class xx{...})或者ClassExpression(let x=class{...}),他們一個是聲明一個是表達式糟描,處理方式是相同的怀喉,
進入對象內部,找到kind為constructor的對象船响,獲取參數(shù)數(shù)據(jù)见间。

class聲明語句:

├──ClassDeclaration...body...
        ├──{kind:constructor}
                ├──FunctionExpression.params

Function構造函數(shù)對應的type是NewExpression或者ClassExpression,參數(shù)在屬性arguments內部,但是Function的參數(shù)都是字符串,
而且最后一個參數(shù)一定是函數(shù)內部語句税朴,因此對于Function構造函數(shù)瘾杭,就是對字符串進行處理诅病。

Function構造函數(shù)

├──NewExpression.arguments
        ├──{value:<String>}
         ---->對字符串進行處理,分割參數(shù)

箭頭函數(shù)

箭頭函數(shù)type是ArrowFunctionExpression粥烁,也僅僅是名稱不同芥永,內部結構幾乎一致。

函數(shù)結構的type就到此钝吮。

參數(shù)結構

參數(shù)的type有以下:

Identifier:最終我們需要獲取的參數(shù)值的type

Property:當存在解構參數(shù)埋涧,例如[a,b] or {x,y}

ArrayPattern:存在解構參數(shù)并且是數(shù)組,例如[a,b]

ObjectPattern:存在解構參數(shù)并且是對象奇瘦,例如{x,y}

RestElement:存在擴展運算符棘催,例如(...args)

我們只需要設置一個遞歸循環(huán),思路和上面一樣耳标,一層進入另一層醇坝,在內部進行查找。

總結

篇幅有限次坡,就寫這么多呼猪,接著做一個總結。

這篇講的主旨只有1個砸琅,通過對AST樹中每一個對象的type分析郑叠,type表示的是對應的代碼的意義,也是代碼的語義明棍,例如

VariableDeclaration內部一定會有init乡革,為什么,因為變量聲明是有初始值的摊腋,如果你不設置沸版,那么就為undefined

type遠不止這次說的這么多,官網(或者Google)上有詳細介紹兴蒸。


最后

AST獲取函數(shù)參數(shù)源代碼在此视粮。

如果本文對你有所幫助,歡迎STAR橙凳,或者你對此有什么更好的想法蕾殴,歡迎留言笑撞!

最重要如果發(fā)現(xiàn)了BUG或者漏匹配,請一定要告知(Issue/PR/留言)钓觉,感激不盡茴肥!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市荡灾,隨后出現(xiàn)的幾起案子瓤狐,更是在濱河造成了極大的恐慌,老刑警劉巖批幌,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件础锐,死亡現(xiàn)場離奇詭異,居然都是意外死亡荧缘,警方通過查閱死者的電腦和手機皆警,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來截粗,“玉大人信姓,你說我怎么就攤上這事⊥┯洌” “怎么了财破?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵掰派,是天一觀的道長从诲。 經常有香客問我,道長靡羡,這世上最難降的妖魔是什么系洛? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮略步,結果婚禮上描扯,老公的妹妹穿的比我還像新娘。我一直安慰自己趟薄,他們只是感情好绽诚,可當我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杭煎,像睡著了一般恩够。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上羡铲,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天蜂桶,我揣著相機與錄音,去河邊找鬼也切。 笑死扑媚,一個胖子當著我的面吹牛腰湾,可吹牛的內容都是我干的。 我是一名探鬼主播疆股,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼费坊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了押桃?” 一聲冷哼從身側響起葵萎,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唱凯,沒想到半個月后羡忘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡磕昼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年卷雕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片票从。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡漫雕,死狀恐怖,靈堂內的尸體忽然破棺而出峰鄙,到底是詐尸還是另有隱情浸间,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布吟榴,位于F島的核電站魁蒜,受9級特大地震影響,放射性物質發(fā)生泄漏吩翻。R本人自食惡果不足惜兜看,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狭瞎。 院中可真熱鬧细移,春花似錦、人聲如沸熊锭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碗殷。三九已至精绎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間亿扁,已是汗流浹背捺典。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留从祝,地道東北人襟己。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓引谜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親擎浴。 傳聞我的和親對象是個殘疾皇子员咽,可洞房花燭夜當晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內容

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,149評論 0 13
  • { "Unterminated string literal.": "未終止的字符串文本贮预。", "Identifi...
    Elbert_Z閱讀 10,839評論 0 2
  • 文/蘇久驍 剛剛吃完飯贝室,可能是通病,第一件事就是打開手機仿吞。 第一眼就看到了騰訊新聞發(fā)的消息:【十六歲少女暑...
    蘇久驍閱讀 437評論 6 4
  • 總結冒泡排序 1:相鄰的兩個數(shù)的比較 2:兩層循環(huán) 第一層是決定多少輪滑频,第二層決定每層需要多少次 3:臨時變量存放...
    MacLin閱讀 553評論 1 3
  • 其一 初夏夜雨為兒遮毯有感(十唐) 夜半雨聲初夏涼, 起尋薄毯護兒忙唤冈。 捂得急痱癢難忍峡迷, 復又自責“瞎恐慌”。 其...
    水波楊山閱讀 615評論 2 3