- 寫(xiě)到這兒的時(shí)候其實(shí)思路就有點(diǎn)亂了,很大部分的原因是之前的代碼寫(xiě)了太久猪落,有的地方確實(shí)忘了仍秤。所以今天打算趁機(jī)總結(jié)一下回顧一下。
- 寫(xiě)這個(gè)parser的原因是實(shí)現(xiàn)前端的表達(dá)式部分临燃。
- parser的流程是拿到表達(dá)式,表達(dá)式肯定是個(gè)字符串,解析字符串膜廊,生成token乏沸。
- 然后在AST構(gòu)建階段,通過(guò)消耗token來(lái)構(gòu)建AST爪瓜。
- compile階段是通過(guò)AST的節(jié)點(diǎn)類(lèi)型選擇相應(yīng)的流程進(jìn)行編譯蹬跃,歸根結(jié)底是編譯出一個(gè)字符串,并且用這個(gè)字符串生成一個(gè)Function铆铆。
數(shù)字和字符串和true,false,null
- 無(wú)論是整數(shù)還是小數(shù)蝶缀,還是科學(xué)計(jì)數(shù),還是字符串還是布爾值算灸,都會(huì)生成一個(gè)token扼劈,這個(gè)token和其他token不同的是,這個(gè)token有一個(gè)value屬性菲驴。這種token稱(chēng)為constants類(lèi)型的token荐吵。
- 構(gòu)建AST的時(shí)候,先檢測(cè)這個(gè)token是不是其他的token赊瞬,如果都不符合先煎,才會(huì)認(rèn)為這個(gè)token是一個(gè)constants類(lèi)型的token,進(jìn)入處理constants的流程巧涧。
- constants會(huì)將這種token轉(zhuǎn)化成Literal類(lèi)型的節(jié)點(diǎn)薯蝎,Literal類(lèi)型的節(jié)點(diǎn)有一個(gè)value屬性。
- 在編譯階段谤绳,Literal類(lèi)型的節(jié)點(diǎn)會(huì)返回它的value屬性占锯。
數(shù)組
- 數(shù)組會(huì)生成中括號(hào)和引號(hào)等token,中括號(hào)和引號(hào)這類(lèi)token是屬于identifier類(lèi)型的token缩筛。
- 構(gòu)建AST時(shí)消略,如果遇到中括號(hào)的token,說(shuō)明是一個(gè)數(shù)組瞎抛,需要進(jìn)入數(shù)組流程艺演。
- 在數(shù)組流程里面,會(huì)生成一個(gè)ArrayExpression類(lèi)型的節(jié)點(diǎn)桐臊,這個(gè)節(jié)點(diǎn)有elements屬性胎撤,用于存儲(chǔ)數(shù)組內(nèi)容。
- 編譯的時(shí)候断凶,遇到這個(gè)ArrayExpression節(jié)點(diǎn)伤提,會(huì)把elements屬性中的內(nèi)容都遍歷出來(lái),返回一個(gè)字符串懒浮。
case ASTBuilder.ArrayExpression:
var elements = [];
for (var i = 0; i < ast.elements.length; i++) {
elements.push(this.recurse(ast.elements[i]))
}
return "[" + elements.join(',') + "]";
對(duì)象
- 對(duì)象就如同數(shù)組飘弧,會(huì)生成大括號(hào)以及冒號(hào)等token识藤。
- AST階段砚著,遇到大括號(hào)的時(shí)候會(huì)進(jìn)入對(duì)象流程次伶,最后得到一個(gè)ObjectExpression節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)有一個(gè)properties屬性稽穆,用于記錄對(duì)象的屬性值冠王。
3.編譯階段,還是會(huì)以字符串的形式處理ObjectExpression節(jié)點(diǎn)舌镶。
case ASTBuilder.ObjectExpression:
var properties = [];
for (var i = 0; i < ast.properties.length; i++) {
var key = ast.properties[i].key.name;
var value = this.escape(ast.properties[i].value.value);
properties.push(key + ":" + value);
}
return "{" + properties.join(',') + "}";
尋找屬性部分
- 遇到
parse('name')
這種不是字符串也不是值的時(shí)候柱彻,產(chǎn)生identifier類(lèi)型token。 - 產(chǎn)生的AST節(jié)點(diǎn)是MemberExpression餐胀。
- 編譯遇到MemberExpression節(jié)點(diǎn)哟楷,檢測(cè)節(jié)點(diǎn)的object屬性和property屬性。
- property屬性可以進(jìn)行另一次primary流程否灾,找到一個(gè)token卖擅。
- object屬性可能是一個(gè)ast樹(shù)。需要遍歷墨技。不管怎樣惩阶,object總會(huì)返回一個(gè)變量。拿這個(gè)圖來(lái)說(shuō):
最深入的節(jié)點(diǎn)是紅色部分user扣汪,identifier類(lèi)型断楷,在這里會(huì)檢測(cè)scope和local,返回一個(gè)變量v0崭别。
然后是綠色部分冬筒,也是一個(gè)object,返回一個(gè)變量是v1=v0.student茅主。
最后到外層返回的是v2 = v1.name舞痰。在program部分得到的結(jié)果就是
return v2
如果是中括號(hào)分割的MemberExpression,則產(chǎn)生的變量為v1=v0[student]...以此類(lèi)推暗膜。
方法調(diào)用部分
0.假設(shè)是這樣的代碼:
var scope = {
user:{
name: 'wangji',
getName: function () {
return this.name;
}
}
}
var fn = parse('user.getName()');
expect(fn(scope)).toBe('wangji');
- 遇到帶括號(hào)的identifier匀奏,代表是方法調(diào)用。
- AST階段產(chǎn)生的是CallExpression節(jié)點(diǎn)学搜。這個(gè)節(jié)點(diǎn)有callee屬性娃善,代表方法調(diào)用時(shí)候的上下文,arguments屬性瑞佩,代表方法調(diào)用時(shí)候的參數(shù)聚磺,這里記錄的是實(shí)參。
- 難度在于解析方法中的this關(guān)鍵字炬丸。
- 思路是在遇到CallExpression的時(shí)候瘫寝,recurse的時(shí)候傳入一個(gè)context對(duì)象蜒蕾,這個(gè)對(duì)象有name屬性用來(lái)記錄方法名(getName),left屬性用來(lái)記錄方法所屬的對(duì)象(scope.user)
- 編譯的時(shí)候編譯CallExpression的callee屬性時(shí)候,傳入一個(gè)context空對(duì)象焕阿,經(jīng)過(guò)一系列編譯后咪啡,往里面不斷填充內(nèi)容,成為所需要的context對(duì)象暮屡。
- 這個(gè)callee屬性是一個(gè)MemberExpression節(jié)點(diǎn)撤摸,于是編譯它的object屬性,這個(gè)object屬性返回一個(gè)變量v1=scope.user褒纲;記錄下來(lái)context.context=v1.
- 然后把v1和property屬性組裝准夷,變成v0=v1.getName;返回v0。
- 這時(shí)候callee成為了v0莺掠。檢測(cè)context對(duì)象衫嵌,發(fā)現(xiàn)是有內(nèi)容的,所以callee變成v1.getName
- 最終執(zhí)行的函數(shù)是scope.user.getName()
10.如果不加context對(duì)象彻秆,最終執(zhí)行的是v0&&vo()
其核心在于函數(shù)調(diào)用時(shí)候的所在上下文楔绞,如果直接調(diào)用v0(),那么v0里面的this是與window對(duì)象綁定的掖棉,如果調(diào)用的是v1.getName墓律,那么getName里面的this是與v1綁定的。