vue源码学习(六)模版编译之代码生成器

vue源码版本为2.6.11(cdn地址为: https://lib.baomitu.com/vue/2.6.11/vue.js

 代码生成器,将 AST 对象生成渲染函数中的内容,这个内容可以被称为代码字符串。

<div id="app">
  <h1>{{message}}</h1>
</div>

该模板转换成AST后的结构如下: 

{
	tag: "div",
	type: 1,
	plain: false,
	start: 0,
	end: 44,
	parent: undefined,
	attrs: [{
		dynamic: undefined,
		end: 13,
		name: "id",
		start: 5,
		value: "app",
	}],
	attrsList: [{
		end: 13,
		name: "id",
		start: 5,
		value: "app"
	}],
	attrsMap: {
		id: "app"
	},
	rawAttrsMap: {
		id: {
			name: "id",
			value: "app",
			start: 5,
			end: 13
		}
	},
	children: [{
		tag: "h1",
		type: 1,
		type: 1,
		plain: true,
		start: 17,
		end: 37,
		parent: {...},
		attrsList: [],
		attrsMap: {},
		rawAttrsMap: {},
		children: [{
			end: 32,
			expression: "_s(message)",
			start: 21,
			text: "{{message}}",
            type: 2,
			tokens: [{
				"@binding": "message"
			}]
		}]
	}]
}

 根据该AST来生成render后是这样的:

{
    render: 'with(this){return _c("div",{attrs:{"id":"app"}},[_c("h1",[_v(_s(message))])])}'
}

生成后的代码字符串中有几个函数调用_c,  _v,  _s

其中_s是返回参数中的字符串:

var _toString = Object.prototype.toString;
function toString(val) {
  return val == null
    ? ""
    : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
    ? JSON.stringify(val, null, 2)
    : String(val);
}
三种方法对应的创建方法与别名
类型创建方法别名
元素节点createElement_c
文本节点createTextVNode_v
注释节点createEmptyVNode_e

生成代码字符串代码入口在:

var createCompiler = createCompilerCreator(function baseCompile(
  template,
  options
) {
  // ...

  // 将ast生成渲染函数
  // code.staticRenderFns为静态渲染函数,code.render为动态渲染函数
//   code = {
//     render: `with(this){return ${_c(tag, data, children, normalizationType)}}`,
//     staticRenderFns: [_c(tag, data, children, normalizationType), ...]
//   }
  var code = generate(ast, options);
  return {
    ast: ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns,
  };
});

generate

var CodegenState = function CodegenState(options) {
  this.options = options;
  this.warn = options.warn || baseWarn;
  this.transforms = pluckModuleFunction(options.modules, "transformCode");
  this.dataGenFns = pluckModuleFunction(options.modules, "genData");
  this.directives = extend(extend({}, baseDirectives), options.directives);
  var isReservedTag = options.isReservedTag || no;
  this.maybeComponent = function (el) {
    return !!el.component || !isReservedTag(el.tag);
  };
  this.onceId = 0;
  this.staticRenderFns = [];
  this.pre = false;
};

// 将AST生成渲染函数
function generate(ast, options) {
  var state = new CodegenState(options);
  var code = ast ? genElement(ast, state) : '_c("div")';
  return {
    // 最外层包一个 with(this) 之后返回
    render: "with(this){return " + code + "}",
    staticRenderFns: state.staticRenderFns,
  };
}

其中 genElement 函数是会根据 AST 的属性调用不同的方法生成字符串返回。也就是拼接字符串了:

genElement

function genElement(el, state) {
  if (el.parent) {
    el.pre = el.pre || el.parent.pre;
  }

  if (el.staticRoot && !el.staticProcessed) {
    /**
     * 处理静态根节点,生成节点的渲染函数
     *   1、将当前静态节点的渲染函数放到 staticRenderFns 数组中
     *   2、返回一个可执行函数 _m(index, true or '')
     */
    return genStatic(el, state);
  } else if (el.once && !el.onceProcessed) {
    /**
     * 处理带有 v-once 指令的节点,结果会有三种:
     *   1、当前节点存在 v-if 指令,得到一个三元表达式,condition ? render1 : render2
     *   2、当前节点是一个包含在 v-for 指令内部的静态节点,得到 `_o(_c(tag, data, children), number, key)`
     *   3、当前节点就是一个单纯的 v-once 节点,得到 `_m(index, true of '')`
     */
    return genOnce(el, state);
  } else if (el.for && !el.forProcessed) {
    /**
     * 处理节点上的 v-for 指令
     * 得到 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})`
     */
    return genFor(el, state);
  } else if (el.if && !el.ifProcessed) {
    /**
     * 处理带有 v-if 指令的节点,最终得到一个三元表达式:condition ? render1 : render2
     */
    return genIf(el, state);
  } else if (el.tag === "template" && !el.slotTarget && !state.pre) {
    /**
     * 当前节点不是 template 标签也不是插槽和带有 v-pre 指令的节点时走这里
     * 生成所有子节点的渲染函数,返回一个数组,格式如:
     * [_c(tag, data, children, normalizationType), ...]
     */
    return genChildren(el, state) || "void 0";
  } else if (el.tag === "slot") {
    /**
     * 生成插槽的渲染函数,得到
     * _t(slotName, children, attrs, bind)
     */
    return genSlot(el, state);
  } else {
    // 处理动态组件和普通元素(自定义组件、原生标签)
    var code;
    if (el.component) {
      /**
       * 处理动态组件,生成动态组件的渲染函数
       * 得到 `_c(compName, data, children)`
       */
      code = genComponent(el.component, el, state);
    } else {
      var data;
      if (!el.plain || (el.pre && state.maybeComponent(el))) {
        // 非普通元素或者带有 v-pre 指令的组件走这里,处理节点的所有属性,返回一个 JSON 字符串,
        // 比如 '{ key: xx, ref: xx, ... }'
        data = genData$2(el, state);
      }
      // 处理子节点,得到所有子节点字符串格式的代码组成的数组,格式:
      // `['_c(tag, data, children)', ...],normalizationType`,
      var children = el.inlineTemplate ? null : genChildren(el, state, true);
      code =
        "_c('" +
        el.tag +
        "'" +
        (data ? "," + data : "") +
        (children ? "," + children : "") +
        ")";
    }
    // 如果提供了 transformCode 方法,
    // 则最终的 code 会经过各个模块(module)的该方法处理,
    // 不过框架没提供这个方法,不过即使处理了,最终的格式也是 _c(tag, data, children)
    for (var i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code);
    }
    return code;
  }
}

