Vue编译器optimize源码分析

目录
  • 引言
  • optimize 源码之旅
    • markStatic$1源码
    • isStatic源码
    • 复杂点的
    • 回归到markStatic$1
    • markStaticRoots 源码

引言

接上文 parseHTML 函数源码解析 chars、end、comment钩子函数

上一章节我们讲到通过解析将template转成AST(抽象语法树),接下来继续对模型树优化,进行静态标注。那么问题来了,什么是静态标注?为什么要静态标注。

在源码的注释中我们找到了下面这段话:

/**
 * Goal of the optimizer: walk the generated template AST tree
 * and detect sub-trees that are purely static, i.e. parts of
 * the DOM that never needs to change.
 *
 * Once we detect these sub-trees, we can:
 *
 * 1. Hoist them into constants, so that we no longer need to
 *    create fresh nodes for them on each re-render;
 * 2. Completely skip them in the patching process.
 */
  • 永远不需要变化的DOM就是静态的。
  • 重新渲染时,作为常量,无需创建新节点;

optimize 源码之旅

function optimize(root, options) {
	if (!root) {
		return
	}
	isStaticKey = genStaticKeysCached(options.staticKeys || '');
	isPlatformReservedTag = options.isReservedTag || no;
	// first pass: mark all non-static nodes.
	markStatic$1(root);
	// second pass: mark static roots.
	markStaticRoots(root, false);
}

可以看到源码并不复杂初始定义了两个变量。

  • isStaticKey 获取 genStaticKeysCached函数返回值, 获取 makeMap (点此查看) 函数返回值引用 。
  • isPlatformReservedTag 获取编译器选项 isReservedTag 的引用,检查给定的字符是否是保留的标签。

接下来就是两个重要的方法 markStatic$1 标注静态节点、markStaticRoots 标注静态根节点,我们先来看下 markStatic$1的源码。

markStatic$1源码

function markStatic$1(node) {
	node.static = isStatic(node);
	if (node.type === 1) {
		// do not make component slot content static. this avoids
		// 1. components not able to mutate slot nodes
		// 2. static slot content fails for hot-reloading
		if (
			!isPlatformReservedTag(node.tag) &&
			node.tag !== 'slot' &&
			node.attrsMap['inline-template'] == null
		) {
			return
		}
		for (var i = 0, l = node.children.length; i < l; i++) {
			var child = node.children[i];
			markStatic$1(child);
			if (!child.static) {
				node.static = false;
			}
		}
		if (node.ifConditions) {
			for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
				var block = node.ifConditions[i$1].block;
				markStatic$1(block);
				if (!block.static) {
					node.static = false;
				}
			}
		}
	}
}

第一步判断节点状态并标注。

node.static = isStatic(node);
在这给元素描述对象(AST) 扩展了static属性,通过isStatic方法调用后返回值,确认哪些节点是静态的,哪些是动态的。

isStatic源码

function isStatic(node) {
	if (node.type === 2) { // expression
		return false
	}
	if (node.type === 3) { // text
		return true
	}
	return !!(node.pre || (
		!node.hasBindings && // no dynamic bindings
		!node.if && !node.for && // not v-if or v-for or v-else
		!isBuiltInTag(node.tag) && // not a built-in
		isPlatformReservedTag(node.tag) && // not a component
		!isDirectChildOfTemplateFor(node) &&
		Object.keys(node).every(isStaticKey)
	))
}

在这判断node.type的值为2,表示为表达式返回false,node.type的值为3,表示为静态文本返回 true 总结:节点类型为表达式,标注为非静态;普通文本为静态。

上面的很容易理解

复杂点的

return !!(node.pre || (
		!node.hasBindings && // no dynamic bindings
		!node.if && !node.for && // not v-if or v-for or v-else
		!isBuiltInTag(node.tag) && // not a built-in
		isPlatformReservedTag(node.tag) && // not a component
		!isDirectChildOfTemplateFor(node) &&
		Object.keys(node).every(isStaticKey)
))

节点类型为表达式,标注为非静态;普通文本为静态。

  • 无动态绑定
  • 没有 v-if 和 v-for 指令
  • 不是内置的标签
  • 是平台保留标签(html和svg标签)
  • 不是 template 标签的直接子元素并且没有包含在 for 循环中
  • 结点包含的属性只能有isStaticKey中指定的几个

现在你知道了 node.static=isStatic(node) 什么情况为false, 什么情况为true吧!

回归到markStatic$1

if (node.type === 1) {
	// do not make component slot content static. this avoids
	// 1. components not able to mutate slot nodes
	// 2. static slot content fails for hot-reloading
	if (
		!isPlatformReservedTag(node.tag) &&
		node.tag !== 'slot' &&
		node.attrsMap['inline-template'] == null
	) {
		return
	}
	for (var i = 0, l = node.children.length; i < l; i++) {
		var child = node.children[i];
		markStatic$1(child);
		if (!child.static) {
			node.static = false;
		}
	}
	if (node.ifConditions) {
		for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
			var block = node.ifConditions[i$1].block;
			markStatic$1(block);
			if (!block.static) {
				node.static = false;
			}
		}
	}
}

