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)) // 保存注释
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章