前言

本文是vue2.x源码分析的第六篇,主要讲解编译compile过程!

调用方式

  1. var compiled = compile(template, options);

1 分析compile

  1. //tips:请结合断点调试,该函数位于闭包createCompiler中,有的变量是在上层函数中定义的
  2. function compile (template, options) {
  3. var finalOptions = Object.create(baseOptions);
  4. var errors = [];
  5. var tips = [];
  6. finalOptions.warn = function (msg, tip$$1) {
  7. (tip$$1 ? tips : errors).push(msg);
  8. };
  9. if (options) {
  10. //合并自定义modules
  11. if (options.modules) {
  12. finalOptions.modules = (baseOptions.modules || []).concat(options.modules);
  13. }
  14. //合并自定义directives
  15. if (options.directives) {
  16. finalOptions.directives = extend(
  17. Object.create(baseOptions.directives),
  18. options.directives
  19. );
  20. }
  21. // copy other options
  22. for (var key in options) {
  23. if (key !== 'modules' && key !== 'directives') {
  24. finalOptions[key] = options[key];
  25. }
  26. }
  27. }
  28. /*以上都是处理finalOptions,到这里finalOptions如下:
  29. {
  30. delimiters:undefined,
  31. shouldDecodeNewlines:false,
  32. warn:function (msg, tip$$1),
  33. __proto__:Object
  34. }
  35. 这个__proto__指向一个预先定义好的baseOptions对象,该对象长这样:
  36. var baseOptions = {
  37. expectHTML: true,
  38. modules: modules$1,//modules$1=[klass$1,style$1]
  39. directives: directives$1, //这里预先定义了html,text,model三个指令
  40. isPreTag: isPreTag,
  41. isUnaryTag: isUnaryTag,
  42. mustUseProp: mustUseProp,
  43. canBeLeftOpenTag: canBeLeftOpenTag,
  44. isReservedTag: isReservedTag,
  45. getTagNamespace: getTagNamespace,
  46. staticKeys: genStaticKeys(modules$1)
  47. };
  48. */
  49. var compiled = baseCompile(template, finalOptions); //主要函数
  50. {
  51. errors.push.apply(errors, detectErrors(compiled.ast));
  52. }
  53. compiled.errors = errors;
  54. compiled.tips = tips;
  55. return compiled
  56. }

来看看baseCompile(template, finalOptions)

  1. function baseCompile (template,options) {
  2. var ast = parse(template.trim(), options); //主要函数1
  3. optimize(ast, options);
  4. var code = generate(ast, options); //主要函数2
  5. return {
  6. ast: ast,
  7. render: code.render,
  8. staticRenderFns: code.staticRenderFns
  9. }
  10. }

