vue 中Virtual Dom被创建的方法

本文将通过解读render函数的源码,来分析vue中的vNode是如何创建的。在vue2.x的版本中,无论是直接书写render函数,还是使用template或el属性,或是使用.vue单文件的形式,最终都需要编译成render函数进行vnode的创建,最终再渲染成真实的DOM。 如果对vue源码的目录还不是很了解,推荐先阅读下 深入vue -- 源码目录和编译过程。

01  render函数

render方法定义在文件 src/core/instance/render.js 中

Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options // ... // set parent vnode. this allows render functions to have access // to the data on the placeholder node. vm.$vnode = _parentVnode // render self let vnode try { vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { handleError(e, vm, `render`) // return error render result, // or previous vnode to prevent render error causing blank component /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e) } catch (e) { handleError(e, vm, `renderError`) vnode = vm._vnode } } else { vnode = vm._vnode } } // if the returned array contains only a single node, allow it if (Array.isArray(vnode) && vnode.length === 1) { vnode = vnode[0] } // return empty vnode in case the render function errored out if (!(vnode instanceof VNode)) { if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ) } vnode = createEmptyVNode() } // set parent vnode.parent = _parentVnode return vnode }

_render定义在vue的原型上,会返回vnode,vnode通过代码render.call(vm._renderProxy, vm.$createElement)进行创建。

在创建vnode过程中,如果出现错误,就会执行catch中代码做降级处理。

_render中最核心的代码就是:

vnode = render.call(vm._renderProxy, vm.$createElement)

接下来,分析下这里的render,vm._renderProxy,vm.$createElement分别是什么。

render函数

const { render, _parentVnode } = vm.$options

render方法是从$options中提取的。render方法有两种途径得来:

在组件中开发者直接手写的render函数

通过编译template属性生成

参数 vm._renderProxy

vm._renderProxy定义在 src/core/instance/init.js 中,是call的第一个参数,指定render函数执行的上下文。

/* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm }

生产环境:

vm._renderProxy = vm,也就是说,在生产环境,render函数执行的上下文就是当前vue实例,即当前组件的this。

开发环境:

开发环境会执行initProxy(vm),initProxy定义在文件 src/core/instance/proxy.js 中。

let initProxy // ... initProxy = function initProxy (vm) { if (hasProxy) { // determine which proxy handler to use const options = vm.$options const handlers = options.render && options.render._withStripped ? getHandler : hasHandler vm._renderProxy = new Proxy(vm, handlers) } else { vm._renderProxy = vm } }

hasProxy的定义如下

const hasProxy = typeof Proxy !== 'undefined' && isNative(Proxy)

用来判断浏览器是否支持es6的Proxy。

Proxy作用是在访问一个对象时,对其进行拦截,new Proxy的第一个参数表示所要拦截的对象,第二个参数是用来定制拦截行为的对象。

开发环境,如果支持Proxy就会对vm实例进行拦截,否则和生产环境相同,直接将vm赋值给vm._renderProxy。具体的拦截行为通过handlers对象指定。

当手写render函数时,handlers = hasHandler,通过template生成的render函数,handlers = getHandler。 hasHandler代码:

const hasHandler = { has (target, key) { const has = key in target const isAllowed = allowedGlobals(key) || (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)) if (!has && !isAllowed) { if (key in target.$data) warnReservedPrefix(target, key) else warnNonPresent(target, key) } return has || !isAllowed } }

getHandler代码

const getHandler = { get (target, key) { if (typeof key === 'string' && !(key in target)) { if (key in target.$data) warnReservedPrefix(target, key) else warnNonPresent(target, key) } return target[key] } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/df19bd306ec1aa06538fa0401d25ea42.html