【vue源码解析】render到底做了什么?
栏目: JavaScript · 发布时间: 5年前
内容简介:render函数可以作为一道分割线,render函数的左边可以称之为编译期,将Vue的模板转换为渲染函数。render函数的右边是Vue的运行时,主要是基于渲染函数生成Virtual DOM树,Diff和Patch。
render的作用
render函数可以作为一道分割线,render函数的左边可以称之为编译期,将Vue的模板转换为渲染函数。render函数的右边是Vue的运行时,主要是基于渲染函数生成Virtual DOM树,Diff和Patch。
- render渲染函数将结合数据生成Virtual DOM的。
- 有了虚拟的DOM树后,再交给Patch函数,负责把这些虚拟DOM真正施加到真实的DOM上。在这个过程中,Vue有自身的响应式系统来侦测在渲染过程中所依赖到的数据来源。在渲染过程中,侦测到数据来源之后就可以精确感知数据源的变动。
- 根据需要重新进行渲染。当重新进行渲染之后,会生成一个新的树,将新的树与旧的树进行diff对比,就可以最终落实到真实DOM上的改动。
一个简单的实例,看render:
vue的渲染机制可以总结如下图:
render前置操作
- 为何要用with(this){}包裹?
- 何时将render函数的字符串转成函数?
第1个问题?:render函数里面的变量必须要指向Vue实例
第2个问题?:经过parse生成AST和optimize对AST树的优化,会生成render函数的字符串。在下图框出的createCompilerCreator(在src\compiler\create-compiler.js),调用createCompileToFunctionFn将字符串转成函数。
createCompileToFunctionFn(在src\compiler\to-function.js)中,会优先读缓存信息,若没有才会执行编译方法,同时将render字符串通过createFunction(在src\compiler\to-function.js中)调用New Function()的方法,创造render函数,并且缓存信息。
function createFunction (code, errors) { try { return new Function(code) } catch (err) { errors.push({ err, code }) return noop }}复制代码
render详解
render初始化入口:src\core\instance\index.js
renderMixin主要做了三件事情
- 执行installRenderHelpers(Vue.prototype),在原型上扩展如下,生成vnode节点的几个函数解析,在执行render函数时,会调用,这里先简单的看一个。
-
在原型上扩展Vue.prototype.$nextTick方法,在watch监听数据变化时,不会立马更新视图,会推到一个队列里,nextTick会触发视图的更新
-
在原型上扩展Vue.prototype._render。
//核心代码 vnode = render.call(vm._renderProxy, vm.$createElement)复制代码
以上是render函数执行的核心代码,render归根结底,是调用createElement创建vnode节点下面会详细分析createElement到底做了哪些事情,首先我们先通过一个例子,来看下,createElement方法的入参,主要为3个入参,可通过一个例子呈现:
- 第一个参数是HTML标签字符 “必选”
- 第一个参数是HTML标签字符 “必选”
- 第三个参数是传涵盖子元素的一个数组 “可选”
<div id="app"> <render-element></render-element> </div> Vue.component('render-element', { render: function (createElement) { var self = this return createElement( 'div', // 第一个参数是HTML标签字符 “必选” { class: { title: true }, style: { border: '1px solid', padding: '10px' } }, // 第二个参数是包含模板相关属性的数据对象 “可选” [ createElement('h1', 'Hello Vue!'), createElement('p', '开始学习Vue!') ] // 第三个参数是传涵盖子元素的一个数组 “可选” ) } }) let app = new Vue({ el: '#app' })复制代码
createElement的详细解析过程
const SIMPLE_NORMALIZE = 1 const ALWAYS_NORMALIZE = 2 function createElement (context, tag, data, children, normalizationType, alwaysNormalize) { // 兼容不传data的情况 if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE // 调用_createElement创建虚拟节点 return _createElement(context, tag, data, children, normalizationType) } function _createElement (context, tag, data, children, normalizationType) { // 如果存在data.__ob__,说明data是被Observer观察的数据,不能用作虚拟节点的data,返回一个空节点 if (data && data.__ob__) { process.env.NODE_ENV !== 'production' && warn( `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + 'Always create fresh vnode data objects in each render!', context ) return createEmptyVNode() } // 当组件的tag未空,渲染一个空节点 if (!tag) { return createEmptyVNode() } // 作用域插槽 if (Array.isArray(children) && typeof children[0] === 'function') { data = data || {} data.scopedSlots = { default: children[0] } children.length = 0 } // 根据normalizationType的值,选择不同的处理方法 if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } let vnode, ns // 如果标签名是字符串类型 if (typeof tag === 'string') { let Ctor // 获取标签命名空间 ns = config.getTagNamespace(tag) // 判断是否为保留标签 if (config.isReservedTag(tag)) { // 如果是保留标签,就创建一个这样的vnode vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) // vm的components上查找是否有这个标签的定义 } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) { // 如果找到了这个标签的定义,就以此创建虚拟组件节点 vnode = createComponent(Ctor, data, context, children, tag) } else { // 兜底方案,正常创建一个vnode vnode = new VNode( tag, data, children, undefined, undefined, context ) } // 当tag不是字符串的时候,我们认为tag是组件的构造类,直接创建 } else { vnode = createComponent(tag, data, context, children) } // 如果有vnode if (vnode) { // 应用namespace,绑定data,然后返回vnode if (ns) applyNS(vnode, ns) if (isDef(data)) registerDeepBindings(data) return vnode } else { return createEmptyVNode() } } }复制代码
梳理成流程图如下:
特别注意当通过tag判断为组件时,会执行createcompontent()
vnode应用到dom
在数据产生变化时,会通知到vm.watcher,最终调用vm.update,也就是调用patch方法映射到真实的dom节点中,这里涉及到数据双向绑定相关,请详见[vue]双向数据绑定。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- ReactNative源码解析-初识源码
- Spring源码系列:BeanDefinition源码解析
- Spring源码分析:AOP源码解析(下篇)
- Spring源码分析:AOP源码解析(上篇)
- 注册中心 Eureka 源码解析 —— EndPoint 与 解析器
- 新一代Json解析库Moshi源码解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web之困:现代Web应用安全指南
(美)Michal Zalewski / 朱筱丹 / 机械工业出版社 / 2013-10 / 69
《web之困:现代web应用安全指南》在web安全领域有“圣经”的美誉,在世界范围内被安全工作者和web从业人员广为称道,由来自google chrome浏览器团队的世界顶级黑客、国际一流安全专家撰写,是目前唯一深度探索现代web浏览器安全技术的专著。本书从浏览器设计的角度切入,以探讨浏览器的各主要特性和由此衍生出来的各种安全相关问题为主线,深入剖析了现代web浏览器的技术原理、安全机制和设计上的......一起来看看 《Web之困:现代Web应用安全指南》 这本书的介绍吧!