2 分析 parse(template.trim(), options);

  1. //主要是调用parseHTML(html, options)解析html,返回结果ast是含有如下属性的对象
  2. // attrs:Array
  3. // attrsList:Array
  4. // attrsMap:Object
  5. // children:Array
  6. // parent:undefined
  7. // plain:false
  8. // static:false
  9. // staticRoot:false
  10. // tag:"div"
  11. // type:1
  12. // __proto__:Object
  13. function parse (template,options) {
  14. warn$2 = options.warn || baseWarn;
  15. platformGetTagNamespace = options.getTagNamespace || no;
  16. platformMustUseProp = options.mustUseProp || no;
  17. platformIsPreTag = options.isPreTag || no;
  18. //这个pluckModuleFunction函数作用就是从options.modules中取出key为'preTransformNode'的值
  19. preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
  20. transforms = pluckModuleFunction(options.modules, 'transformNode');
  21. postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');
  22. delimiters = options.delimiters;
  23. var stack = [];
  24. var preserveWhitespace = options.preserveWhitespace !== false;
  25. var root; //作为结果返回
  26. var currentParent;
  27. var inVPre = false;
  28. var inPre = false;
  29. var warned = false;
  30. function warnOnce (msg) {
  31. if (!warned) {
  32. warned = true;
  33. warn$2(msg);
  34. }
  35. }
  36. function endPre (element) {
  37. // check pre state
  38. if (element.pre) {
  39. inVPre = false;
  40. }
  41. if (platformIsPreTag(element.tag)) {
  42. inPre = false;
  43. }
  44. }
  45. //parseHTML第二个参数里有很重要的三个函数:start,end,chars
  46. parseHTML(template, {
  47. warn: warn$2,
  48. expectHTML: options.expectHTML,
  49. isUnaryTag: options.isUnaryTag,
  50. canBeLeftOpenTag: options.canBeLeftOpenTag,
  51. shouldDecodeNewlines: options.shouldDecodeNewlines,
  52. //start和end函数负责构建节点树
  53. start: function start (tag, attrs, unary) {
  54. // check namespace.
  55. // inherit parent ns if there is one
  56. var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);
  57. // handle IE svg bug
  58. /* istanbul ignore if */
  59. if (isIE && ns === 'svg') {
  60. attrs = guardIESVGBug(attrs);
  61. }
  62. var element = { //节点
  63. type: 1,
  64. tag: tag,
  65. attrsList: attrs,
  66. attrsMap: makeAttrsMap(attrs),
  67. parent: currentParent,
  68. children: []
  69. };
  70. if (ns) {
  71. element.ns = ns;
  72. }
  73. //不处理style和script标签
  74. if (isForbiddenTag(element) && !isServerRendering()) {
  75. element.forbidden = true;
  76. "development" !== 'production' && warn$2(
  77. 'Templates should only be responsible for mapping the state to the ' +
  78. 'UI. Avoid placing tags with side-effects in your templates, such as ' +
  79. "<" + tag + ">" + ', as they will not be parsed.'
  80. );
  81. }
  82. // 猜测:html如果用了其他的模板,如ejs等需要先转换
  83. // apply pre-transforms
  84. for (var i = 0; i < preTransforms.length; i++) {
  85. preTransforms[i](element, options);
  86. }
  87. //处理v-pre指令
  88. if (!inVPre) {
  89. processPre(element);
  90. if (element.pre) {
  91. inVPre = true;
  92. }
  93. }
  94. if (platformIsPreTag(element.tag)) {
  95. inPre = true;
  96. }
  97. //如果含有v-pre指令,则直接调用processRawAttrs(element);处理原始属性
  98. if (inVPre) {
  99. processRawAttrs(element);
  100. } else {
  101. processFor(element);//处理v-for指令,会将v-for='xx'替换成其他字符串
  102. processIf(element);//处理v-if指令
  103. processOnce(element);//处理v-once指令
  104. processKey(element);//处理key
  105. // determine whether this is a plain element after
  106. // removing structural attributes
  107. // 移除结构性属性后判断该元素是不是plain元素
  108. element.plain = !element.key && !attrs.length;
  109. processRef(element);//处理ref
  110. processSlot(element);//处理slot
  111. processComponent(element);//处理component
  112. for (var i$1 = 0; i$1 < transforms.length; i$1++) {
  113. transforms[i$1](element, options); //对class和style属性进行处理
  114. }
  115. //以上处理了v-for,v-if,v-once,v-pre等指令,但还有其它指令,如v-on,v-bind,
  116. //以及它们的快捷写法'@:',':',该函数就是处理这些指令以及普通元素,处理的结果就是
  117. //在element上加了一个attrs属性,存放原始属性
  118. processAttrs(element);
  119. }
  120. function checkRootConstraints (el) {
  121. {
  122. if (el.tag === 'slot' || el.tag === 'template') {
  123. warnOnce(
  124. "Cannot use <" + (el.tag) + "> as component root element because it may " +
  125. 'contain multiple nodes.'
  126. );
  127. }
  128. if (el.attrsMap.hasOwnProperty('v-for')) {
  129. warnOnce(
  130. 'Cannot use v-for on stateful component root element because ' +
  131. 'it renders multiple elements.'
  132. );
  133. }
  134. }
  135. }
  136. // 经过上述处理后,由于可能有v-if这种会改变树结构的指令,所以需要对结构树
  137. // 进一步处理,至此第一轮while循环解析完成,接下来就是重复这个过程了
  138. if (!root) {
  139. root = element;
  140. checkRootConstraints(root); //根节点不能是slot/template元素,且不能含有v-for指令
  141. } else if (!stack.length) {
  142. // 允许根元素使用 v-if, v-else-if and v-else
  143. if (root.if && (element.elseif || element.else)) {
  144. checkRootConstraints(element);
  145. addIfCondition(root, {
  146. exp: element.elseif,
  147. block: element
  148. });
  149. } else {
  150. warnOnce(
  151. "Component template should contain exactly one root element. " +
  152. "If you are using v-if on multiple elements, " +
  153. "use v-else-if to chain them instead."
  154. );
  155. }
  156. }
  157. if (currentParent && !element.forbidden) {
  158. if (element.elseif || element.else) {
  159. processIfConditions(element, currentParent);
  160. } else if (element.slotScope) { // scoped slot
  161. currentParent.plain = false;
  162. var name = element.slotTarget || '"default"';(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element;
  163. } else {
  164. currentParent.children.push(element);
  165. element.parent = currentParent;
  166. }
  167. }
  168. if (!unary) {
  169. currentParent = element;
  170. stack.push(element);
  171. } else {
  172. endPre(element);
  173. }
  174. // apply post-transforms
  175. for (var i$2 = 0; i$2 < postTransforms.length; i$2++) {
  176. postTransforms[i$2](element, options);
  177. }
  178. },
  179. end: function end () {
  180. // 删除尾随空格
  181. var element = stack[stack.length - 1];
  182. var lastNode = element.children[element.children.length - 1];
  183. if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
  184. element.children.pop();
  185. }
  186. // pop stack
  187. stack.length -= 1;
  188. currentParent = stack[stack.length - 1];
  189. endPre(element);
  190. },
  191. chars: function chars (text) {
  192. if (!currentParent) {
  193. {
  194. if (text === template) {
  195. warnOnce(
  196. 'Component template requires a root element, rather than just text.'
  197. );
  198. } else if ((text = text.trim())) {
  199. warnOnce(
  200. ("text \"" + text + "\" outside root element will be ignored.")
  201. );
  202. }
  203. }
  204. return
  205. }
  206. // IE textarea placeholder bug
  207. /* istanbul ignore if */
  208. if (isIE &&
  209. currentParent.tag === 'textarea' &&
  210. currentParent.attrsMap.placeholder === text) {
  211. return
  212. }
  213. var children = currentParent.children;
  214. text = inPre || text.trim()
  215. ? decodeHTMLCached(text)
  216. // only preserve whitespace if its not right after a starting tag
  217. : preserveWhitespace && children.length ? ' ' : '';
  218. if (text) {
  219. var expression;
  220. if (!inVPre && text !== ' ' && (expression = parseText(text, delimiters))) {
  221. children.push({
  222. type: 2,
  223. expression: expression,
  224. text: text
  225. });
  226. } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
  227. children.push({
  228. type: 3,
  229. text: text
  230. });
  231. }
  232. }
  233. }
  234. });
  235. return root
  236. }