先来说说staticRenderFns,这个数组中的函数与VDOM中的diff算法优化相关,我们会在模板编译优化过程中给每个节点加上staticRoot属性,其中标记为true的VNode就会生成静态节点渲染函数并放到到staticRenderFns数组中, 对应的方法是genStatic

genStatic

if (el.staticRoot && !el.staticProcessed) {
  return genStatic(el, state);
}


/**
 * 生成静态节点的渲染函数
 *   1、将当前静态节点的渲染函数放到 staticRenderFns 数组中
 *   2、返回一个可执行函数 _m(idx, true or '') 字符串
 */
function genStatic(el, state) {
  // 标记当前静态节点已经被处理过了
  el.staticProcessed = true;
  var originalPreState = state.pre;
  if (el.pre) {
    state.pre = el.pre;
  }
  state.staticRenderFns.push(
    "with(this){return " + genElement(el, state) + "}"
  );
  state.pre = originalPreState;
  return (
    "_m(" +
    (state.staticRenderFns.length - 1) +
    (el.staticInFor ? ",true" : "") +
    ")"
  );
}

 v-once这个指令不需要任何表达式,它的作用就是定义它的元素或组件只会渲染一次,包括元素或者组件的所有字节点。首次渲染后,不再随着数据的改变而重新渲染。也就是说使用v-once,那么该块都将被视为静态内容。

genOnce

/**
 * 处理带有 v-once 指令的节点,结果会有三种:
 *   1、当前节点存在 v-if 指令,得到一个三元表达式,condition ? render1 : render2
 *   2、当前节点是一个包含在 v-for 指令内部的静态节点,得到 `_o(_c(tag, data, children), number, key)`
 *   3、当前节点就是一个单纯的 v-once 节点,得到 `_m(idx, true of '')`
 */
