以一個interpreter實現者的視角去看閉包:寫一個interpreter
作為一個前端開發,一個javascript的使用者,對閉包的實現非常好奇,於是研究了一翻並寫了一個簡單的interpreter. 這個interpreter支持VarStatement,RereturnStaturn,FunctionLiterial,和CallExpression。
Interpreter一直以來都是以黒盒的形式展現在前端開發面前,本文的目的是打開盒子去更深入的了解我們日常所用的編程語言。
Interpreter從功能上主要包含三部分:Lexer,Parser和Evaluator
1. Lexer的主要作用是將code轉化為一個個Token
例如Lexer的輸入為var a = 1;輸出如下圖
2.Parser的作用是把Lexer輸出的tokens以一定的規律組合生成抽象語法樹,抽象語法樹是一種樹形結構,這個樹形結構描述了code中所包含的所用信息。
vara=1;
function() {
varb=1;
};
上面這段代碼生成的語法樹結構如下:
這個抽象語法樹的跟元素是Program對象,它的statements屬性包含兩個對象:一個VarStatement和一個ExpressionStatement。
ExpressionStatement的expression屬性是FunctionLiteral。
FunctionLiteral包含body屬性,這個屬性是個BlockStatement。
BlockStatement對象的結構和Program類似。
3. Evaluator的作用是遍歷抽象語法樹,根據碰到的node類型去做不同的操作,其實Evaluator就是一個遞歸函數,代碼如下:
export default functionEval(node, env) {
switch(node.constructor) {
caseProgram:
returnevalStatements(node.statements, env)
caseExpressionStatement:
returnEval(node.expression, env)
caseVarStatement:
letvalue=Eval(node.value, env)
env.set(node.name.value,value)
returnvalue
caseFunctionLiteral:
return newFunction(node.body, env)
caseCallExpression:
letfunc=Eval(node.func, env)
returnapplyFunction(func)
}
return null
}
這個遞歸函數的第二個入參env是一個對象,它的作用是綁定和獲取變了。其代碼如下:
export default classEnvironment {
constructor() {
this.store= {}
}
set(name, value) {
this.store[name] = value
}
get(name) {
return this.store[name]
}
}
當Evaluator遇到VarStatement節點的時候,會把VarStatement所包含的信息以鍵值對的形式綁定到當前env中。當它遇到Identifier節點的時候會去當前的env中拿之前綁定的變數。
functionevalIdentifier(node, env) {
letval= env.get(node.value)
returnval
}
接下來重點講閉包:結合下面的代碼來說,被返回了的inner函數能獲取到outer函數中所定義的a變數就是閉包的表現。
vartest=function() {// outer
var a=1;
return function(){// inner
return a+2;
};
};
test()();
1.首先Eval會先遇到VarStatement,把test變數存儲在當前的environment對象env1中
2.Eval遇到test()(),這是一個CallExpression,要調用的對象是test()這顯然還是個CallExpression,
因此會先解析test(),到目前為止environment對象還是env1
3.開始解析test(),首先從當前的environment對象env1中拿出test,test是一個FunctionLiteral
4.調用test,也就是解析這個FunctionLiteral,也就是解析這個函數的函數體:Eval(func.body,env?)
5.關鍵是在什麼environment下去解析這個函數體,在解析這個函數體的時候我們會碰到一個VarStatement,
VarStatement會向當前的envirnment中綁定變數test,我們肯定是不希望這個test直接綁定在env1中的,
因為可能會覆蓋env1中綁定的變數。所以在解析這個函數的函數體的時候我們去新建一個envirnment,這個
envirnment可以綁定變數,並且有一個屬性指向env1。
6.這時候就輪到EnclosedEnvironment對象上場了,這個對象的定義如下
export classEnclosedEnvironment {
constructor(outer) {
this.outer= outer
this.env=newEnvironment()
}
set(name, value) {
this.env.set(name, value)
}
get(name) {
return this.env.get(name)this.outer.get(name)
}
}
7.也就是我們會新建一個對象env2,這個env2的outer屬性是env1,這時候當前的environment對象
已經是env2了
8. 在env2環境下解析outer函數的函數體,返回一個FunctionLiteral,也就是inner函數表達式
9. 解析inner函數體,在什麼環境下? 當然是env3環境下,env3有一個outer屬性是指向env2的
總結一下其實就是當沒遇到CallExpression的時候解析的環境env(n)是不變的,一旦遇到CallExpression,就需要
新建一個EnclosedEnvironment對象env(n+1),env(n+1)的outer屬性是指向env(n)的。
interpreter demo效果如下
想要代碼的同學分享本文到朋友圈並截圖發到公眾號
TAG:前端磚家 |
※「猿·視角」Supreme:Nike你走吧,我覺得adidas更好。
※解構| MM6maisonmargiela 怪才的另類視角
※輕鬆分享展示HoloLens視角,微軟正式發布SpectatorView
※中尉視角:2018年上海音響展之 DenonMarantzDefinitivePolk Audio 篇
※Being in the World——淺談Olafur Eliasson作品在建築、景觀、及城市設計視角下的表達
※Imagination Technologies 獨家視角:2018年CES上的科技趨勢
※Piaget推出Sunlight Escape系列高級珠寶 以不同視角詮釋作品
※猿視角:呵,瘋漲的 Air Jordan 1
※西澳大學:NovelPerspective:人物視角識別
※交易平台視角下的Staking Economy
※盲人視角射擊遊戲《Muffled Warfare》在Steam平台開啟搶先體驗
※Madame Figaro 10月刊 | 時尚的另一個視角
※視角 Stone Island品牌預覽
※Ambarella又來了!GGAI視角
※每日一圖|養魚場之上帝視角。|攝影師:Bernhard Lang
※Lisa和Rose的閨蜜視角 我擺pose你拍照 逆光也很美
※「猿·視角」飛躍罰球線:Jordan Brand 是個讓人歡喜的"大騙子"
※photoshop中怎麼調整照片的透視角度
※來自母親的視角——Sally Mann
※從營銷視角揭秘Facebook、Twitter、Ins等5大社交平台的演算法運作原理