• 9.2. 代码生成

    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))])])"