function genOnce(el, state) {
  // 标记当前节点的 v-once 指令已经被处理过了
  el.onceProcessed = true;
  if (el.if && !el.ifProcessed) {
    // 处理v-if 指令的节点
    return genIf(el, state);
  } else if (el.staticInFor) {
    // 有 v-for 指令节点内部的静态节点
    // 获取 v-for 指令的 key
    var key = "";
    var parent = el.parent;
    while (parent) {
      if (parent.for) {
        key = parent.key;
        break;
      }
      parent = parent.parent;
    }
    // key 不存在则给出提示,v-once 节点只能用于带有 key 的 v-for 节点内部
    if (!key) {
      state.warn(
        "v-once can only be used inside v-for that is keyed. ",
        el.rawAttrsMap["v-once"]
      );
      return genElement(el, state);
    }
    // 生成 `_o(_c(tag, data, children), number, key)`
    return (
      "_o(" + genElement(el, state) + "," + state.onceId++ + "," + key + ")"
    );
  } else {
    // 上面几种情况都不符合,说明就是一个简单的静态节点,和处理静态根节点时的操作一样
    return genStatic(el, state);
  }
}

genFor 

// 处理有v-for指令的节点
function genFor(el, state, altGen, altHelper) {
  // 遍历的对象
  var exp = el.for;
  // 迭代时的别名
  var alias = el.alias;
  // iterator 为 v-for = "(item ,index ) in arr" 时会有,比如 iterator1 = index
  var iterator1 = el.iterator1 ? "," + el.iterator1 : "";
  var iterator2 = el.iterator2 ? "," + el.iterator2 : "";

  // 提示,v-for 指令在组件上时必须使用 key
  if (
    state.maybeComponent(el) &&
    el.tag !== "slot" &&
    el.tag !== "template" &&
    !el.key
  ) {
    state.warn(
      "<" +
        el.tag +
        ' v-for="' +
        alias +
        " in " +
        exp +
        '">: component lists rendered with ' +
        "v-for should have explicit keys. " +
        "See https://vuejs.org/guide/list.html#key for more info.",
      el.rawAttrsMap["v-for"],
      true /* tip */
    );
  }
  // 标记当前节点上的 v-for 指令已经被处理过了
  el.forProcessed = true; // avoid recursion
  // 得到 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})`
  return (altHelper || '_l') + "((" + exp + ")," +
      "function(" + alias + iterator1 + iterator2 + "){" +
        "return " + ((altGen || genElement)(el, state)) +
      '})'
}

genIf 

// 处理带有 v-if 指令的节点
function genIf(el, state, altGen, altEmpty) {
  // 标记当前节点的 v-if 指令已经被处理过了
  el.ifProcessed = true; // avoid recursion
  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty);
}

function genIfConditions(conditions, state, altGen, altEmpty) {
  if (!conditions.length) {
    // 长度若为空,则直接返回一个空节点渲染函数
    return altEmpty || "_e()";
  }

  var condition = conditions.shift();
  if (condition.exp) {
    // 如果 condition.exp 条件成立,则得到一个三元表达式,
    // 如果条件不成立,则通过递归的方式找 conditions 数组中下一个元素,
    // 直到找到条件成立的元素,然后返回一个三元表达式
    return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
  } else {
    return "" + genTernaryExp(condition.block);
  }

  // v-if with v-once should generate code like (a)?_m(0):_m(1)
  function genTernaryExp(el) {
    return altGen
      ? altGen(el, state)
      : el.once
      ? genOnce(el, state)
      : genElement(el, state);
  }
}

当前节点不是 template 标签也不是插槽和带有 v-pre 指令的节点时调用genChildren方法 生成所有子节点的渲染函数

genChildren

