1. const TextModes = {
    2. DATA: "DATA",
    3. RCDATA: "RCDATA",
    4. RAWTXT: "RAWTXT",
    5. CDATA: "CDATA",
    6. }
    7. //优化的解析函数
    8. export function parser1(str) {
    9. const context = {
    10. source: str,
    11. mode: TextModes.DATA,
    12. //advanceBy函数用来消费指定数量的字符,它接收一个数字作为参数
    13. advanceBy(num) {
    14. context.source = context.source.slice(num)
    15. },
    16. //消费空白字符串
    17. advanceSpaces() {
    18. const match = /^[\t\r\n\f ]+/.exec(context.source)
    19. if (match) {
    20. context.advanceBy(match[0].length)
    21. }
    22. },
    23. }
    24. const nodes = parseChildren(context, [])
    25. return {
    26. type: "Root",
    27. children: nodes,
    28. }
    29. }
    30. function isEnd(context, ancestors) {
    31. if (!context.source) return true
    32. for (let i = ancestors.length - 1; i >= 0; i--) {
    33. if (context.source.startsWith(`</${ancestors[i].tag}`)) return true
    34. }
    35. }
    36. //解析文本插值{{}}
    37. function parseInterpolation(context) {
    38. context.advanceBy("{{".length)
    39. let closeIndex = context.source.indexOf("}}")
    40. if (closeIndex < 0) {
    41. console.error("插值缺少结束界定符")
    42. }
    43. const content = context.source.slice(0, closeIndex)
    44. context.advanceBy(content.length)
    45. context.advanceBy("}}".length)
    46. return {
    47. type: "Expression",
    48. content,
    49. }
    50. }
    51. //解析文本
    52. function parseText(context) {
    53. //文本结尾索引
    54. let endIndex = context.source.length
    55. //<位置
    56. let ltIndex = context.source.indexOf("<")
    57. //{{位置
    58. const delimiterIndex = context.source.indexOf("{{")
    59. if (ltIndex > -1 && ltIndex < endIndex) {
    60. endIndex = ltIndex
    61. }
    62. if (delimiterIndex > -1 && delimiterIndex < endIndex) {
    63. endIndex = delimiterIndex
    64. }
    65. const content = context.source.slice(0, endIndex)
    66. context.advanceBy(content.length)
    67. return {
    68. type: "Text",
    69. content,
    70. }
    71. }
    72. //解析属性
    73. function parseAttributes(context) {
    74. const props = []
    75. const { advanceSpaces, advanceBy } = context
    76. while (!context.source.startsWith(">") && !context.source.startsWith("/>")) {
    77. const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)
    78. const name = match[0]
    79. advanceBy(name.length)
    80. advanceSpaces()
    81. //消费等于号
    82. advanceBy(1)
    83. advanceSpaces()
    84. let value = ""
    85. const quote = context.source[0]
    86. const isQuoted = quote === "'" || quote === '"'
    87. if (isQuoted) {
    88. advanceBy(1)
    89. const endQuoteIndex = context.source.indexOf(quote)
    90. if (endQuoteIndex > -1) {
    91. value = context.source.slice(0, endQuoteIndex)
    92. advanceBy(value.length)
    93. advanceBy(1)
    94. } else {
    95. console.error("缺少引号")
    96. }
    97. } else {
    98. const match = /^[^\t\r\n\f />]+/.exec(context.source)
    99. value = match[0]
    100. advanceBy(value.length)
    101. }
    102. advanceSpaces()
    103. props.push({
    104. type: "Attribute",
    105. name,
    106. value,
    107. })
    108. }
    109. return props
    110. }
    111. //解析标签
    112. function parseTag(context, type = "start") {
    113. const { advanceBy, advanceSpaces } = context
    114. const match =
    115. type === "start"
    116. ? /^<([a-z][^\t\r\n\f />]*)/i.exec(context.source)
    117. : /^<\/([a-z][^\t\r\n\f />]*)/i.exec(context.source)
    118. const tag = match[1]
    119. advanceBy(match[0].length)
    120. advanceSpaces()
    121. const props = parseAttributes(context)
    122. const isSelfClosing = context.source.startsWith("/>")
    123. advanceBy(isSelfClosing ? 2 : 1)
    124. return {
    125. type: "Element",
    126. tag,
    127. props,
    128. children: [],
    129. isSelfClosing,
    130. }
    131. }
    132. //解析元素
    133. function parseElement(context, ancestors) {
    134. const element = parseTag(context)
    135. if (element.isSelfClosing) return element
    136. if (element.tag === "textarea" || element.tag === "title") {
    137. context.mode = TextModes.RCDATA
    138. } else if (/style|xmp|iframe|noembed|noframes|noscript/.test(element.tag)) {
    139. context.mode = TextModes.RAWTXT
    140. } else {
    141. context.mode = TextModes.DATA
    142. }
    143. ancestors.push(element)
    144. element.children = parseChildren(context, ancestors)
    145. ancestors.pop()
    146. if (context.source.startsWith(`</${element.tag}`)) {
    147. parseTag(context, "end")
    148. } else {
    149. console.error(`${element.tag}标签缺少闭合标签`)
    150. }
    151. return element
    152. }
    153. function parseChildren(context, ancestors) {
    154. let nodes = []
    155. const { mode, source } = context
    156. while (!isEnd(context, ancestors)) {
    157. let node
    158. if (mode === TextModes.DATA || mode === TextModes.RCDATA) {
    159. if (mode === TextModes.DATA && source[0] === "<") {
    160. if (source[1] === "!") {
    161. if (source.startsWith("<!--")) {
    162. //注释
    163. node = parseComment(context)
    164. } else if (source.startsWith("<![CDATA[")) {
    165. //CDATA
    166. node = parseCDATA(context, ancestors)
    167. }
    168. } else if (source[1] === "/") {
    169. //结束标签
    170. console.error("无效的结束标签")
    171. continue
    172. } else if (/[a-z]/i.test(source[1])) {
    173. //标签
    174. node = parseElement(context, ancestors)
    175. }
    176. } else if (source.startsWith("{{")) {
    177. //插值
    178. node = parseInterpolation(context)
    179. }
    180. }
    181. if (!node) {
    182. //node不存在说明是解析文本
    183. node = parseText(context)
    184. }
    185. nodes.push(node)
    186. }
    187. return nodes
    188. }
    189. // const tree = parser1("<div id='foo' v-show='display'></div>")
    190. const tree = parser1("<div>{{vue}}</div>")
    191. console.log(tree)