Vue生成AST算法的解析

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在看vue源碼的過程中,不只加深了對vue本身的理解,也理解了正則,以及各種設計模式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"博客大部分是從代碼開始講起,所以,我打算更細緻的講一講這部分的思想。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我們要知道什麼是AST,以及爲什麼要用AST生成虛擬dom。"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AST是指抽象語法樹(abstract syntax tree),或者語法樹(syntax tree),是源代碼的抽象語法結構的樹狀表現形式。Vue在mount過程中,template會被編譯成AST語法樹。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單來說,在js中AST的表現形式是像下面的結構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/45/454683bf9a35c1912a32654a45d970d5.png","alt":null,"title":"AST Object","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它是怎麼來的呢? 是通過js中的類html字符串轉換而來的,所以我們的任務就變成了把類html轉換成這種結構啦。 下面我先給出一串模板"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const template = `\n
\n
\n \n
\n
\n`"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"#1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 首先我們對這個字符串進行trim操作,去除兩端的空格。然後我們要用"},{"type":"codeinline","content":[{"type":"text","text":" let textStart = html.indexOf(' 0的時候,說明在標籤或者註釋之前含有文本(這裏我們暫時不考慮文本中有"}]},{"type":"text","text":"我們要把它的tagName, id, class等等屬性得到。設置兩個棧。一個叫checkStack,用於與之後匹配到的結束標籤進行比對。一個叫AstStack,用於輔助AST的建立。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e8/e82eaa2ffe0b64f9dc5d9b2fa66f680d.png","alt":null,"title":"checkStack","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/36/36e464b8a82bbd201868e1f82ac0d893.png","alt":null,"title":"AST Stack","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"checkStack中單純存放了tagName和attr,而AstStack中存放的是他的父元素和子元素,這些可以用於建立一個AST。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以流程如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"匹配到開始標籤 --> 獲得各種屬性,創建一個match對象--> 推入checkStack中-->推入AstStack中 (這裏先不討論自閉和標籤的情況,等會會用代碼來詳細討論)"}]},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果匹配到註釋,不推入checkStack,具體流程如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"匹配到註釋開始 --> 獲得註釋所有的內容,並匹配到註釋結束標籤 --> 不推入checkStack --> 從AstStack中獲取最後一個元素, 把該註釋對象推入AstStack棧頂的children數組中"}]},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"2.2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 沒有標籤或註釋,則會把該段純文本推入AstStack棧頂元素的children中, 並不再遍歷該段html,如果沒有棧頂元素,則會報錯"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"2.3"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 與2.2情況類似,只是還會繼續遍歷html"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"3"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"知道了流程和怎麼處理,我們可以愉快的開始看代碼啦!我們來看看精簡版的,然後大家熟悉了流程可以再去看vue的源碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先來定義幾個正則"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const attribute = /^\\s*([^\\s\"'<>\\\\/=]+)(?:\\s*(=)\\s*(?:\"([^\"]*)\"+|'([^']*)'+|([^\\s\"'=<>`]+)))?/\nconst ncname = '[a-zA-Z_][\\\\w\\\\-\\\\.]*'\nconst qnameCapture = `((?:${ncname}\\\\:)?${ncname})`\n// 匹配開始標籤開始部分\nconst startTagOpen = new RegExp(`^/\n// 匹配結束標籤\nconst endTag = new RegExp(`^]*>`)\n// 匹配註釋\nconst comment = /^')\n console.log(commentEnd)\n if (commentEnd >= 0) {\n if (opt.comment) {\n opt.comment(html.substring(4, commentEnd)) // 保存註釋
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章