// 生成所有子节点的渲染函数
function genChildren(el, state, checkSkip, altGenElement, altGenNode) {
  var children = el.children;
  if (children.length) {
    var el$1 = children[0];
    // optimize single v-for
    // 只有一个子节点且子节点的上有 v-for 指令且子节点的标签不为 template 或者 slot
    // 直接调用 genElement 生成该节点的渲染函数
    if (
      children.length === 1 &&
      el$1.for &&
      el$1.tag !== "template" &&
      el$1.tag !== "slot"
    ) {
        var normalizationType = checkSkip
        ? state.maybeComponent(el$1) ? ",1" : ",0"
        : "";
      return ("" + ((altGenElement || genElement)(el$1, state)) + normalizationType)
    }
    var normalizationType$1 = checkSkip
      ? getNormalizationType(children, state.maybeComponent)
      : 0;
    var gen = altGenNode || genNode;
    // 返回一个数组,数组的每个元素都是一个子节点的渲染函数
    return ("[" + (children.map(function (c) { return gen(c, state); }).join(',')) + "]" + (normalizationType$1 ? ("," + normalizationType$1) : ''))
  }
}

genNode、genText、genComment 

function genNode(node, state) {
  if (node.type === 1) {
    return genElement(node, state);
  } else if (node.type === 3 && node.isComment) {
    return genComment(node);
  } else {
    return genText(node);
  }
}
function genText(text) {
  return ("_v(" + (text.type === 2
      ? text.expression // no need for () because already wrapped in _s()
      : transformSpecialNewlines(JSON.stringify(text.text))) + ")")
}

function genComment(comment) {
  return "_e(" + JSON.stringify(comment.text) + ")";
}

genSlot 

// 处理插槽
function genSlot(el, state) {
    // 插槽名称
  var slotName = el.slotName || '"default"';
  // 生成所有的子节点
  var children = genChildren(el, state);
  var res = "_t(" + slotName + (children ? "," + children : "");
  var attrs = el.attrs || el.dynamicAttrs
      ? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(function (attr) { return ({
          // slot props are camelized
          name: camelize(attr.name),
          value: attr.value,
          dynamic: attr.dynamic
        }); }))
      : null;
  var bind$$1 = el.attrsMap["v-bind"];
  if ((attrs || bind$$1) && !children) {
    res += ",null";
  }
  if (attrs) {
    res += "," + attrs;
  }
  if (bind$$1) {
    res += (attrs ? "" : ",null") + "," + bind$$1;
  }
  return res + ")";
}

genComponent 

// 生成动态组件的渲染函数
function genComponent(componentName, el, state) {
  var children = el.inlineTemplate ? null : genChildren(el, state, true);
  return (
    "_c(" +
    componentName +
    "," +
    genData$2(el, state) +
    (children ? "," + children : "") +
    ")"
  );
}

genData$2 

// 处理节点上的属性
function genData$2(el, state) {
  var data = "{";

  // 首先先处理指令,因为指令可能在生成其它属性之前改变这些属性
  var dirs = genDirectives(el, state);
  if (dirs) {
    data += dirs + ",";
  }

  // key
  if (el.key) {
    data += "key:" + el.key + ",";
  }
  // ref
  if (el.ref) {
    data += "ref:" + el.ref + ",";
  }
  if (el.refInFor) {
    data += "refInFor:true,";
  }
  // pre
  if (el.pre) {
    data += "pre:true,";
  }
  // 动态组件
  if (el.component) {
    data += 'tag:"' + el.tag + '",';
  }
  // 为节点执行模块(class、style)的 genData 方法,
  // 得到 data = { staticClass: xx, class: xx, staticStyle: xx, style: xx }
  for (var i = 0; i < state.dataGenFns.length; i++) {
    data += state.dataGenFns[i](el);
  }
  // attributes
  if (el.attrs) {
    data += "attrs:" + genProps(el.attrs) + ",";
  }
  // DOM props
  if (el.props) {
    data += "domProps:" + genProps(el.props) + ",";
  }
  // 自定义事件
  if (el.events) {
    data += genHandlers(el.events, false) + ",";
  }
  // 带 native 修饰符的事件
  if (el.nativeEvents) {
    data += genHandlers(el.nativeEvents, true) + ",";
  }
  // 非作用域插槽
  if (el.slotTarget && !el.slotScope) {
    data += "slot:" + el.slotTarget + ",";
  }
  // 作用域插槽
  if (el.scopedSlots) {
    data += genScopedSlots(el, el.scopedSlots, state) + ",";
  }
  // component v-model
  if (el.model) {
    data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
  }
  // 内联模版
  if (el.inlineTemplate) {
    var inlineTemplate = genInlineTemplate(el, state);
    if (inlineTemplate) {
      data += inlineTemplate + ",";
    }
  }
  data = data.replace(/,$/, "") + "}";
  // v-bind dynamic argument wrap
  // v-bind with dynamic arguments must be applied using the same v-bind object
  // merge helper so that class/style/mustUseProp attrs are handled correctly.
  // 动态属性
  if (el.dynamicAttrs) {
    data =
      "_b(" + data + ',"' + el.tag + '",' + genProps(el.dynamicAttrs) + ")";
  }
  // v-bind data wrap
  if (el.wrapData) {
    data = el.wrapData(data);
  }
  // v-on data wrap
  if (el.wrapListeners) {
    data = el.wrapListeners(data);
  }
  return data;
}

