Skip to content

Latest commit

 

History

History
217 lines (156 loc) · 5.71 KB

vuejs源码-模版编译原理.md

File metadata and controls

217 lines (156 loc) · 5.71 KB

Vue模版的编译原理是什么?

Vue模版编译,也就是compilte阶段,它其实就是将template转化为render function 的过程,它会经过以下三个阶段:

  1. parse阶段将template字符串通过各种正则表达式生成一颗抽象语法树AST,在解析的过程中是通过while不断循环这个字符串,每解析完一个标签指针向下移动;并且用栈来建立节点间的层级关系,也就是用来保存解析好的标签头。

  2. optimize阶段将生成的抽象语法树AST进行静态标记,这些被标记为静态的节点在后面的patch过程中会被跳过对比,从而达到优化的目的。标记的主要过程是为每个节点设置类似于static这样的属性,或者给根节点设置一个staticRoot属性表明这是不是一个静态根。

  3. 在进入到generate阶段之前,说明已经生成了被静态标记过的AST,而generate就是将AST转化为render function字符串。

Vue模版的编译阶段是怎样实现optimize的?

optimize主要是为了给parse阶段生成的AST进行静态标记。

这些被标记为静态的节点在后面的patch过程中会被跳过对比,从而达到优化的目的。

AST抽象语法树,实际就是一个JavaScript对象,而进行静态标记,就是给其中的每个节点定义一个类似于叫static的属性,或者给根节点设置一个staticRoot属性表明这是不是一个静态根。

例如下面👇的这颗AST,在刚开始是没有static属性的,经过optimize阶段就有了:

{
	'type': 1,
	'attrsMap': {
		'class': 'wrap'
	},
	'staticClass': 'wrap'
	'tag': 'div',
+	'static': false,
+ 'staticRoot': false,
	'children': [
		{
			'type': 3,
			'attrsMap': {
				'class': 'header'
			},
			'staticClass': 'header',
			'tag': 'div',
+			'static': true
		},
		{
			'type': 2,
			'attrsMap': {
				'class': 'footer',
				'v-if': 'isShow'
			},
			'ifConditions': [
				'exp': 'isShow'
			],
			'staticClass': 'footer',
			'tag': 'div',
+			'static': false
		}
	]
}

实现一个optimize函数,首先我们需要一个用来判断是否为「静态」的函数。

isStatic函数

判断一个节点是否为「静态」,判断的标准是:

  • 节点的type2(表达式节点),为非静态节点,返回false
  • 节点的type3(文本节点),为静态节点,返回true
  • 若是节点中存在if或者for这样的条件时,也为非静态节点,返回false
// 判断是否是静态节点
function isStatic (node) {
  const { type } = node
  if (type === 2) { // 表达式节点
    return false
  } else if (type === 3) { // 文本节点
    return true
  }
  return (!node.if && !node.for) // 不存在if和for
}

markStatic函数

有了判断是否为「静态」的函数之后,就需要遍历所有节点,然后打上标记了。

标记的依据是:

  • 首先通过isStatic判断当前节点是否为静态的节点,设置好static属性
  • 然后会遍历所有的子节点,设置好static属性,如果其中有子节点为非静态的,则当前节点也是非静态的。

来看看伪代码:

function markStatic (node) {
  node.static = isStatic(node) // 标记当前节点的static
  if (node.type === 1) {
    for (let i = 0; i < node.children.length; i++) {
      const child = node.children[i]
      markStatic(child) // 标记子节点
      if (!child.static) {
        node.static = false
      }
    }
  }
}

父节点为非静态节点时,它下面的所有子节点就一定为非静态吗?

不一定,例如下面这种情况,就不是:

<div class="wrap">
	<div class="header">我是头部静态节点</div>
	<div class="footer" v-if="isShow">我是尾部非静态节点</div>
</div>

markStaticRoots

除了使用markStatic函数来给每个节点标记static属性之外,还可以用一个名为markStaticRoots的函数来标记是否为静态根(staticRoot),将staticRoot属性设置为true

试想一下,下面这个结构:

<div class="wrap">
	<div class="header">我是头部静态节点</div>
	<div class="footer">我是尾部静态节点</div>
</div>

这段template下的节点全为静态节点,我们就可以将其标记为静态根。

判断的条件是:

  • 传入的节点本身的static就是为true
  • 并且该节点有子节点且不止有一个文本节点
function markStaticRoots () {
  if (node.type === 1) {
    if (node.static && node.children && !(
      node.children.length === 1 && node.children[0].type === 3
    )) {
      node.staticRoot = true
      return
    } else {
      node.staticRoot = false
    }
  }
}

optimize函数

现在可以来实现optimize函数了:

function optimize (rootAst) {
  markStatic(rootAst)
  markStaticRoots(rootAst)
}

nextTick的实现

Vue.js 实现了一个 nextTick 函数,传入一个 cb ,这个 cb 会被存储到一个队列中,在下一个 tick 时触发队列中的所有 cb 事件。

let callbacks = [];
let pending = false;

function nextTick (cb) {
    callbacks.push(cb);

    if (!pending) {
        pending = true;
        setTimeout(flushCallbacks, 0);
    }
}

function flushCallbacks () {
    pending = false;
    const copies = callbacks.slice(0);
    callbacks.length = 0;
    for (let i = 0; i < copies.length; i++) {
        copies[i]();
    }
}

这样做的目的是:

例如在一个时间点内,一直调用nextTick

nextTick(cb)
nextTick(cb)
nextTick(cb)

由于setTimeout的原因,pending变为了true之后就不会执行if里的代码了,而是等定时器执行了之后才变回来