├── screenshots ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png └── 8.png ├── demo.html ├── README.md └── vue-ast.js /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwStar/vue-ast/HEAD/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwStar/vue-ast/HEAD/screenshots/2.png -------------------------------------------------------------------------------- /screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwStar/vue-ast/HEAD/screenshots/3.png -------------------------------------------------------------------------------- /screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwStar/vue-ast/HEAD/screenshots/4.png -------------------------------------------------------------------------------- /screenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwStar/vue-ast/HEAD/screenshots/5.png -------------------------------------------------------------------------------- /screenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwStar/vue-ast/HEAD/screenshots/6.png -------------------------------------------------------------------------------- /screenshots/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwStar/vue-ast/HEAD/screenshots/7.png -------------------------------------------------------------------------------- /screenshots/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwStar/vue-ast/HEAD/screenshots/8.png -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 14 | 15 | 16 |

请打开控制台

17 |
18 | 19 | 20 | 21 |
22 | 23 | 33 | 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **什么是AST** 2 | ---------- 3 | 4 | 在Vue的mount过程中,template会被编译成AST语法树,AST是指抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式。 5 | 6 | **Virtual Dom** 7 | --------- 8 | Vue的一个厉害之处就是利用Virtual DOM模拟DOM对象树来优化DOM操作的一种技术或思路。 9 | Vue源码中虚拟DOM构建经历 template编译成AST语法树 -> 再转换为render函数 最终返回一个VNode(VNode就是Vue的虚拟DOM节点) 10 | 本文通过对源码中AST转化部分进行简单提取,因为源码中转化过程还需要进行各种兼容判断,非常复杂,所以笔者对主要功能代码进行提取,用了300-400行代码完成对template转化为AST这个功能。下面用具体代码进行分析。 11 | 12 | ``` 13 | function parse(template) { 14 | var currentParent; //当前父节点 15 | var root; //最终返回出去的AST树根节点 16 | var stack = []; 17 | parseHTML(template, { 18 | start: function start(tag, attrs, unary) { 19 | ...... 20 | }, 21 | end: function end() { 22 | ...... 23 | }, 24 | chars: function chars(text) { 25 | ...... 26 | } 27 | }) 28 | return root 29 | } 30 | ``` 31 | 32 | 第一步就是调用parse这个方法,把template传进来,这里假设template为 `
{{message}}
` 33 | 34 | 然后声明3个变量 35 | currentParent -> 存放当前父元素,root -> 最终返回出去的AST树根节点,stack -> 一个栈用来辅助树的建立 36 | 接着调用parseHTML函数进行转化,传入template和options(包含3个方法 start,end,chars 等下用到这3个函数再进行解释)接下来先看parseHTML这个方法 37 | 38 | ``` 39 | function parseHTML(html, options) { 40 | var stack = []; //这里和上面的parse函数一样用到stack这个数组 不过这里的stack只是为了简单存放标签名 为了和结束标签进行匹配的作用 41 | var isUnaryTag$$1 = isUnaryTag; //判断是否为自闭合标签 42 | var index = 0; 43 | var last; 44 | while (html) { 45 | //  第一次进入while循环时,由于字符串以<开头,所以进入startTag条件,并进行AST转换,最后将对象弹入stack数组中 46 | last = html; 47 | var textEnd = html.indexOf('<'); 48 | if (textEnd === 0) { // 此时字符串是不是以<开头 49 | // End tag: 50 | var endTagMatch = html.match(endTag); 51 | if (endTagMatch) { 52 | var curIndex = index; 53 | advance(endTagMatch[0].length); 54 | parseEndTag(endTagMatch[1], curIndex, index); 55 | continue 56 | } 57 | 58 | // Start tag: // 匹配起始标签 59 | var startTagMatch = parseStartTag(); //处理后得到match 60 | if (startTagMatch) { 61 | handleStartTag(startTagMatch); 62 | continue 63 | } 64 | } 65 | 66 | // 初始化为undefined 这样安全且字符数少一点 67 | var text = (void 0), rest = (void 0), next = (void 0); 68 | if (textEnd >= 0) { // 截取<字符索引 => 这里截取到闭合的< 69 | rest = html.slice(textEnd); //截取闭合标签 70 | // 处理文本中的<字符 71 | // 获取中间的字符串 => {{message}} 72 | text = html.substring(0, textEnd); //截取到闭合标签前面部分 73 | advance(textEnd); //切除闭合标签前面部分 74 | 75 | } 76 | // 当字符串没有<时 77 | if (textEnd < 0) { 78 | text = html; 79 | html = ''; 80 | } 81 | // // 处理文本 82 | if (options.chars && text) { 83 | options.chars(text); 84 | } 85 | } 86 | } 87 | ``` 88 | 函数进入while循环对html进行获取`<`标签索引 `var textEnd = html.indexOf('<');`如果textEnd === 0 说明当前是标签或者 再用正则匹配是否当前是结束标签。`var endTagMatch = html.match(endTag);` 匹配不到那么就是开始标签,调用parseStartTag()函数解析。 89 | 90 | ``` 91 | function parseStartTag() { //返回匹配对象 92 | var start = html.match(startTagOpen); // 正则匹配 93 | if (start) { 94 | var match = { 95 | tagName: start[1], // 标签名(div) 96 | attrs: [], // 属性 97 | start: index // 游标索引(初始为0) 98 | }; 99 | advance(start[0].length); 100 | var end, attr; 101 | while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { 102 | advance(attr[0].length); 103 | match.attrs.push(attr); 104 | } 105 | if (end) { 106 | advance(end[0].length); // 标记结束位置 107 | match.end = index; //这里的index 是在 parseHTML就定义 在advance里面相加 108 | return match // 返回匹配对象 起始位置 结束位置 tagName attrs 109 | } 110 | } 111 | } 112 | 113 | ``` 114 | 该函数主要是为了构建一个match对象,对象里面包含tagName(标签名),attrs(标签的属性),start(`<`左开始标签在template中的位置),end(`>`右开始标签在template中的位置) 如template = `
{{message}}
` 程序第一次进入该函数 匹配的是div标签 所以tagName就是div 115 | start:0 end:14 如图: 116 | 117 | 118 | 119 | 接着把match返回出去 作为调用handleStartTag的参数 120 | 121 | ``` 122 | var startTagMatch = parseStartTag(); //处理后得到match 123 | if (startTagMatch) { 124 | handleStartTag(startTagMatch); 125 | continue 126 | } 127 | ``` 128 | 129 | 接下来看handleStartTag这个函数: 130 | 131 | ``` 132 | function handleStartTag(match) { 133 | var tagName = match.tagName; 134 | var unary = isUnaryTag$$1(tagName) //判断是否为闭合标签 135 | var l = match.attrs.length; 136 | var attrs = new Array(l); 137 | for (var i = 0; i < l; i++) { 138 | var args = match.attrs[i]; 139 | var value = args[3] || args[4] || args[5] || ''; 140 | attrs[i] = { 141 | name: args[1], 142 | value: value 143 | }; 144 | } 145 | if (!unary) { 146 | stack.push({tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs}); 147 | lastTag = tagName; 148 | } 149 | if (options.start) { 150 | options.start(tagName, attrs, unary, match.start, match.end); 151 | } 152 | } 153 | ``` 154 | 函数中分为3部分 第一部分是for循环是对attrs进行转化,我们从上一步的parseStartTag()得到的match对象中的attrs属性如图 155 | 156 | 157 | 158 | 当时attrs是上面图这样子滴 我们通过这个循环把它转化为只带name 和 value这2个属性的对象 如图: 159 | 160 | 161 | 162 | 接着判断如果不是自闭合标签,把标签名和属性推入栈中(注意 这里的stack这个变量在parseHTML中定义,作用是为了存放标签名 为了和结束标签进行匹配的作用。)接着调用最后一步 options.start 这里的options就是我们在parse函数中 调用parseHTML是传进来第二个参数的那个对象(包含start end chars 3个方法函数) 这里开始看options.start这个函数的作用: 163 | 164 | ``` 165 | start: function start(tag, attrs, unary) { 166 | var element = { 167 | type: 1, 168 | tag: tag, 169 | attrsList: attrs, 170 | attrsMap: makeAttrsMap(attrs), 171 | parent: currentParent, 172 | children: [] 173 | }; 174 | processAttrs(element); 175 | if (!root) { 176 | root = element; 177 | } 178 | if(currentParent){ 179 | currentParent.children.push(element); 180 | element.parent = currentParent; 181 | } 182 | if (!unary) { 183 | currentParent = element; 184 | stack.push(element); 185 | } 186 | } 187 | ``` 188 | 这个函数中 生成element对象 再连接元素的parent 和 children节点 最终push到栈中 189 | 此时栈中第一个元素生成 如图: 190 | 191 | 192 | 193 | 194 | 195 | 完成了while循环的第一次执行,进入第二次循环执行,这个时候html变成`{{message}}` 接着截取到 处理过程和第一次一致 经过这次循环stack中元素如图: 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 接着继续执行第三个循环 这个时候是处理文本节点了 {{message}} 204 | 205 | ``` 206 | // 初始化为undefined 这样安全且字符数少一点 207 | var text = (void 0), rest = (void 0), next = (void 0); 208 | if (textEnd >= 0) { // 截取<字符索引 => 这里截取到闭合的< 209 | rest = html.slice(textEnd); //截取闭合标签 210 | // 处理文本中的<字符 211 | // 获取中间的字符串 => {{message}} 212 | text = html.substring(0, textEnd); //截取到闭合标签前面部分 213 | advance(textEnd); //切除闭合标签前面部分 214 | } 215 | // 当字符串没有<时 216 | if (textEnd < 0) { 217 | text = html; 218 | html = ''; 219 | } 220 | // 另外一个函数 221 | if (options.chars && text) { 222 | options.chars(text); 223 | } 224 | ``` 225 | 这里的作用就是把文本提取出来 调用options.chars这个函数 接下来看options.chars 226 | 227 | ``` 228 | chars: function chars(text) { 229 | if (!currentParent) { //如果没有父元素 只是文本 230 | return 231 | } 232 | 233 | var children = currentParent.children; //取出children 234 | // text => {{message}} 235 | if (text) { 236 | var expression; 237 | if (text !== ' ' && (expression = parseText(text))) { 238 | // 将解析后的text存进children数组 239 | children.push({ 240 | type: 2, 241 | expression: expression, 242 | text: text 243 | }); 244 | } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') { 245 | children.push({ 246 | type: 3, 247 | text: text 248 | }); 249 | } 250 | } 251 | } 252 | }) 253 | ``` 254 | 这里的主要功能是判断文本是{{xxx}}还是简单的文本xxx,如果是简单的文本 push进父元素的children里面,type设置为3,如果是字符模板{{xxx}},调用parseText转化。如这里的`{{message}}`转化为 `_s(message)`(加上_s是为了AST的下一步转为render函数,本文中暂时不会用到。) 再把转化后的内容push进children。 255 | 256 | 257 | 258 | 259 | 又走完一个循环了,这个时候html = `` 剩下2个结束标签进行匹配了 260 | 261 | ``` 262 | var endTagMatch = html.match(endTag); 263 | if (endTagMatch) { 264 | var curIndex = index; 265 | advance(endTagMatch[0].length); 266 | parseEndTag(endTagMatch[1], curIndex, index); 267 | continue 268 | } 269 | ``` 270 | 接下来看parseEndTag这个函数 传进来了标签名 开始索引和结束索引 271 | 272 | ``` 273 | function parseEndTag(tagName, start, end) { 274 | var pos, lowerCasedTagName; 275 | if (tagName) { 276 | lowerCasedTagName = tagName.toLowerCase(); 277 | } 278 | // Find the closest opened tag of the same type 279 | if (tagName) { // 获取最近的匹配标签 280 | for (pos = stack.length - 1; pos >= 0; pos--) { 281 | // 提示没有匹配的标签 282 | if (stack[pos].lowerCasedTag === lowerCasedTagName) { 283 | break 284 | } 285 | } 286 | } else { 287 | // If no tag name is provided, clean shop 288 | pos = 0; 289 | } 290 | 291 | if (pos >= 0) { 292 | // Close all the open elements, up the stack 293 | for (var i = stack.length - 1; i >= pos; i--) { 294 | if (options.end) { 295 | options.end(stack[i].tag, start, end); 296 | } 297 | } 298 | 299 | // Remove the open elements from the stack 300 | stack.length = pos; 301 | lastTag = pos && stack[pos - 1].tag; 302 | } 303 | ``` 304 | 这里首先找到栈中对应的开始标签的索引pos,再从该索引开始到栈顶的所以元素调用options.end这个函数 305 | 306 | ``` 307 | end: function end() { 308 | // pop stack 309 | stack.length -= 1; 310 | currentParent = stack[stack.length - 1]; 311 | }, 312 | ``` 313 | 把栈顶元素出栈,因为这个元素已经匹配到结束标签了,再把当前父元素更改。终于走完了,把html的内容循环完,最终return root 这个root就是我们所要得到的AST 314 | 315 | 316 | 317 | 这只是Vue的冰山一角,文中有什么不对的地方请大家帮忙指正,本人最近也一直在学习Vue的源码,希望能够拿出来与大家一起分享经验,接下来会继续更新后续的源码,如果觉得有帮忙请给个Star哈 318 | 319 | ## 项目运行 320 | ``` 321 | git clone https://github.com/zwStar/vue-ast 322 | 323 | 打开demo.html 324 | 325 | F12打开控制台 然后在input中输入template 控制台可以看到转换后的AST树 326 | 327 | ``` 328 | 329 | -------------------------------------------------------------------------------- /vue-ast.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | (global.Parse = factory()) 3 | }(this, (function () { 4 | var ncname = '[a-zA-Z_][\\w\\-\\.]*'; 5 | var qnameCapture = '((?:' + ncname + '\\:)?' + ncname + ')'; 6 | var startTagOpen = new RegExp('^<' + qnameCapture); //匹配开始标签的 < 7 | var startTagClose = /^\s*(\/?)>/; //匹配 开始标签的 > 8 | var endTag = new RegExp('^<\\/' + qnameCapture + '[^>]*>'); //匹配结束标签 9 | 10 | // Regular Expressions for parsing tags and attributes 11 | var singleAttrIdentifier = /([^\s"'<>/=]+)/; 12 | var singleAttrAssign = /(?:=)/; 13 | var singleAttrValues = [ 14 | // attr value double quotes 15 | /"([^"]*)"+/.source, 16 | // attr value, single quotes 17 | /'([^']*)'+/.source, 18 | // attr value, no quotes 19 | /([^\s"'=<>`]+)/.source 20 | ]; 21 | var attribute = new RegExp( 22 | '^\\s*' + singleAttrIdentifier.source + 23 | '(?:\\s*(' + singleAttrAssign.source + ')' + 24 | '\\s*(?:' + singleAttrValues.join('|') + '))?' 25 | ); 26 | 27 | var defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g; //匹配 {{xxxx}} 28 | function makeMap(str, 29 | expectsLowerCase //true 30 | ) { 31 | var map = Object.create(null); //创建一个对象 32 | var list = str.split(','); 33 | for (var i = 0; i < list.length; i++) { 34 | map[list[i]] = true; 35 | } 36 | return expectsLowerCase 37 | ? function (val) { 38 | return map[val.toLowerCase()]; 39 | } 40 | : function (val) { 41 | return map[val]; 42 | } 43 | } 44 | 45 | var isUnaryTag = makeMap( // 自闭合标签 46 | 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' + 47 | 'link,meta,param,source,track,wbr' 48 | ); 49 | 50 | function Parse(template) { 51 | return this._init(template); 52 | } 53 | function parseHTML(html, options) { 54 | var stack = []; 55 | var isUnaryTag$$1 = isUnaryTag; //判断是否为自闭合标签 56 | var index = 0; 57 | var last, lastTag; //lastTag 为了匹配结束标签 因为执行一次后 lastTag会被赋值tagName 58 | while (html) { 59 | //  第一次进入while循环时,由于字符串以<开头,所以进入startTag条件,并进行AST转换,最后将对象弹入stack数组中 60 | //  而这一次,字符串开头为{,所以会继续执行下面的代码。代码将{{message}}作为text抽离出来,并调用了参数中另外一个函数:options 61 | last = html; 62 | var textEnd = html.indexOf('<'); 63 | if (textEnd === 0) { // 此时字符串是不是以<开头 64 | // End tag: 65 | var endTagMatch = html.match(endTag); 66 | if (endTagMatch) { 67 | var curIndex = index; 68 | advance(endTagMatch[0].length); 69 | parseEndTag(endTagMatch[1], curIndex, index); 70 | continue 71 | } 72 | 73 | // Start tag: // 匹配起始标签 74 | var startTagMatch = parseStartTag(); //处理后得到match 75 | if (startTagMatch) { 76 | handleStartTag(startTagMatch); 77 | continue 78 | } 79 | } 80 | 81 | // 初始化为undefined 这样安全且字符数少一点 82 | var text = (void 0), rest = (void 0), next = (void 0); 83 | if (textEnd >= 0) { // 截取<字符索引 => 这里截取到闭合的< 84 | rest = html.slice(textEnd); //截取闭合标签 85 | // 处理文本中的<字符 86 | // 获取中间的字符串 => {{message}} 87 | text = html.substring(0, textEnd); //截取到闭合标签前面部分 88 | advance(textEnd); //切除闭合标签前面部分 89 | 90 | } 91 | // 当字符串没有<时 92 | if (textEnd < 0) { 93 | text = html; 94 | html = ''; 95 | } 96 | // // 另外一个函数 97 | if (options.chars && text) { 98 | options.chars(text); 99 | } 100 | } 101 | 102 | // 该函数将函数局部变量index往前推 并切割字符串 103 | function advance(n) { 104 | index += n; 105 | html = html.substring(n); 106 | } 107 | 108 | function parseStartTag() { //返回匹配对象 109 | var start = html.match(startTagOpen); // 正则匹配 110 | if (start) { 111 | var match = { 112 | tagName: start[1], // 标签名(div) 113 | attrs: [], // 属性 114 | start: index // 游标索引(初始为0) 115 | }; 116 | advance(start[0].length); 117 | var end, attr; // 进行属性的正则匹配 118 | // startTagClose匹配/>或> attribute匹配属性 正则太长 没法讲 本例中attr匹配后 => ['id=app','id','=','app'] 119 | while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { //如果不是 > 标签 并且是attribute 比如
先得到app 第二次while 的dao> 120 | advance(attr[0].length); // 属性加入 121 | match.attrs.push(attr); 122 | } 123 | if (end) { // 第一while匹配到 attr 第二次就能end到 在第二次while循环后 end匹配到结束标签 => ['>',''] 124 | // match.unarySlash = end[1]; //如果是> end[1]就是"" 如果过 div> end[1] 就是div 125 | advance(end[0].length); // 标记结束位置 126 | match.end = index; //这里的index 是在 parseHTML就定义 在advance里面相加 127 | return match // 返回匹配对象 起始位置 结束位置 tagName attrs 128 | } 129 | } 130 | } 131 | 132 | function handleStartTag(match) { 133 | var tagName = match.tagName; 134 | // var unarySlash = match.unarySlash; 135 | // if (expectHTML) { 136 | // if (lastTag === 'p' && isNonPhrasingTag(tagName)) { 137 | // parseEndTag(lastTag); 138 | // } 139 | // if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) { 140 | // parseEndTag(tagName); 141 | // } 142 | // } 143 | var unary = isUnaryTag$$1(tagName) 144 | var l = match.attrs.length; 145 | var attrs = new Array(l); 146 | for (var i = 0; i < l; i++) { 147 | var args = match.attrs[i]; 148 | var value = args[3] || args[4] || args[5] || ''; 149 | attrs[i] = { 150 | name: args[1], 151 | value: value 152 | }; 153 | } 154 | if (!unary) { 155 | stack.push({tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs}); 156 | lastTag = tagName; 157 | } 158 | if (options.start) { 159 | options.start(tagName, attrs, unary, match.start, match.end); 160 | } 161 | } 162 | 163 | function parseEndTag(tagName, start, end) { 164 | // 参数修正 165 | var pos, lowerCasedTagName; 166 | if (start == null) { 167 | start = index; 168 | } 169 | if (end == null) { 170 | end = index; 171 | } 172 | 173 | if (tagName) { 174 | lowerCasedTagName = tagName.toLowerCase(); 175 | } 176 | 177 | // Find the closest opened tag of the same type 178 | if (tagName) { // 获取最近的匹配标签 179 | for (pos = stack.length - 1; pos >= 0; pos--) { 180 | // 提示没有匹配的标签 181 | if (stack[pos].lowerCasedTag === lowerCasedTagName) { 182 | break 183 | } 184 | } 185 | } else { 186 | // If no tag name is provided, clean shop 187 | pos = 0; 188 | } 189 | 190 | if (pos >= 0) { 191 | // Close all the open elements, up the stack 192 | for (var i = stack.length - 1; i >= pos; i--) { 193 | if (options.end) { 194 | options.end(stack[i].tag, start, end); 195 | } 196 | } 197 | 198 | // Remove the open elements from the stack 199 | stack.length = pos; 200 | lastTag = pos && stack[pos - 1].tag; 201 | } else if (lowerCasedTagName === 'br') { 202 | if (options.start) { 203 | options.start(tagName, [], true, start, end); 204 | } 205 | } else if (lowerCasedTagName === 'p') { 206 | if (options.start) { 207 | options.start(tagName, [], false, start, end); 208 | } 209 | if (options.end) { // 调用剩下的一个参数函数 210 | options.end(tagName, start, end); 211 | } 212 | } 213 | } 214 | } 215 | function parse(template) { 216 | var currentParent; 217 | var root; 218 | var stack = []; 219 | parseHTML(template, { 220 | start: function start(tag, attrs, unary) { 221 | var element = { 222 | type: 1, 223 | tag: tag, 224 | attrsList: attrs, 225 | attrsMap: makeAttrsMap(attrs), 226 | parent: currentParent, 227 | children: [] 228 | }; 229 | processAttrs(element); 230 | if (!root) { 231 | root = element; 232 | } 233 | if(currentParent){ 234 | currentParent.children.push(element); 235 | element.parent = currentParent; 236 | } 237 | if (!unary) { 238 | currentParent = element; 239 | stack.push(element); 240 | } 241 | }, 242 | end: function end() { 243 | // remove trailing whitespace 244 | var element = stack[stack.length - 1]; /*从stack中取出最后一个ele*/ 245 | var lastNode = element.children[element.children.length - 1]; /*获取该ele的最后一个子节点*/ 246 | // /*该子节点是非
标签的文本*/
247 |                 if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
248 |                     element.children.pop();
249 |                 }
250 |                 // pop stack
251 |                 stack.length -= 1;
252 |                 currentParent = stack[stack.length - 1];
253 |             },
254 |             chars: function chars(text) {
255 |                 if (!currentParent) {   //如果没有父元素 只是文本
256 |                     return
257 |                 }
258 | 
259 |                 var children = currentParent.children;  //取出children
260 |                 // text => {{message}}
261 |                 if (text) {
262 |                     var expression;
263 |                     if (text !== ' ' && (expression = parseText(text))) {
264 |                         // 将解析后的text弄进children数组
265 |                         children.push({
266 |                             type: 2,
267 |                             expression: expression,
268 |                             text: text
269 |                         });
270 |                     } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
271 |                         children.push({
272 |                             type: 3,
273 |                             text: text
274 |                         });
275 |                     }
276 |                 }
277 |             }
278 |         })
279 |         return root
280 |     }
281 |     // 在最后,调用processAttrs对动态绑定的属性(v-,@,:)进行处理,代码如下:
282 |     function processAttrs(el) {
283 |         // {name:'id',value:'app'}
284 |         /*获取元素属性列表*/
285 |         var list = el.attrsList;
286 |         var i, l, name, rawName, value, modifiers, isProp;
287 |         for (i = 0, l = list.length; i < l; i++) {
288 |             // 属性名
289 |             name = rawName = list[i].name;
290 |             // 属性值
291 |             value = list[i].value;
292 |             addAttr(el, name, JSON.stringify(value));        // 添加了个attrs属性  /*将属性放入ele的attr属性中*/
293 |         }
294 |     }
295 | 
296 |     function addAttr(el, name, value) {
297 |         (el.attrs || (el.attrs = [])).push({name: name, value: value});
298 |     }
299 |     function parseText(text,    //对Text进行解析
300 |                        delimiters) {
301 |         var tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE;       // 如果delimiters为false defaultTagRE 为匹配{{xxx}}的正则
302 |         if (!tagRE.test(text)) {        // /\{\{((?:.|\n)+?)\}\}/g 在这里调用test方法后lasatIndex会变化
303 |             return
304 |         }
305 |         var tokens = [];
306 |         var lastIndex = tagRE.lastIndex = 0;
307 |         var match, index;
308 | 
309 |         // 0:"{{message}}"
310 |         // 1:"message"
311 |         // index:0
312 |         // input:"{{message}}"
313 | 
314 |         // 匹配到中间的文本
315 |         while ((match = tagRE.exec(text))) {
316 |             index = match.index;
317 |             // push text token
318 |             // 将{{message}}之前的文本push进去
319 |             if (index > lastIndex) {
320 |                 tokens.push(JSON.stringify(text.slice(lastIndex, index)));
321 |             }
322 |             // tag token
323 |             // 该方法对特殊字符进行处理
324 |             var exp = (match[1].trim());
325 |             tokens.push(("_s(" + exp + ")"));
326 |             lastIndex = index + match[0].length;
327 |         }
328 |         if (lastIndex < text.length) {       // push}}后面的文本
329 |             tokens.push(JSON.stringify(text.slice(lastIndex)));
330 |         }
331 |         return tokens.join('+')
332 |     }
333 | 
334 |     function makeAttrsMap(attrs) {
335 |         var map = {};
336 |         for (var i = 0, l = attrs.length; i < l; i++) {
337 |             map[attrs[i].name] = attrs[i].value;
338 |         }
339 |         return map
340 |     }
341 |     Parse.prototype._init = function(template){
342 |         return parse(template)
343 |     }
344 |     return Parse;
345 | })));
346 | 
347 | 
348 | 
349 | 
350 | 
351 | 
352 | 
353 | 
354 | 


--------------------------------------------------------------------------------