vue的_render
定义在src/core/instance/render.js
文件中:
// src/core/instance/render.js
Vue.prototype._render = function(): VNode {
const vm: Component = this;
// 在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法
const { render, _parentVnode } = vm.$options;
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
);
}
vm.$vnode = _parentVnode;
let vnode;
try {
currentRenderingInstance = vm;
// 重点在_renderProxy和$createElement函数
// ======================================
vnode = render.call(vm._renderProxy, vm.$createElement);
// ======================================
} catch (e) {
handleError(e, vm, `render`);
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;
}
} finally {
currentRenderingInstance = null;
}
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0];
}
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;
};
_renderProxy
// src/core/instance/init.js
Vue.prototype._init = function(options?: Object) {
//...
// 在生产环境,`_renderProxy`就是其本身,开发环境就会调用`initProxy`
if (process.env.NODE_ENV !== "production") {
initProxy(vm);
} else {
vm._renderProxy = vm;
}
// ...
};
关于initProxy
函数:
// src/core/instance/proxy.js
let initProxy;
// 判断一下浏览器是否支持Proxy
const hasProxy = typeof Proxy !== "undefined" && isNative(Proxy);
initProxy = function initProxy(vm) {
// 如果浏览器支持proxy
if (hasProxy) {
// determine which proxy handler to use
const options = vm.$options;
// handlers,是负责定义代理行为的对象。
// options.render._withStripped 的取值一般情况下都是 false ,所以 handlers 的取值为 hasHandler
const handlers =
options.render && options.render._withStripped ? getHandler : hasHandler;
vm._renderProxy = new Proxy(vm, handlers);
} else {
vm._renderProxy = vm;
}
};
// getHandler方法主要是针对读取代理对象的某个属性时进行的操作,
// 该方法可以在开发者错误的调用vm属性时,提供提示作用
const getHandler = {
get (target, key) {
if (typeof key === 'string' && !(key in target)) {
warnNonPresent(target, key)
}
return target[key]
}
}
// hasHandler方法的应用场景在于查看vm实例是否拥有某个属性
const hasHandler = {
// has函数就是求出属性查询的结果然后存入 has变量中
has(target, key) {
// 首先使用in操作符判断该属性是否在vm实例上存在
const has = key in target;
// allowedGlobals看属性名称是否可用
// 就是检查key是不是这些全局的属性、函数其中的任意一个。
// 所以isAllowed为true的条件就是key是js全局关键字或者非vm.$data下的以_开头的字符串。
const isAllowed =
// allowedGlobals最终存储的是一个代表特殊属性名称的映射表
allowedGlobals(key) ||
(typeof key === "string" &&
key.charAt(0) === "_" &&
!(key in target.$data));
// 如果!has(访问的key在vm不存在)和!isAllowed(不在特殊属性名称映射表中,或没有以_符号开头)同时成立的话
if (!has && !isAllowed) {
// 在data中声明了, 但是在Vue中,以$或_开头的属性不会被代理,因为有可能与内置属性产生冲突
if (key in target.$data) warnReservedPrefix(target, key);
// 在data中没有声明,但是却被渲染了
else warnNonPresent(target, key);
}
return has || !isAllowed;
}
};
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 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
);
};
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
)
}
$createElement
vm.$createElement
的定义是在initRender
函数中:
function initRender(vm: Component) {
// ...
// 这两行是分别给实例vm加上_c和$createElement方法。
// 这两个方法都调用了createElement方法,只是最后一个参数值不同
// vm._c是内部函数,它是被模板编译成的 render 函数使用
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false);
// vm.$createElement是提供给用户编写的 render 函数使用
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true);
// ...
}
之前我们使用了template
编写代码:
<div id="app">{{message}}</div>
<script>
var app = new Vue({
el: "#app",
data() {
return {
message: "森林小哥哥"
};
}
});
</script>
如果我们不使用template
,而是编写render
data(){
return {
message: 'Hello Wolrd'
}
},
render: function(createElement){
return createElement('div', {
attrs: {
id: app
}
}, this.message)
}
根据源代码解读我们可以改写成
render: function(){
return this.$createElement('div', {
attrs: {
id: app
}
}, this.message)
}
使用字符串模板的话,在相关代码执行完前,会先在页面显示
{{ message }}
,然后再展示 Hello World;而我们手动编写render
函数的话,内部就不用执行把字符串模板转换成render
函数这个操作,并且是空白页面之后立即就显示 Hello World ,用户体验会更好。
总结
_render
函数中 主要调用了vnode = render.call(vm._renderProxy, vm.$createElement);
来获得vnode并返回。其中_renderProxy
就是确定vm._renderProxy
的值,如果浏览器Proxy属性存在,则把包装后的vm
属性赋值给_renderProxy
属性值,否则把vm是实例本身赋值给_renderProxy属性。$createElement
函数的作用就是生成一个 VNode节点(虚拟dom)