在上一系列中,我么已经了解到$mount是如何工做的,最后经过调用 updateComponent方法来执行vm._update(vm._render(), hydrating);
,接下来咱们来分析一下 vm._render()方法是如何运行的。html
首先来看一下使用template的形式,node
new Vue({
el: '#app',
template: '<h1>hello world</h1>'
})
// 或者这种方式
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
复制代码
那么,咱们如何手写 render 函数呢?仔细观察下面这段代码,试想一下这里的 createElement 参数是什么 。浏览器
new Vue({
el: '#app',
render(createElement) {
return createElement('div', {
attrs: {
id: 'app1' //注意这里的id 是 app1
}
}, this.value)
},
data() {
return {
value: 'render function'
}
}
})
复制代码
来看页面效果:bash
请注意上面红框框的 id, 是咱们刚刚定义的 'app1'。因此咱们为何不能将元素绑定在body/html这些元素上就知道了吧,由于它会替换掉页面上的元素。app
好了,进入正题,开始分析源码。函数
Vue 的 _render 方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 Node。它的定义在 src/core/instance/render.js 文件中:工具
注意,在分析源码的时候必定要走主线,不能一次性将某一个方法全看完或看透彻,由于它可能依赖了不少其它变量或者方法,若是都去分析,可能到后面看着看着就什么都看不懂了。在这里咱们只分析 render 方法,像下面的 _parentVnode、$slots、$vnode 等等在后面的系列章节中再分析。ui
Flow 是 facebook 出品的 JavaScript 静态类型检查工具。Vue.js 的源码利用了 Flow 作了静态类型检查,
function (): VNode
表示这个方法的返回值是一个 vnode。this
在这段代码中,主要研究这一段代码
render.call(vm._renderProxy, vm.$createElement)
spa
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
// 下面这两个 if 先不用看
// reset _rendered flag on slots for duplicate slot check
if (process.env.NODE_ENV !== 'production') {
for (const key in vm.$slots) {
// $flow-disable-line
vm.$slots[key]._rendered = false
}
}
if (_parentVnode) {
vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
}
// 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 {
// 分析主线
// 从这里咱们能够看出,手写render方法中的createElement参数就是 vm.$createElement方法
vnode = render.call(vm._renderProxy, vm.$createElement)
// 这个render 是 vm.$options.render
// vm._renderProxy 若是在生产环境下,其实就是 vm
// 若是在开发环境下,就是 Proxy 对象(ES6中的API,不了解的话能够去看看)
// vm.$createElement 定义在 initRender 函数中,初始化的时候定义的
// 手写 render 函数建立 vnode 的方法
// vm.$createElement = function (a, b, c, d) {
// return createElement(vm, a, b, c, d, true);
// };
// 若是是编译生成的render函数,建立vnode的方法则是下面这个方法
// vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
} 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') {
if (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
}
} else {
vnode = vm._vnode
}
}
// 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
}
}
复制代码
这个方法在初始化的时候就定义好了,在 initMixin中的 Vue.prototype._init() 方法中,咱们来看一下是如何定义的:
if (process.env.NODE_ENV !== 'production') {
// 若是是开发环境,就调用 initProxy 方法
initProxy(vm);
} else {
// 不然就是 vm 实例
vm._renderProxy = vm;
}
复制代码
描述: Proxy 能够理解成,在目标对象以前架设一层“拦截”,外界对该对象的访问,都必须先经过这层拦截,所以提供了一种机制,能够对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操做,能够译为“代理器”。
路径: src/core/instance/proxy.js
// proxy.js中还有一些其它方法,这里咱们只看 initProxy方法
let initProxy
if (process.env.NODE_ENV !== 'production') {
// 判断咱们的浏览器支不支持proxy方法
const hasProxy =
typeof Proxy !== 'undefined' && isNative(Proxy)
if (hasProxy) {
const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')
config.keyCodes = new Proxy(config.keyCodes, {
set (target, key, value) {
if (isBuiltInModifier(key)) {
warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
return false
} else {
target[key] = value
return true
}
}
})
}
var hasHandler = {
has: function has (target, key) {
var has = key in target;
var isAllowed = allowedGlobals(key) || key.charAt(0) === '_';
if (!has && !isAllowed) {
// 这个警告就是当你使用了没有定义的变量或者方法时调用的方法
// 方法就不列出来了。
warnNonPresent(target, key);
}
return has || !isAllowed
}
};
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
// options.render上没有_withStripped,因此handlers 就是hasHandler
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm
}
}
}
export { initProxy }
复制代码
当执行完了 _initProxy 以后,再回到 render 方法中看以下这段代码
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
// render function 返回了多个 vnode 根节点
// 应当返回单一 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
return vnode
复制代码
vm._render 最终是经过执行 createElement 方法并返回的是 vnode,那这个 createElement 是如何建立 vnode 的呢 ? 在后面的两个系列中,我会先介绍 virtual DOM 的概念,而后再分析 createElement 的实现。