genDirectives

/**
 * 运行指令的编译方法,如果指令存在运行时任务,则返回 directives: [{ name, rawName, value, arg, modifiers }, ...}] 
 */
function genDirectives(el, state) {
  var dirs = el.directives;
  if (!dirs) {
    return;
  }
  // 指令的处理结果
  var res = "directives:[";
  // 标记,用于标记指令是否需要在运行时完成的任务,比如 v-model 的 input 事件
  var hasRuntime = false;
  var i, l, dir, needRuntime;
  for (i = 0, l = dirs.length; i < l; i++) {
    dir = dirs[i];
    needRuntime = true;
    var gen = state.directives[dir.name];
    if (gen) {
      // 执行指令的编译方法,如果指令还需要运行时完成一部分任务,则返回 true,比如 v-model
      needRuntime = !!gen(el, dir, state.warn);
    }
    if (needRuntime) {
      // 表示该指令在运行时还有任务
      hasRuntime = true;
      res +=
        '{name:"' +
        dir.name +
        '",rawName:"' +
        dir.rawName +
        '"' +
        (dir.value
          ? ",value:(" + dir.value + "),expression:" + JSON.stringify(dir.value)
          : "") +
        (dir.arg
          ? ",arg:" + (dir.isDynamicArg ? dir.arg : '"' + dir.arg + '"')
          : "") +
        (dir.modifiers ? ",modifiers:" + JSON.stringify(dir.modifiers) : "") +
        "},";
    }
  }
  if (hasRuntime) {
    // 有指令存在运行时任务时,才会返回 res
    return res.slice(0, -1) + "]";
  }
}

genProps 

// 遍历属性数组 props,得到所有属性组成的字符串
function genProps (props) {
    // 静态属性
    var staticProps = "";
    // 动态属性
    var dynamicProps = "";
    for (var i = 0; i < props.length; i++) {
      var prop = props[i];
      var value = transformSpecialNewlines(prop.value);
      if (prop.dynamic) {
        dynamicProps += (prop.name) + "," + value + ",";
      } else {
        staticProps += "\"" + (prop.name) + "\":" + value + ",";
      }
    }
    // 去掉静态属性最后的逗号
    staticProps = "{" + (staticProps.slice(0, -1)) + "}";
    if (dynamicProps) {
      return ("_d(" + staticProps + ",[" + (dynamicProps.slice(0, -1)) + "])")
    } else {
      return staticProps
    }
}

genHandlers 

// 生成自定义事件的代码
function genHandlers(events, isNative) {
  var prefix = isNative ? "nativeOn:" : "on:";
  // 静态
  var staticHandlers = "";
  // 动态
  var dynamicHandlers = "";
  for (var name in events) {
    // 获取指定事件的回调函数名
    var handlerCode = genHandler(events[name]);
    if (events[name] && events[name].dynamic) {
      dynamicHandlers += name + "," + handlerCode + ",";
    } else {
      staticHandlers += '"' + name + '":' + handlerCode + ",";
    }
  }
  // 去掉末尾的逗号
  staticHandlers = "{" + staticHandlers.slice(0, -1) + "}";
  if (dynamicHandlers) {
    return (
      prefix +
      "_d(" +
      staticHandlers +
      ",[" +
      dynamicHandlers.slice(0, -1) +
      "])"
    );
  } else {
    return prefix + staticHandlers;
  }
}

参考资料:

深入浅出Vue.js

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值