来看看parseHTML(template,options)

  1. /*解析过程中最重要的函数,因此代码量较大*/
  2. function parseHTML (html, options) { //将template传给html
  3. var stack = [];
  4. var expectHTML = options.expectHTML;
  5. var isUnaryTag$$1 = options.isUnaryTag || no; //是否是一元标签
  6. var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;
  7. var index = 0;
  8. var last, lastTag;
  9. while (html) { //通过while循环一步步处理html,每处理一步就缩短html,直至html为空
  10. last = html;
  11. // 不处理script/style/textarea元素
  12. if (!lastTag || !isPlainTextElement(lastTag)) {
  13. var textEnd = html.indexOf('<');
  14. if (textEnd === 0) {
  15. // 当匹配到Comment,只对html推进,不做其他处理
  16. if (comment.test(html)) {
  17. var commentEnd = html.indexOf('-->');
  18. if (commentEnd >= 0) {
  19. advance(commentEnd + 3);
  20. continue
  21. }
  22. }
  23. //当匹配到conditionalComment,同Comment一样处理
  24. // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
  25. if (conditionalComment.test(html)) {
  26. var conditionalEnd = html.indexOf(']>')
  27. if (conditionalEnd >= 0) {
  28. advance(conditionalEnd + 2);
  29. continue
  30. }
  31. }
  32. //当匹配到doctype,同Comment一样处理,/^<!DOCTYPE [^>]+>/i
  33. var doctypeMatch = html.match(doctype);
  34. if (doctypeMatch) {
  35. advance(doctypeMatch[0].length);
  36. continue
  37. }
  38. // 当匹配到end tag,同Comment一样处理,/^<\/((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)[^>]*>/
  39. var endTagMatch = html.match(endTag);
  40. if (endTagMatch) {
  41. var curIndex = index;
  42. advance(endTagMatch[0].length);
  43. parseEndTag(endTagMatch[1], curIndex, index);
  44. continue
  45. }
  46. // 除以上四种,就默认以下处理
  47. var startTagMatch = parseStartTag();
  48. if (startTagMatch) {
  49. handleStartTag(startTagMatch);
  50. continue
  51. }
  52. }
  53. var text = (void 0), rest$1 = (void 0), next = (void 0);
  54. if (textEnd >= 0) {
  55. rest$1 = html.slice(textEnd);
  56. while (
  57. !endTag.test(rest$1) &&
  58. !startTagOpen.test(rest$1) &&
  59. !comment.test(rest$1) &&
  60. !conditionalComment.test(rest$1)
  61. ) {
  62. // < in plain text, be forgiving and treat it as text
  63. next = rest$1.indexOf('<', 1);
  64. if (next < 0) { break }
  65. textEnd += next;
  66. rest$1 = html.slice(textEnd);
  67. }
  68. text = html.substring(0, textEnd);
  69. advance(textEnd);
  70. }
  71. if (textEnd < 0) {
  72. text = html;
  73. html = '';
  74. }
  75. if (options.chars && text) {
  76. options.chars(text);
  77. }
  78. }
  79. else {
  80. var stackedTag = lastTag.toLowerCase();
  81. var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'));
  82. var endTagLength = 0;
  83. var rest = html.replace(reStackedTag, function (all, text, endTag) {
  84. endTagLength = endTag.length;
  85. if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
  86. text = text
  87. .replace(/<!--([\s\S]*?)-->/g, '$1')
  88. .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
  89. }
  90. if (options.chars) {
  91. options.chars(text);
  92. }
  93. return ''
  94. });
  95. index += html.length - rest.length;
  96. html = rest;
  97. parseEndTag(stackedTag, index - endTagLength, index);
  98. }
  99. if (html === last) {
  100. options.chars && options.chars(html);
  101. if ("development" !== 'production' && !stack.length && options.warn) {
  102. options.warn(("Mal-formatted tag at end of template: \"" + html + "\""));
  103. }
  104. break
  105. }
  106. }
  107. // Clean up any remaining tags
  108. parseEndTag();
  109. function advance (n) {
  110. index += n;
  111. html = html.substring(n);
  112. }
  113. function parseStartTag () {
  114. //startTagOpen='/^<((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)/'
  115. var start = html.match(startTagOpen);
  116. if (start) {
  117. var match = {
  118. tagName: start[1],
  119. attrs: [],
  120. start: index
  121. };
  122. advance(start[0].length);
  123. var end, attr;
  124. //开始寻找属性
  125. //startTagClose='/^\s*(\/?)>/'
  126. //attribute='/^\s*([^\s"'<>\/=]+)(?:\s*((?:=))\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/'
  127. while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
  128. advance(attr[0].length);
  129. match.attrs.push(attr);
  130. }
  131. if (end) {
  132. match.unarySlash = end[1];//若'/'存在,则赋值给unarySlash
  133. advance(end[0].length);
  134. match.end = index;
  135. return match //至此,parseStartTag结束,接下来执行handleStartTag(match);match此时长这样
  136. /*
  137. attrs:Array(1)
  138. end:14
  139. start:0
  140. tagName:"div"
  141. unarySlash:""
  142. __proto__:Object
  143. */
  144. }
  145. }
  146. }
  147. function handleStartTag (match) {
  148. var tagName = match.tagName;
  149. var unarySlash = match.unarySlash;
  150. if (expectHTML) {
  151. if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
  152. parseEndTag(lastTag);
  153. }
  154. if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {
  155. parseEndTag(tagName);
  156. }
  157. }
  158. var unary = isUnaryTag$$1(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash;
  159. var l = match.attrs.length;
  160. //新建一个attrs属性,遍历match.attrs,使得attrs=[{name:'id',value:'app'}]这种map结构
  161. var attrs = new Array(l);
  162. for (var i = 0; i < l; i++) {
  163. var args = match.attrs[i];
  164. // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
  165. if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
  166. if (args[3] === '') { delete args[3]; }
  167. if (args[4] === '') { delete args[4]; }
  168. if (args[5] === '') { delete args[5]; }
  169. }
  170. var value = args[3] || args[4] || args[5] || '';
  171. attrs[i] = {
  172. name: args[1],
  173. value: decodeAttr(
  174. value,
  175. options.shouldDecodeNewlines
  176. )
  177. };
  178. }
  179. if (!unary) {
  180. stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs });
  181. lastTag = tagName;
  182. }
  183. //这个是最重要的函数,对一些特殊的属性做特殊处理,例如指令属性v-text='message'
  184. if (options.start) {
  185. options.start(tagName, attrs, unary, match.start, match.end);//tips:返回到parse函数中看start执行过程
  186. }
  187. }
  188. function parseEndTag (tagName, start, end) {
  189. var pos, lowerCasedTagName;
  190. if (start == null) { start = index; }
  191. if (end == null) { end = index; }
  192. if (tagName) {
  193. lowerCasedTagName = tagName.toLowerCase();
  194. }
  195. // Find the closest opened tag of the same type
  196. if (tagName) {
  197. for (pos = stack.length - 1; pos >= 0; pos--) {
  198. if (stack[pos].lowerCasedTag === lowerCasedTagName) {
  199. break
  200. }
  201. }
  202. } else {
  203. // If no tag name is provided, clean shop
  204. pos = 0;
  205. }
  206. if (pos >= 0) {
  207. // Close all the open elements, up the stack
  208. for (var i = stack.length - 1; i >= pos; i--) {
  209. if ("development" !== 'production' &&
  210. (i > pos || !tagName) &&
  211. options.warn) {
  212. options.warn(
  213. ("tag <" + (stack[i].tag) + "> has no matching end tag.")
  214. );
  215. }
  216. if (options.end) {
  217. options.end(stack[i].tag, start, end);
  218. }
  219. }
  220. // Remove the open elements from the stack
  221. stack.length = pos;
  222. lastTag = pos && stack[pos - 1].tag;
  223. } else if (lowerCasedTagName === 'br') {
  224. if (options.start) {
  225. options.start(tagName, [], true, start, end);
  226. }
  227. } else if (lowerCasedTagName === 'p') {
  228. if (options.start) {
  229. options.start(tagName, [], false, start, end);
  230. }
  231. if (options.end) {
  232. options.end(tagName, start, end);
  233. }
  234. }
  235. }
  236. }