来看看它做了什么,通过一个 if 判断node.type值为1,对标签节点进行处理。如果遇到特殊情况会直接退出去。 什么特殊情况呢?

// do not make component slot content static. this avoids
// 1. components not able to mutate slot nodes
// 2. static slot content fails for hot-reloading
if (
	!isPlatformReservedTag(node.tag) &&
	node.tag !== 'slot' &&
	node.attrsMap['inline-template'] == null
) {
	return
}

当遇到了非平台保留标签 isPlatformReservedTag(node.tag), 并且标签节点是 slot,并且节点中有inline-template(内联模板)三者都满足此时会终止函数的执行。

如果不满足条件:

for (var i = 0, l = node.children.length; i &lt; l; i++) {
	var child = node.children[i];
	markStatic$1(child);
	if (!child.static) {
		node.static = false;
	}
}

通过 node.children 找到子节点,递归子节点。

if (!child.static) {
     node.static = false;
}

子节点非静态,该节点也标注非静态 。这块设计的不太合理有更多好的优化方案,在Vue3.0中增加了"动静分离的策略" 尤大称之为 Block tree 后续在跟大家掰扯。

接下来看下 markStaticRoots。

markStaticRoots 源码

function markStaticRoots(node, isInFor) {
	if (node.type === 1) {
		if (node.static || node.once) {
			node.staticInFor = isInFor;
		}
		//一个节点要成为根节点,那么要满足以下条件:
                //1、静态节点,并且有子节点
                //2、子节点不能仅为一个文本节点
		if (node.static &amp;&amp; node.children.length &amp;&amp; !(
				node.children.length === 1 &amp;&amp;
				node.children[0].type === 3
			)) {
			node.staticRoot = true;
			return
		} else {
			node.staticRoot = false;
		}
                //循环递归标记
		if (node.children) {
			for (var i = 0, l = node.children.length; i &lt; l; i++) {
				markStaticRoots(node.children[i], isInFor || !!node.for);
			}
		}
		if (node.ifConditions) {
			for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 &lt; l$1; i$1++) {
				markStaticRoots(node.ifConditions[i$1].block, isInFor);
			}
		}
	}
}

一个节点要成为静态根节点,需要满足以下条件:

  • 自身为静态节点,并且有子节点
  • 子节点不能仅为一个文本节点

对于第二个条件,主要考虑到标记静态根节点的受益较小。接下来递归循环其子节点,循环标记。

