9.2. 代码生成

模板编译的最后一步是根据解析完的AST树生成对应平台的渲染函数,也就是render函数的生成过程, 对应var code = generate(ast, options);

  1. function generate (ast,options) {
  2. var state = new CodegenState(options);
  3. var code = ast ? genElement(ast, state) : '_c("div")';
  4. return {
  5. render: ("with(this){return " + code + "}"), // with函数
  6. staticRenderFns: state.staticRenderFns
  7. }
  8. }

其中核心处理在getElement中,getElement函数会根据不同指令类型处理不同的分支,对于普通模板的编译会进入genData函数中处理,同样分析只针对事件相关的处理,从前面解析出的AST树明显看出,AST树中多了events的属性,genHandlers函数会为event属性做逻辑处理。

  1. function genData (el, state) {
  2. var data = '{';
  3. // directives first.
  4. // directives may mutate the el's other properties before they are generated.
  5. var dirs = genDirectives(el, state);
  6. if (dirs) { data += dirs + ','; }
  7. //其他处理
  8. ···
  9. // event handlers
  10. if (el.events) {
  11. data += (genHandlers(el.events, false)) + ",";
  12. }
  13. ···
  14. return data
  15. }

genHandlers的逻辑,会遍历解析好的AST树,拿到event对象属性,并根据属性上的事件对象拼接成字符串。

  1. function genHandlers (events,isNative) {
  2. var prefix = isNative ? 'nativeOn:' : 'on:';
  3. var staticHandlers = "";
  4. var dynamicHandlers = "";
  5. // 遍历ast树解析好的event对象
  6. for (var name in events) {
  7. //genHandler本质上是将事件对象转换成可拼接的字符串
  8. var handlerCode = genHandler(events[name]);
  9. if (events[name] && events[name].dynamic) {
  10. dynamicHandlers += name + "," + handlerCode + ",";
  11. } else {
  12. staticHandlers += "\"" + name + "\":" + handlerCode + ",";
  13. }
  14. }
  15. staticHandlers = "{" + (staticHandlers.slice(0, -1)) + "}";
  16. if (dynamicHandlers) {
  17. return prefix + "_d(" + staticHandlers + ",[" + (dynamicHandlers.slice(0, -1)) + "])"
  18. } else {
  19. return prefix + staticHandlers
  20. }
  21. }
  22. // 事件模板书写匹配
  23. var isMethodPath = simplePathRE.test(handler.value); // doThis
  24. var isFunctionExpression = fnExpRE.test(handler.value); // () => {} or function() {}
  25. var isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, '')); // doThis($event)
  26. function genHandler (handler) {
  27. if (!handler) {
  28. return 'function(){}'
  29. }
  30. // 事件绑定可以多个,多个在解析ast树时会以数组的形式存在,如果有多个则会递归调用getHandler方法返回数组。
  31. if (Array.isArray(handler)) {
  32. return ("[" + (handler.map(function (handler) { return genHandler(handler); }).join(',')) + "]")
  33. }
  34. // value: doThis 可以有三种方式
  35. var isMethodPath = simplePathRE.test(handler.value); // doThis
  36. var isFunctionExpression = fnExpRE.test(handler.value); // () => {} or function() {}
  37. var isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, '')); // doThis($event)
  38. // 没有任何修饰符
  39. if (!handler.modifiers) {
  40. // 符合函数定义规范,则直接返回调用函数名 doThis
  41. if (isMethodPath || isFunctionExpression) {
  42. return handler.value
  43. }
  44. // 不符合则通过function函数封装返回
  45. return ("function($event){" + (isFunctionInvocation ? ("return " + (handler.value)) : handler.value) + "}") // inline statement
  46. } else {
  47. // 包含修饰符的场景
  48. }
  49. }

模板中事件的写法有三种,分别对应上诉上个正则匹配的内容。

  1. <div @click="doThis"></div>
  2. <div @click="doThis($event)"></div>
  3. <div @click="()=>{}"></div> <div @click="function(){}"></div>

上述对事件对象的转换,如果事件不带任何修饰符,并且满足正确的模板写法,则直接返回调用事件名,如果不满足,则有可能是<div @click="console.log(11)"></div>的写法,此时会封装到function($event){}中。

包含修饰符的场景较多,我们单独列出分析。以上文中的例子说明,modifiers: { stop: true }会拿到stop对应需要添加的逻辑脚本'$event.stopPropagation();',并将它添加到函数字符串中返回。

  1. function genHandler() {
  2. // ···
  3. } else {
  4. var code = '';
  5. var genModifierCode = '';
  6. var keys = [];
  7. // 遍历modifiers上记录的修饰符
  8. for (var key in handler.modifiers) {
  9. if (modifierCode[key]) {
  10. // 根据修饰符添加对应js的代码
  11. genModifierCode += modifierCode[key];
  12. // left/right
  13. if (keyCodes[key]) {
  14. keys.push(key);
  15. }
  16. // 针对exact的处理
  17. } else if (key === 'exact') {
  18. var modifiers = (handler.modifiers);
  19. genModifierCode += genGuard(
  20. ['ctrl', 'shift', 'alt', 'meta']
  21. .filter(function (keyModifier) { return !modifiers[keyModifier]; })
  22. .map(function (keyModifier) { return ("$event." + keyModifier + "Key"); })
  23. .join('||')
  24. );
  25. } else {
  26. keys.push(key);
  27. }
  28. }
  29. if (keys.length) {
  30. code += genKeyFilter(keys);
  31. }
  32. // Make sure modifiers like prevent and stop get executed after key filtering
  33. if (genModifierCode) {
  34. code += genModifierCode;
  35. }
  36. // 根据三种不同的书写模板返回不同的字符串
  37. var handlerCode = isMethodPath
  38. ? ("return " + (handler.value) + "($event)")
  39. : isFunctionExpression
  40. ? ("return (" + (handler.value) + ")($event)")
  41. : isFunctionInvocation
  42. ? ("return " + (handler.value))
  43. : handler.value;
  44. return ("function($event){" + code + handlerCode + "}")
  45. }
  46. }
  47. var modifierCode = {
  48. stop: '$event.stopPropagation();',
  49. prevent: '$event.preventDefault();',
  50. self: genGuard("$event.target !== $event.currentTarget"),
  51. ctrl: genGuard("!$event.ctrlKey"),
  52. shift: genGuard("!$event.shiftKey"),
  53. alt: genGuard("!$event.altKey"),
  54. meta: genGuard("!$event.metaKey"),
  55. left: genGuard("'button' in $event && $event.button !== 0"),
  56. middle: genGuard("'button' in $event && $event.button !== 1"),
  57. right: genGuard("'button' in $event && $event.button !== 2")
  58. };

经过这一转换后,生成with封装的render函数如下:

  1. "_c('div',{attrs:{"id":"app"}},[_c('div',{on:{"click":function($event){$event.stopPropagation();return doThis($event)}}},[_v("点击")]),_v(" "),_c('span',[_v(_s(count))])])"