以上这个过程只是一个解析过程,将相应的属性放到相应的位置,但是还没有产生可执行代码,以下
generate函数的作用就是根据这些属性来产生相应的代码。

3 分析 generate(ast, options)

  1. // 温故下,返回结果ast是含有如下属性的对象
  2. // ```javascript
  3. // attrs:Array //保存原始的html特性
  4. // attrsList:Array
  5. // attrsMap:Object
  6. // children:Array
  7. // parent:undefined
  8. // plain:false
  9. // static:false
  10. // staticRoot:false
  11. // tag:"div"
  12. // type:1
  13. // __proto__:Object
  14. function generate (ast,options) {
  15. // save previous staticRenderFns so generate calls can be nested
  16. var prevStaticRenderFns = staticRenderFns;
  17. var currentStaticRenderFns = staticRenderFns = [];
  18. var prevOnceCount = onceCount;
  19. onceCount = 0;
  20. currentOptions = options;
  21. warn$3 = options.warn || baseWarn;
  22. transforms$1 = pluckModuleFunction(options.modules, 'transformCode');
  23. dataGenFns = pluckModuleFunction(options.modules, 'genData');
  24. platformDirectives$1 = options.directives || {};
  25. isPlatformReservedTag$1 = options.isReservedTag || no;
  26. var code = ast ? genElement(ast) : '_c("div")'; //主要函数,执行genElement(ast)
  27. staticRenderFns = prevStaticRenderFns;
  28. onceCount = prevOnceCount;
  29. return {
  30. render: ("with(this){return " + code + "}"),
  31. staticRenderFns: currentStaticRenderFns
  32. }
  33. }

