Vue源码详解之compile函数指令的提取

# Vue源码详解之compile函数指令的提取

# compile

compile阶段执行的compileRoot函数就是编译我们在transclude阶段说过的,我们分别提取到了el顶级元素的属性和模板的顶级元素的属性,如果是component,那就需要把两者分开编译生成两个link。

我们来说说compile函数,他对元素执行compileNode,对其childNodes执行compileNodeList:

export function compile (el, options, partial) {
  // link function for the node itself.
  var nodeLinkFn = partial || !options._asComponent
    ? compileNode(el, options)
    : null
	// link function for the childNodes
	// 如果nodeLinkFn.terminal为true,说明nodeLinkFn接管了整个元素和其子元素的编译过程,那也就不用编译el.childNodes
  var childLinkFn =
    !(nodeLinkFn && nodeLinkFn.terminal) &&
    !isScript(el) &&
    el.hasChildNodes()
      ? compileNodeList(el.childNodes, options)
      : null

  return function compositeLinkFn (vm, el, host, scope, frag) {
    // cache childNodes before linking parent, fix #657
    var childNodes = toArray(el.childNodes)
    // link
    // 任何link都是包裹在linkAndCapture中执行的,详见linkAndCapture函数
    var dirs = linkAndCapture(function compositeLinkCapturer () {
      if (nodeLinkFn) nodeLinkFn(vm, el, host, scope, frag)
      if (childLinkFn) childLinkFn(vm, childNodes, host, scope, frag)
    }, vm)
    return makeUnlinkFn(vm, dirs)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function compileNode (node, options) {
  var type = node.nodeType
  if (type === 1 && !isScript(node)) {
    return compileElement(node, options)
  } else if (type === 3 && node.data.trim()) {
    return compileTextNode(node, options)
  } else {
    return null
  }
}
1
2
3
4
5
6
7
8
9
10

# compileElement

function compileElement (el, options) {
  
  if (el.tagName === 'TEXTAREA') {
	// textarea元素是把tag中间的内容当做了他的value,这和input什么的不太一样
	// 因此大家写模板的时候通常是这样写: <textarea>{{hello}}</textarea>
	// 但是template转换成dom之后,这个内容跑到了textarea元素的value属性上,tag中间的内容是空的,
	// 因此遇到textarea的时候需要单独编译一下它的value
    var tokens = parseText(el.value)
    if (tokens) {
      el.setAttribute(':value', tokensToExp(tokens))
      el.value = ''
    }
  }
  var linkFn
  var hasAttrs = el.hasAttributes()
  var attrs = hasAttrs && toArray(el.attributes)
  // check terminal directives (for & if)
  if (hasAttrs) {
    linkFn = checkTerminalDirectives(el, attrs, options)
  }
  // check element directives
  if (!linkFn) {
    linkFn = checkElementDirectives(el, options)
  }
  // check component
  if (!linkFn) {
    linkFn = checkComponent(el, options)
  }
  // normal directives
  if (!linkFn && hasAttrs) {
	// 一般会进入到这里
    linkFn = compileDirectives(attrs, options)
  }
  return linkFn
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

最终会生成这么一个指令描述对象,以v-bind:href.literal="mylink"为例:

{
	arg:"href",
	attr:"v-bind:href.literal",
	def:Object,// v-bind指令的定义
	expression:"mylink", // 表达式,如果是插值的话,那主要用到的是下面的interp字段
	filters:undefined
	hasOneTime:undefined
	interp:undefined,// 存放插值token
	modifiers:Object, // literal修饰符的定义
	name:"bind" //指令类型
	raw:"mylink"  //未处理前的原始属性值
}
1
2
3
4
5
6
7
8
9
10
11
12
上次更新: 2022/7/6 上午11:51:19