_render()
_render 函数的的作用就是把当前 vm 实例渲染成 VNODE,VNODE的概念在下一节会讲。
_render 函数路径是: \src\core\instance\render.js
JavaScript
export function renderMixin (Vue: Class<Component>) {
// ... 省略代码 ...
// 定义私有的render方法,返回 VNode 对象
Vue.prototype._render = function (): VNode {
// 缓存当前实例
const vm: Component = this
// 获取options中的render函数
// 这个render可以是自己写的也可以是通过编译生成的
const { render, _parentVnode } = vm.$options
// ... 省略代码 ...
try {
currentRenderingInstance = vm
// 调用render函数
// 第一个参数是上下文(在生产环境下就是this,开发环境是Proxy对象)
// 第二个参数是Vue的createElement方法
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
// ... 省略代码 ...
} finally {
// ... 省略代码 ...
}
// 如果返回的vnode不是VNode对象的话,就创建一个空的vnode,防止报错
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()
}
// 设置父节点
vnode.parent = _parentVnode
return vnode
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
renderMixin 是在 第一章 讲new Vue()时,\src\core\instance\index.js 中调用的。
上述代码比较核心的是 vnode = render.call(vm._renderProxy, vm.$createElement)
粗略看一下这两个参数:
vm._renderProxy
这个是在 _init() 的时候初始化的,\src\core\instance\init.js
JavaScript
Vue.prototype._init = function (options?: Object) {
// ... 省略代码 ...
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// ... 省略代码 ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
这里的 initProxy 在本文下方部分,接下来先看第二个参数
vm.$createElement
createElement 是在当前模块的 initRender 中定义的方法
JavaScript
export function initRender (vm: Component) {
// ... 省略代码 ...
// 被编译生成的render函数所使用的的方法
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// 手写render函数的方法
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// ... 省略代码 ...
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
都调用了 createElement 方法,这个方法在下一节讲 Virtual DOM 会解释,这里先不用深究,它的作用就是创建虚拟dom。
这个 initRender 方法就是在 _init() 初始化中调用的
initProxy()
需要先了解一下ES6的 Proxy
这里的 Proxy的作用就是拦截 vm 实例对象的属性访问器,如果 vm 上没有要访问的属性,就执行自己的逻辑,而不是浏览器自身的报错。
比如 在Vue中通过 this.xxx 想要获取 data 中定义的变量,如果this上没有 xxx 变量的话,正常浏览器就返回 undefined; 但是这里Vue做了一层拦截,如果this上没有 xxx 变量,就会执行自己的报警告信息。
所以才能在使用过程中在控制台看到一些Vue自己的报错信息。
代码在:\src\core\instance\proxy.js
JavaScript
let initProxy
// 开发环境才需要处理 Proxy,虽然在init中已经判断过一次了
// 但是在别的地方也会单独使用这个模块,所以在这里再做一次判断
if (process.env.NODE_ENV !== 'production') {
// 定义报错信息
const allowedGlobals = makeMap(
'Infinity,undefined,NaN,isFinite,isNaN,' +
'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
'require' // for Webpack/Browserify
)
const warnNonPresent = (target, key) => {
warn(
`Property or method "${key}" is not defined on the instance but ` +
'referenced during render. Make sure that this property is reactive, ' +
'either in the data option, or for class-based components, by ' +
'initializing the property. ' +
'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',
target
)
}
const warnReservedPrefix = (target, key) => {
warn(
`Property "${key}" must be accessed with "$data.${key}" because ` +
'properties starting with "$" or "_" are not proxied in the Vue instance to ' +
'prevent conflicts with Vue internals' +
'See: https://vuejs.org/v2/api/#data',
target
)
}
// 判断浏览器支不支持 Proxy
const hasProxy =
typeof Proxy !== 'undefined' && isNative(Proxy)
// ... 省略代码 ...
const hasHandler = {
has (target, key) {
const has = key in target
const isAllowed = allowedGlobals(key) ||
(typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
// 如果元素不在target下,也不是在允许的全局属性下,就报警告
// 在data或者methods中未定义却在template中调用了 就会报错
if (!has && !isAllowed) {
if (key in target.$data) warnReservedPrefix(target, key)
else warnNonPresent(target, key)
}
return has || !isAllowed
}
}
// ... 省略代码 ...
initProxy = function initProxy (vm) {
// 先判断浏览器支不支持 Proxy(ES6提供的API,对对象访问做一个劫持)
if (hasProxy) {
// 确定使用哪个代理处理程序
const options = vm.$options
// 本文的案例中,这里的handlers就是hasHandler
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
vm._renderProxy = new Proxy(vm, handlers)
} else {
// target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
vm._renderProxy = vm
}
}
}
export { initProxy }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76