来看看 genElement(ast)

  1. //根据ast的属性是否有once,for,if,slot,component等属性执行不同函数,否则当普通元素处理并执行genData和genChildren函数,这两个函数都是在做字符串的拼装工作,最后返回拼装完成的code字符串
  2. function genElement (el) {
  3. if (el.staticRoot && !el.staticProcessed) { //静态节点
  4. return genStatic(el)
  5. } else if (el.once && !el.onceProcessed) { //v-once节点
  6. return genOnce(el)
  7. } else if (el.for && !el.forProcessed) { //v-for节点
  8. return genFor(el)
  9. } else if (el.if && !el.ifProcessed) { //v-if节点
  10. return genIf(el)
  11. } else if (el.tag === 'template' && !el.slotTarget) {
  12. return genChildren(el) || 'void 0'
  13. } else if (el.tag === 'slot') { //处理slot
  14. return genSlot(el)
  15. } else {
  16. // component or element
  17. var code;
  18. if (el.component) { //处理组件节点
  19. code = genComponent(el.component, el);
  20. } else {
  21. var data = el.plain ? undefined : genData(el); //处理元素节点的data
  22. var children = el.inlineTemplate ? null : genChildren(el, true);//处理元素节点的子元素
  23. code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
  24. }
  25. // module transforms
  26. for (var i = 0; i < transforms$1.length; i++) {
  27. code = transforms$1[i](el, code);
  28. }
  29. return code
  30. }
  31. }