以上就是Vue 编译器optimize源码分析的详细内容,更多关于Vue 编译器optimize的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue 中的compile操作方法

    在 Vue 里,模板编译也是非常重要的一部分,里面也非常复杂,这次探究不会深入探究每一个细节,而是走一个全景概要,来吧,大家和我一起去一探究竟. 初体验 我们看了 Vue 的初始化函数就会知道,在最后一步,它进行了 vm.$mount(el) 的操作,而这个 $mount 在两个地方定义过,分别是在 entry-runtime-with-compiler.js(简称:eMount) 和 runtime/index.js(简称:rMount) 这两个文件里,那么这两个有什么区别呢? // entr

  • 解决vue安装less报错Failed to compile with 1 errors的问题

    1.创建vue项目后安装less,执行 npm install less less-loader --save-dev 下载版本为:less-loader@6.1.0 , less@3.11.3,重启服务报错,报错信息如下: 2.报错原因 less 本版太高需要降低版本,执行代码 先移除之前版本: npm uninstall less-loader 下载指定版本: npm install less-loader@5.0.0 -D 3.重启代码就可以了,若还是报错可移除文件node_modules

  • 详解如何解决Vue和vue-template-compiler版本之间的问题

    今天把远程仓库拉下项目,运行'npm run dev'时,报错 Module build failed: Error: Cannot find module 'vue-template-compiler' 报错原因:通常出现于一些依赖库的更新或者安装新的依赖库之后(可以认为npm update已经成为一种习惯),导致了vue和vue-template-compiler的版本不一致. 解决方案:统一vue和vue-template-compiler的版本 "vue": "2.3

  • vue原理Compile之optimize标记静态节点源码示例

    目录 引言 而 optimize 的作用是什么呢? 1 是否存在 v-pre 2 不能存在 node.hasBindings 3 不能存在 node.if 和 node.for 4 节点名称不能是slot 或者 component 5 isPlatformReservedTag(node.tag) 6 isDirectChildOfTemplateFor(node) 7 Object.keys(node).every(isStaticKey) 标记静态节点 1 isStatic 这个方法对 as

  • vue原理Compile从新建实例到结束流程源码

    目录 引言 1 获取 template 模板 2 生成 render 3 保存 render createCompiler baseCompile createCompilerCreator 生成一个函数 compile 返回 compileToFunctions 和 compile 但是为什么分出一个 compileToFunctions 呢? 因为要做模板缓存!! createCompileToFunctionFn 他的缓存是怎么做的 理清思路 内部函数的作用是 引言 Compile 的内容

  • Vue编译器optimize源码分析

    目录 引言 optimize 源码之旅 markStatic$1源码 isStatic源码 复杂点的 回归到markStatic$1 markStaticRoots 源码 引言 接上文 parseHTML 函数源码解析 chars.end.comment钩子函数 上一章节我们讲到通过解析将template转成AST(抽象语法树),接下来继续对模型树优化,进行静态标注.那么问题来了,什么是静态标注?为什么要静态标注. 在源码的注释中我们找到了下面这段话: /** * Goal of the opt

  • Vue编译器AST抽象语法树源码分析

    目录 引言 baseCompile主要核心代码 如何写一个程序来识别 Token parse 函数解析模板字符串 引言 接上篇  Vue编译器源码分析compile 解析 baseCompile主要核心代码 // `createCompilerCreator` allows creating compilers that use alternative // parser/optimizer/codegen, e.g the SSR optimizing compiler. // Here we

  • Vue编译器源码分析compileToFunctions作用详解

    目录 引言 Vue.prototype.$mount函数体 源码出处 options.delimiters & options.comments compileToFunctions函数逐行分析 createFunction 函数源码 引言 Vue编译器源码分析 接上篇文章我们来分析:compileToFunctions的作用. 经过前面的讲解,我们已经知道了 compileToFunctions 的真正来源你可能会问为什么要弄的这么复杂?为了搞清楚这个问题,我们还需要继续接触完整的代码. 下面

  • Vue 中 template 有且只能一个 root的原因解析(源码分析)

    引言 今年, 疫情 并没有影响到各种面经的正常出现,可谓是络绎不绝(学不动...).然后,在前段时间也看到一个这样的关于 Vue 的问题, 为什么每个组件 template 中有且只能一个 root? 可能,大家在平常开发中,用的较多就是 template 写 html 的形式.当然,不排除用 JSX 和 render() 函数的.但是,究其本质,它们最终都会转化成 render() 函数.然后,再由 render() 函数转为 Vritual DOM (以下统称 VNode ).而 rende

  • vue 虚拟dom的patch源码分析

    本文介绍了vue 虚拟dom的patch源码分析,分享给大家,具体如下: 源码目录:src/core/vdom/patch.js function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { let oldStartIdx = 0 let newStartIdx = 0 let oldEndIdx = oldCh.length - 1 let oldStartVnode = oldCh[0]

  • Vue 源码分析之 Observer实现过程

    导语: 本文是对 Vue 官方文档深入响应式原理(https://cn.vuejs.org/v2/guide/reactivity.html)的理解,并通过源码还原实现过程. 响应式原理可分为两步,依赖收集的过程与触发-重新渲染的过程.依赖收集的过程,有三个很重要的类,分别是 Watcher.Dep.Observer.本文主要解读 Observer . 这篇文章讲解上篇文章没有覆盖到的 Observer 部分的内容,还是先看官网这张图: Observer 最主要的作用就是实现了上图中touch

  • Vue.js源码分析之自定义指令详解

    前言 除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令. 官网介绍的比较抽象,显得很高大上,我个人对自定义指令的理解是:当自定义指令作用在一些DOM元素或组件上时,该元素在初次渲染.插入到父节点.更新.解绑时可以执行一些特定的操作(钩子函数() 自定义指令有两种注册方式,一种是全局注册,使用Vue.directive(指令名,配置参数)注册,注册之后所有的Vue实例都可以使用,另一种是局部注册,在创建Vue实例时通过directives属性创建局部指

  • Vue源码分析之虚拟DOM详解

    为什么需要虚拟dom? 虚拟DOM就是为了解决浏览器性能问题而被设计出来的.例如,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一次性attch到DOM树上,再进行后续操作,避免大量无谓的计算量.简单来说,可以把Virtual DOM 理解为一个简单的JS对象,并且最少包含标签名( tag).属性(attrs)和子元素对象( children)三个属性. ----- 元素节点: 元素节点更贴近于我们

  • Vue高级组件之函数式组件的使用场景与源码分析

    目录 介绍 使用场景 源码分析 总结 介绍 Vue提供了一种可以让组件变为无状态.无实例的函数化组件.从原理上说,一般子组件都会经过实例化的过程,而单纯的函数组件并没有这个过程,它可以简单理解为一个中间层,只处理数据,不创建实例,也是由于这个行为,它的渲染开销会低很多.实际的应用场景是,当我们需要在多个组件中选择一个来代为渲染,或者在将children,props,data等数据传递给子组件前进行数据处理时,我们都可以用函数式组件来完成,它本质上也是对组件的一个外部包装. 使用场景 定义两个组件

  • Vue3源码分析组件挂载初始化props与slots

    目录 前情提要 初始化组件 (1).setupComponent (2).initProps (3).initSlots 额外内容 总结 前情提要 上文我们分析了挂载组件主要调用了三个函数: createComponentInstance(创建组件实例).setupComponent(初始化组件).setupRenderEffect(更新副作用).并且上一节中我们已经详细讲解了组件实例上的所有属性,还包括emit.provide等的实现.本文我们将继续介绍组件挂载流程中的初始化组件. 本文主要内

随机推荐