来看看genData(el)

  1. //整个函数都是在做data字符串的拼装工作,最后返回data
  2. function genData (el) {
  3. var data = '{';
  4. // 首先处理指令,因为在el产生之前,指令可能会改变el的其他属性
  5. var dirs = genDirectives(el);
  6. if (dirs) { data += dirs + ','; }
  7. // 处理key属性
  8. if (el.key) {
  9. data += "key:" + (el.key) + ",";
  10. }
  11. // 处理ref属性
  12. if (el.ref) {
  13. data += "ref:" + (el.ref) + ",";
  14. }
  15. if (el.refInFor) {
  16. data += "refInFor:true,";
  17. }
  18. // 处理v-pre指令
  19. if (el.pre) {
  20. data += "pre:true,";
  21. }
  22. // record original tag name for components using "is" attribute
  23. if (el.component) { //处理组件
  24. data += "tag:\"" + (el.tag) + "\",";
  25. }
  26. // 处理class和style属性
  27. for (var i = 0; i < dataGenFns.length; i++) {
  28. data += dataGenFns[i](el);
  29. }
  30. //处理特性 attributes
  31. if (el.attrs) {
  32. data += "attrs:{" + (genProps(el.attrs)) + "},";
  33. }
  34. //处理属性 DOM property
  35. if (el.props) {
  36. data += "domProps:{" + (genProps(el.props)) + "},";
  37. }
  38. //处理事件
  39. if (el.events) {
  40. data += (genHandlers(el.events)) + ",";
  41. }
  42. //处理本地事件
  43. if (el.nativeEvents) {
  44. data += (genHandlers(el.nativeEvents, true)) + ",";
  45. }
  46. 处理slot目标
  47. if (el.slotTarget) {
  48. data += "slot:" + (el.slotTarget) + ",";
  49. }
  50. //处理scoped的slot
  51. if (el.scopedSlots) {
  52. data += (genScopedSlots(el.scopedSlots)) + ",";
  53. }
  54. //处理v-model
  55. if (el.model) {
  56. data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
  57. }
  58. // 处理内联模板
  59. if (el.inlineTemplate) {
  60. var inlineTemplate = genInlineTemplate(el);
  61. if (inlineTemplate) {
  62. data += inlineTemplate + ",";
  63. }
  64. }
  65. data = data.replace(/,$/, '') + '}';
  66. // v-bind data wrap 处理v-bind
  67. if (el.wrapData) {
  68. data = el.wrapData(data);
  69. }
  70. return data
  71. }

来看看genDirectives(el)

  1. function genDirectives (el) {
  2. var dirs = el.directives;
  3. if (!dirs) { return }
  4. var res = 'directives:[';
  5. var hasRuntime = false;
  6. var i, l, dir, needRuntime;
  7. for (i = 0, l = dirs.length; i < l; i++) {
  8. dir = dirs[i];
  9. needRuntime = true;
  10. var gen = platformDirectives$1[dir.name] || baseDirectives[dir.name];
  11. if (gen) {
  12. // compile-time directive that manipulates AST.
  13. // returns true if it also needs a runtime counterpart.
  14. needRuntime = !!gen(el, dir, warn$3);
  15. }
  16. if (needRuntime) {
  17. hasRuntime = true;
  18. res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:\"" + (dir.arg) + "\"") : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
  19. }
  20. }
  21. if (hasRuntime) {
  22. return res.slice(0, -1) + ']'
  23. }
  24. }

来看看genChildren

  1. function genChildren (el, checkSkip) {
  2. var children = el.children;
  3. if (children.length) {
  4. var el$1 = children[0];
  5. // optimize single v-for
  6. if (children.length === 1 &&
  7. el$1.for &&
  8. el$1.tag !== 'template' &&
  9. el$1.tag !== 'slot') {
  10. return genElement(el$1) //只有一个子元素并且有v-for属性时,递归调用genElement
  11. }
  12. var normalizationType = checkSkip ? getNormalizationType(children) : 0;
  13. return ("[" + (children.map(genNode).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
  14. }
  15. }

来看看genNode

  1. function genNode (node) {
  2. if (node.type === 1) { //type=1,表示是元素节点,递归调用genElement
  3. return genElement(node)
  4. } else {
  5. return genText(node) //按照vue的用法,不是元素节点就只能是文本节点了
  6. }
  7. }
  8. /*
  9. function genText (text) {
  10. return ("_v(" + (text.type === 2
  11. ? text.expression // no need for () because already wrapped in _s()
  12. : transformSpecialNewlines(JSON.stringify(text.text))) + ")")
  13. }
  14. */

4 小结

  • compile过程即baseCompile过程:

    1. 调用parse函数对原始模板进行解析得到ast;
    2. 调用generate函数处理ast得到最终render函数
  • 本篇偏向对编译的整体过程分析,没有对诸如指令到底是怎么编译的进行分析,后面章节将结合实例具体分析指令等编译过程,让我们先瞅瞅vue一共提供了哪些内置指令:
  1. v-text
  2. v-html
  3. v-show
  4. v-if
  5. v-else
  6. v-else-if
  7. v-for
  8. v-on
  9. v-bind
  10. v-model
  11. v-pre
  12. v-cloak
  13. v-once

除此之外,还有三个特殊属性key,ref,slot以及内置组件component,transition,transition-group,keep-alive,slot,接下来的章节将对这些内容进行分析