虽然vue3已经出来很久了,但我觉得vue.js的源码还是非常值得去学习一下。vue.js里面封装的很多工具类在我们平时工作项目中也会经常用到。所以我近期会对vue.js的源码进行解读,分享值得去学习的代码片段,这篇文章将会持续更新。
1.children 的规范化:normalizeArrayChildren
2.组件实例化:initInjections
3.slot插槽函数:resolveSlots,normalizeScopedSlots,normalizeScopedSlot,proxyNormalSlot,renderSlot
4.Vue 的各类渲染方法–辅助函数:
markOnce;// 标记v-once
toNumber;// 转换成Number类型
toString;//转换成字符串
renderList;//生成列表VNode
renderSlot;//生成解析slot节点
looseEqual;
looseIndexOf;
renderStatic;//生成静态元素
resolveFilter;// 获取过滤器
checkKeyCodes;//检查键盘事件keycode
bindObjectProps;//绑定对象属性
createTextVNode;//创建文本VNod
createEmptyVNode;//创建空节点VNode
resolveScopedSlots;//获取作用域插槽
bindObjectListeners;//处理v-on=’{}'到vnode data上
bindDynamicKeys;//处理动态属性名
prependModifier;//处理修饰符
1.vue的事件机制
①.监听事件:$on
②.监听事件,只监听1次:$once
③.移除自定义事件监听器:$off
④.触发事件: $emit
2.函数式组件的实现
createFunctionalComponent
3.组件的渲染和更新过程
componentVNodeHooks
在组件初始化的时候实现init、prepatch、insert、destroy钩子函数
//children 的规范化,simpleNormalizeChildren和normalizeChildren都是用来把children由树状结构变成一维数组
// 模板编译器试图通过在编译时静态分析模板来最小化规范化的需要
// 对于纯HTML标记,可以完全跳过规范化,因为生成的呈现函数保证返回Array。有两种情况需要额外的规范化:
// 当子级包含组件时-因为功能组件可能返回数组而不是单个根。在这种情况下,只需要一个简单的规范化—如果任何子对象是数组,
// 我们就用Array.prototype.concat将整个对象展平。它保证只有1级深度,因为功能组件已经规范化了它们自己的子级
function simpleNormalizeChildren (children) {
for (var i = 0; i < children.length; i++) {
if (Array.isArray(children[i])) {
return Array.prototype.concat.apply([], children)
}
}
return children
}
// 2.当子级包含总是生成嵌套数组的构造时,例如、、v-for,或者当子级由用户提供手写的呈现函数/JSX时。
// 在这种情况下,需要完全正常化,以满足所有可能类型的儿童价值观。
function normalizeChildren (children) {
return isPrimitive(children)
? [createTextVNode(children)]
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined
}
// node节点的判断条件
function isTextNode (node) {
return isDef(node) && isDef(node.text) && isFalse(node.isComment)
}
//children 的规范化,normalizeArrayChildren接收 2 个参数,children 表示要规范的子节点,nestedIndex 表示嵌套的索引
function normalizeArrayChildren (children, nestedIndex) {
var res = [];
var i, c, lastIndex, last;
//遍历children,
for (i = 0; i < children.length; i++) {
//将单个节点赋值给c
c = children[i];
//判断c的类型,如果是一个数组类型,则递归调用 normalizeArrayChildren;
//否则通过 createTextVNode 方法转换成 VNode 类型;
if (isUndef(c) || typeof c === 'boolean') { continue }
lastIndex = res.length - 1;
last = res[lastIndex];
//如果是一个数组类型,则递归调用 normalizeArrayChildren
if (Array.isArray(c)) {
if (c.length > 0) {
// 如果 children 是一个列表并且列表还存在嵌套的情况,则根据 nestedIndex 去更新它的 key
c = normalizeArrayChildren(c, ((nestedIndex || '') + "_" + i));
if (isTextNode(c[0]) && isTextNode(last)) {
res[lastIndex] = createTextVNode(last.text + (c[0]).text);
c.shift();
}
res.push.apply(res, c);
}
} else if (isPrimitive(c)) {
if (isTextNode(last)) {
//通过 createTextVNode 方法转换成 VNode 类型
res[lastIndex] = createTextVNode(last.text + c);
} else if (c !== '') {
res.push(createTextVNode(c));
}
} else {
if (isTextNode(c) && isTextNode(last)) {
res[lastIndex] = createTextVNode(last.text + c.text);
} else {
if (isTrue(children._isVList) &&
isDef(c.tag) &&
isUndef(c.key) &&
isDef(nestedIndex)) {
c.key = "__vlist" + nestedIndex + "_" + i + "__";
}
res.push(c);
}
}
}
return res
}
/* initProvide的作用就是将$options里的provide赋值到当前实例上 */
function initProvide (vm) {
//如果provide存在,当它是函数时执行该返回,否则直接将provide保存到Vue实例的_provided属性上
var provide = vm.$options.provide;
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide;
}
}
//组件实例化,初始化Inject参数, initInjections在初始化data/props之前被调用,主要作用是初始化vue实例的inject
function initInjections (vm) {
//遍历祖先节点,获取对应的inject
var result = resolveInject(vm.$options.inject, vm);
if (result) {
// toggleObserving是vue内部对逻辑的一个优化,就是禁止掉根组件 props的依赖收集
toggleObserving(false);
Object.keys(result).forEach(function (key) {
//将key编程响应式,这样就可以访问该元素
{
defineReactive$$1(vm, key, result[key], function () {
warn(
"Avoid mutating an injected value directly since the changes will be " +
"overwritten whenever the provided component re-renders. " +
"injection being mutated: \"" + key + "\"",
vm
);
});
}
});
toggleObserving(true);
}
}
// 确定Inject,vm指当前组件的实例
function resolveInject (inject, vm) {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
var result = Object.create(null);
//如果有符号类型,调用Reflect.ownKeys()返回所有的key,再调用filter
var keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject);
//获取所有的key,此时keys就是个字符串数组
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
// #6574 in case the inject object is observed...
if (key === '__ob__') { continue }
var provideKey = inject[key].from;
var source = vm;
while (source) {
//如果source存在_provided 且 含有provideKey这个属性,则将值保存到result[key]中
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey];
break
}
// 否则将source赋值给父Vue实例,直到找到对应的providekey为止
source = source.$parent;
}
// 如果最后source不存在,即没有从当前实例或祖先实例的_provide找到privideKey这个key
if (!source) {
// 如果有定义defult,则使用默认值
if ('default' in inject[key]) {
var provideDefault = inject[key].default;
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault;
} else {
warn(("Injection \"" + key + "\" not found"), vm);
}
}
}
return result
}
}
/* */
/**
* 主要作用是将children VNodes转化成一个slots对象,处理组件slot,返回slot插槽对象
* children指占位符Vnode里的内容
* context指占位符Vnode所在的vue实例
*/
function resolveSlots (
children,
context
) {
// 判断是否有children,即是否有插槽VNode
if (!children || !children.length) {
return {}
}
var slots = {};
// 遍历每一个子节点
for (var i = 0, l = children.length; i < l; i++) {
var child = children[i];
// data为VNodeData,保存父组件传递到子组件的props以及attrs等
var data = child.data;
//移出slot,删除该节点attrs的slot
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot;
}
// 判断是否为具名插槽,如果为具名插槽,还需要 子组件 / 函数子组件 渲染上下文一致
// 当需要向子组件的子组件传递具名插槽时,不会保持插槽的名字
if ((child.context === context || child.fnContext === context) &&
data && data.slot != null
) {
var name = data.slot;
var slot = (slots[name] || (slots[name] = []));
//处理父组件采用template形式的插槽
if (child.tag === 'template') {
slot.push.apply(slot, child.children || []);
} else {
slot.push(child);
}
} else {
//返回匿名default插槽VNode数组
(slots.default || (slots.default = [])).push(child);
}
}
// 忽略仅仅包含whitespace的插槽
for (var name$1 in slots) {
if (slots[name$1].every(isWhitespace)) {
delete slots[name$1];
}
}
return slots
}
// 方法用于判断指定字符是否为空白字符,空白符包含:空格、tab键、换行符
function isWhitespace (node) {
return (node.isComment && !node.asyncFactory) || node.text === ' '
}
/* 是否为异步占位 */
function isAsyncPlaceholder (node) {
return node.isComment && node.asyncFactory
}
/*normalizeScopedSlots函数的核心就是返回res对象,其key为slotTarget,value为fn */
//slots: 某节点 data 属性上 scopedSlots
//normalSlots: 当前节点下的普通插槽
//prevSlots 当前节点下的特殊插槽
function normalizeScopedSlots (
slots,
normalSlots,
prevSlots
) {
var res;
//判断是否拥有普通插槽
var hasNormalSlots = Object.keys(normalSlots).length > 0;
var isStable = slots ? !!slots.$stable : !hasNormalSlots;
var key = slots && slots.$key;
if (!slots) {
res = {};
} else if (slots._normalized) {
return slots._normalized
} else if (
isStable &&
prevSlots &&
prevSlots !== emptyObject &&
// slots $key 值与 prevSlots $key 相等
key === prevSlots.$key &&
//slots中没有普通插槽
!hasNormalSlots &&
//prevSlots中没有普通插槽
!prevSlots.$hasNormal
) {
return prevSlots
} else {
res = {};
//遍历作用域插槽
for (var key$1 in slots) {
if (slots[key$1] && key$1[0] !== '$') {
res[key$1] = normalizeScopedSlot(normalSlots, key$1, slots[key$1]);
}
}
}
// 对普通插槽进行遍历,将slot代理到scopeSlots上
for (var key$2 in normalSlots) {
if (!(key$2 in res)) {
res[key$2] = proxyNormalSlot(normalSlots, key$2);
}
}
// avoriaz seems to mock a non-extensible $scopedSlots object
// and when that is passed down this would cause an error
if (slots && Object.isExtensible(slots)) {
(slots)._normalized = res;
}
// $key , $hasNormal , $stable 是直接使用 vue 内部对 Object.defineProperty 封装好的 def() 方法进行赋值的
def(res, '$stable', isStable);
def(res, '$key', key);
def(res, '$hasNormal', hasNormalSlots);
return res
}
//将scopeSlots对应属性和方法挂载到scopeSlots,生成闭包,返回一个名为normalized的函数,$scopedSlots对象中的值就是此函数
function normalizeScopedSlot(normalSlots, key, fn) {
var normalized = function () {
var res = arguments.length ? fn.apply(null, arguments) : fn({});
res = res && typeof res === 'object' && !Array.isArray(res)
? [res] // single vnode
: normalizeChildren(res);
var vnode = res && res[0];
return res && (
!vnode ||
(res.length === 1 && vnode.isComment && !isAsyncPlaceholder(vnode)) // #9658, #10391
) ? undefined
: res
};
// this is a slot using the new v-slot syntax without scope. although it is
// compiled as a scoped slot, render fn users would expect it to be present
// on this.$slots because the usage is semantically a normal slot.
if (fn.proxy) {
Object.defineProperty(normalSlots, key, {
get: normalized,
enumerable: true,
configurable: true
});
}
return normalized
}
// 将slot代理到scopeSlots上
function proxyNormalSlot(slots, key) {
return function () { return slots[key]; }
}
/* */
/**
* 用于呈现v-for列表的运行时帮助程序
*/
function renderList (
val,
render
) {
var ret, i, l, keys, key;
//如果val为数组,则遍历val
if (Array.isArray(val) || typeof val === 'string') {
ret = new Array(val.length);
// 调用传入的函数,把值传入,数组保存结果
for (i = 0, l = val.length; i < l; i++) {
ret[i] = render(val[i], i);
}
//如果val为数字类型
} else if (typeof val === 'number') {
ret = new Array(val);
// 调用传入的函数,把值传入,数组保存结果
for (i = 0; i < val; i++) {
ret[i] = render(i + 1, i);
}
//如果val为object类型,则遍历对象
} else if (isObject(val)) {
if (hasSymbol && val[Symbol.iterator]) {
ret = [];
var iterator = val[Symbol.iterator]();
var result = iterator.next();
// 调用传入的函数,把值传入,数组保存结果
while (!result.done) {
ret.push(render(result.value, ret.length));
result = iterator.next();
}
} else {
keys = Object.keys(val);
ret = new Array(keys.length);
for (i = 0, l = keys.length; i < l; i++) {
key = keys[i];
ret[i] = render(val[key], key, i);
}
}
}
if (!isDef(ret)) {
ret = [];
}
(ret)._isVList = true;
return ret
}
/* */
// 调用renderSlot用函数的返回值进行渲染
// renderSlot函数会根据插槽名字找到对应的作用域Slot包装成的函数,
// 然后执行它,把子组件内的数据{ child:child }传进去
function renderSlot (
name,//插槽名
fallbackRender,//插槽默认内容生成的 vnode 数组
props,// props 对象
bindObject //v-bind 绑定对象
) {
var scopedSlotFn = this.$scopedSlots[name];
var nodes;
if (scopedSlotFn) {
props = props || {};
if (bindObject) {
if (!isObject(bindObject)) {
warn('slot v-bind without argument expects an Object', this);
}
props = extend(extend({}, bindObject), props);
}
nodes =
scopedSlotFn(props) ||
(typeof fallbackRender === 'function' ? fallbackRender() : fallbackRender);
} else {
nodes =
this.$slots[name] ||
(typeof fallbackRender === 'function' ? fallbackRender() : fallbackRender);
}
var target = props && props.slot;
if (target) {
return this.$createElement('template', { slot: target }, nodes)
} else {
return nodes
}
}
/* */
/**
*找到我们写的过滤器,并将参数传入进去
*/
function resolveFilter (id) {
// resolveAsset用于获取资源,也就是获取组件的构造函数
return resolveAsset(this.$options, 'filters', id, true) || identity
}
/* 检查按下的键,是否和配置的键值对匹配 */
function isKeyNotMatch (expect, actual) {
if (Array.isArray(expect)) {
return expect.indexOf(actual) === -1
} else {
return expect !== actual
}
}
/**
* 用于检查config.prototype中的键代码的运行时帮助程序,以Vue.prototype的形式公开
*/
function checkKeyCodes (
eventKeyCode,
key,
builtInKeyCode,
eventKeyName,
builtInKeyName
) {
// 比如 key 传入的是自定义名字 aaaa
// keyCode 从Vue 定义的 keyNames 获取 aaaa 的实际数字
// keyName 从 Vue 定义的 keyCode 获取 aaaa 的别名
// 并且以用户定义的为准,可以覆盖Vue 内部定义的
var mappedKeyCode = config.keyCodes[key] || builtInKeyCode;
// 该键只在 Vue 内部定义的 keyCode 中
if (builtInKeyName && eventKeyName && !config.keyCodes[key]) {
return isKeyNotMatch(builtInKeyName, eventKeyName)
// 该键只在 用户自定义配置的 keyCode 中
} else if (mappedKeyCode) {
return isKeyNotMatch(mappedKeyCode, eventKeyCode)
//原始键名
} else if (eventKeyName) {
return hyphenate(eventKeyName) !== key
}
return eventKeyCode === undefined
}
/**
* 用于将v-bind=“object”合并到VNode数据中的运行时帮助程序
*/
function bindObjectProps (
data,
tag,
value,
asProp,
isSync
) {
if (value) {
if (!isObject(value)) {
warn(
'v-bind without argument expects an Object or Array value',
this
);
} else {
if (Array.isArray(value)) {
value = toObject(value);
}
var hash;
var loop = function ( key ) {
if (
key === 'class' ||
key === 'style' ||
isReservedAttribute(key)
) {
hash = data;
} else {
var type = data.attrs && data.attrs.type;
hash = asProp || config.mustUseProp(tag, type, key)
? data.domProps || (data.domProps = {})
: data.attrs || (data.attrs = {});
}
var camelizedKey = camelize(key);
var hyphenatedKey = hyphenate(key);
if (!(camelizedKey in hash) && !(hyphenatedKey in hash)) {
hash[key] = value[key];
if (isSync) {
var on = data.on || (data.on = {});
on[("update:" + key)] = function ($event) {
value[key] = $event;
};
}
}
};
for (var key in value) loop( key );
}
}
return data
}
/* */
/**
* 生成静态元素
*/
function renderStatic (
index,
isInFor
) {
var cached = this._staticTrees || (this._staticTrees = []);
var tree = cached[index];
// if has already-rendered static tree and not inside v-for,
// we can reuse the same tree.
if (tree && !isInFor) {
return tree
}
// otherwise, render a fresh tree.
tree = cached[index] = this.$options.staticRenderFns[index].call(
this._renderProxy,
null,
this // for render fns generated for functional component templates
);
markStatic(tree, ("__static__" + index), false);
return tree
}
/**
* 标记v-once
*/
function markOnce (
tree,
index,
key
) {
markStatic(tree, ("__once__" + index + (key ? ("_" + key) : "")), true);
return tree
}
// 标记静态元素
function markStatic (
tree,
key,
isOnce
) {
if (Array.isArray(tree)) {
for (var i = 0; i < tree.length; i++) {
if (tree[i] && typeof tree[i] !== 'string') {
markStaticNode(tree[i], (key + "_" + i), isOnce);
}
}
} else {
markStaticNode(tree, key, isOnce);
}
}
//标记静态节点
function markStaticNode (node, key, isOnce) {
node.isStatic = true;
node.key = key;
node.isOnce = isOnce;
}
/* //处理v-on=’{}'到vnode data上 */
function bindObjectListeners (data, value) {
if (value) {
if (!isPlainObject(value)) {
warn(
'v-on without argument expects an Object value',
this
);
} else {
var on = data.on = data.on ? extend({}, data.on) : {};
for (var key in value) {
var existing = on[key];
var ours = value[key];
on[key] = existing ? [].concat(existing, ours) : ours;
}
}
}
return data
}
/* 获取作用域插槽 */
function resolveScopedSlots (
fns, // see flow/vnode
res,
// the following are added in 2.6
hasDynamicKeys,
contentHashKey
) {
res = res || { $stable: !hasDynamicKeys };
for (var i = 0; i < fns.length; i++) {
var slot = fns[i];
if (Array.isArray(slot)) {
resolveScopedSlots(slot, res, hasDynamicKeys);
} else if (slot) {
// marker for reverse proxying v-slot without scope on this.$slots
if (slot.proxy) {
slot.fn.proxy = true;
}
res[slot.key] = slot.fn;
}
}
if (contentHashKey) {
(res).$key = contentHashKey;
}
return res
}
/*//处理动态属性名 */
function bindDynamicKeys (baseObj, values) {
for (var i = 0; i < values.length; i += 2) {
var key = values[i];
if (typeof key === 'string' && key) {
baseObj[values[i]] = values[i + 1];
} else if (key !== '' && key !== null) {
// null is a special value for explicitly removing a binding
warn(
("Invalid value for dynamic directive argument (expected string or null): " + key),
this
);
}
}
return baseObj
}
//处理修饰符
// 帮助程序将修改器运行时标记动态追加到事件名称。
// 请确保仅在值已为字符串时追加,否则将转换为字符串并导致类型检查丢失。
function prependModifier (value, symbol) {
return typeof value === 'string' ? symbol + value : value
}
/* 安装渲染工具函数,大多数服务于编译器编译出来的代码 */
function installRenderHelpers (target) {
target._o = markOnce;// 标记v-once
target._n = toNumber;// 转换成Number类型
target._s = toString;//转换成字符串
target._l = renderList;//生成列表VNode
target._t = renderSlot;//生成解析slot节点
target._q = looseEqual;
target._i = looseIndexOf;
target._m = renderStatic;//生成静态元素
target._f = resolveFilter;// 获取过滤器
target._k = checkKeyCodes;//检查键盘事件keycode
target._b = bindObjectProps;//绑定对象属性
target._v = createTextVNode;//创建文本VNod
target._e = createEmptyVNode;//创建空节点VNode
target._u = resolveScopedSlots;//获取作用域插槽
target._g = bindObjectListeners;//处理v-on=’{}'到vnode data上
target._d = bindDynamicKeys;//处理动态属性名
target._p = prependModifier;//处理修饰符
}
/* */
//创建一个包含渲染要素的函数
function FunctionalRenderContext (
data,//组件的数据
props,//父组件传递过来的数据
children,//引用该组件时定义的子节点
parent,
Ctor//组件的构造对象(Vue.extend()里的那个Sub函数)
) {
var this$1 = this;
var options = Ctor.options;
// 确保functional components中的createElement函数获得唯一的上下文
var contextVm;
if (hasOwn(parent, '_uid')) {
contextVm = Object.create(parent);
contextVm._original = parent;
} else {
//确保能够获得对真实上下文实例的保留
contextVm = parent;
parent = parent._original;
}
var isCompiled = isTrue(options._compiled);
var needNormalization = !isCompiled;
this.data = data;
this.props = props;
this.children = children;
this.parent = parent;
this.listeners = data.on || emptyObject;
this.injections = resolveInject(options.inject, parent);
this.slots = function () {
if (!this$1.$slots) {
normalizeScopedSlots(
data.scopedSlots,
this$1.$slots = resolveSlots(children, parent)
);
}
return this$1.$slots
};
Object.defineProperty(this, 'scopedSlots', ({
enumerable: true,
get: function get () {
return normalizeScopedSlots(data.scopedSlots, this.slots())
}
}));
// 对已编译函数模板的支持
if (isCompiled) {
// 公开renderStatic()的$options
this.$options = options;
// 预解析renderSlot()的插槽
this.$slots = this.slots();
this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots);
}
if (options._scopeId) {
this._c = function (a, b, c, d) {
var vnode = createElement(contextVm, a, b, c, d, needNormalization);
if (vnode && !Array.isArray(vnode)) {
vnode.fnScopeId = options._scopeId;
vnode.fnContext = parent;
}
return vnode
};
} else {
this._c = function (a, b, c, d) { return createElement(contextVm, a, b, c, d, needNormalization); };
}
}
//渲染辅助函数
installRenderHelpers(FunctionalRenderContext.prototype);
//函数式组件的实现
function createFunctionalComponent (
Ctor,//Ctro:组件的构造对象(Vue.extend()里的那个Sub函数)
propsData, //propsData:父组件传递过来的数据(还未验证)
data,//data:组件的数据
contextVm, //contextVm:Vue实例
children //children:引用该组件时定义的子节点
) {
var options = Ctor.options;
var props = {};
var propOptions = options.props;
//如果propOptions非空(父组件向当前组件传入了信息),则遍历propOptions
if (isDef(propOptions)) {
for (var key in propOptions) {
// 调用validateProp()依次进行检验
props[key] = validateProp(key, propOptions, propsData || emptyObject);
}
} else {
if (isDef(data.attrs)) { mergeProps(props, data.attrs); }
if (isDef(data.props)) { mergeProps(props, data.props); }
}
//创建一个函数的上下文
var renderContext = new FunctionalRenderContext(
data,
props,
children,
contextVm,
Ctor
);
//执行render函数,参数1为createElement,参数2为renderContext,也就是我们在组件内定义的render函数
var vnode = options.render.call(null, renderContext._c, renderContext);
if (vnode instanceof VNode) {
// 为了避免复用节点,fnContext 导致命名槽点不匹配的情况,
// 直接在设置 fnContext 之前克隆节点,最后返回克隆好的 vnode
return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
} else if (Array.isArray(vnode)) {
var vnodes = normalizeChildren(vnode) || [];
var res = new Array(vnodes.length);
for (var i = 0; i < vnodes.length; i++) {
res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext);
}
return res
}
}
// 为了避免复用节点,fnContext 导致命名槽点不匹配的情况,
// 直接在设置 fnContext 之前克隆节点,最后返回克隆好的 vnode
function cloneAndMarkFunctionalResult (vnode, data, contextVm, options, renderContext) {
// 在设置fnContext之前克隆节点,否则,如果重复使用该节点
// (例如,它来自缓存的正常插槽),fnContext将导致不应匹配的命名插槽。
var clone = cloneVNode(vnode);
clone.fnContext = contextVm;
clone.fnOptions = options;
{
(clone.devtoolsMeta = clone.devtoolsMeta || {}).renderContext = renderContext;
}
if (data.slot) {
(clone.data || (clone.data = {})).slot = data.slot;
}
return clone
}
// 将包含 VNode prop 的多个对象合并为一个单独的对象;
function mergeProps (to, from) {
for (var key in from) {
to[camelize(key)] = from[key];
}
}
//component初始化和更新的方法,
// 是组件初始化的时候实现的几个钩子函数,分别有 init、prepatch、insert、destroy
var componentVNodeHooks = {
// 当 vnode 为 keep-alive 组件时、存在实例且没被销毁,为了防止组件流动,
// 直接执行了 prepatch。否则直接通过执行 createComponentInstanceForVnode
// 创建一个 Component 类型的 vnode 实例,并进行 $mount 操作
init: function init (vnode, hydrating) {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
var mountedNode = vnode; // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode);
} else {
//根据Vnode生成VueComponent实例
var child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
//将VueComponent实例挂载到dom节点上,本文是挂载到 节点
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
},
// 将已有组件更新成最新的 vnode 上的数据
prepatch: function prepatch (oldVnode, vnode) {
var options = vnode.componentOptions;
var child = vnode.componentInstance = oldVnode.componentInstance;
updateChildComponent(
child,
options.propsData, // 更新props
options.listeners, // 更新listeners
vnode, // 创建父节点
options.children //创建子节点
);
},
//insert钩子函数
insert: function insert (vnode) {
var context = vnode.context;
var componentInstance = vnode.componentInstance;
//判断组件实例是否已经被mounted,
if (!componentInstance._isMounted) {
// 直接将componentInstance作为参数执行mounted钩子函数
componentInstance._isMounted = true;
callHook(componentInstance, 'mounted');
}
//如果组件为kepp-alive内置组件
if (vnode.data.keepAlive) {
//如果组件已经mounted
if (context._isMounted) {
// 为了防止 keep-alive 子组件更新触发 activated 钩子函数,
// 直接就放弃了 walking tree 的更新机制,而是直接将组件实例 componentInstance
// 丢到 activatedChildren 这个数组中
queueActivatedComponent(componentInstance);
} else {
// 否则直接出发activated钩子函数进行mounted
activateChildComponent(componentInstance, true);
}
}
},
// 组件销毁函数
destroy: function destroy (vnode) {
var componentInstance = vnode.componentInstance;
if (!componentInstance._isDestroyed) {
// 如果不是 keep-alive 组件,直接执行 $destory 销毁组件实例,
if (!vnode.data.keepAlive) {
componentInstance.$destroy();
// 否则触发 deactivated 钩子函数进行销毁
} else {
deactivateChildComponent(componentInstance, true /* direct */);
}
}
}
};
var hooksToMerge = Object.keys(componentVNodeHooks);
function createComponent (
Ctor,
data,
context,
children,
tag
) {
if (isUndef(Ctor)) {
return
}
var baseCtor = context.$options._base;
// 普通选项对象:将其转换为构造函数
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
// 如果在此阶段不是构造函数或异步组件工厂则提示
if (typeof Ctor !== 'function') {
{
warn(("Invalid Component definition: " + (String(Ctor))), context);
}
return
}
// 异步组件
var asyncFactory;
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor;
// 处理了 3 种异步组件的创建方式
Ctor = resolveAsyncComponent(asyncFactory, baseCtor);
if (Ctor === undefined) {
// 异步组件 patch,创建一个注释节点作为占位符
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {};
// 解析constructor上的options属性
resolveConstructorOptions(Ctor);
//将组件v-modal数据转换为props和events
if (isDef(data.model)) {
transformModel(Ctor.options, data);
}
//提取props
var propsData = extractPropsFromVNodeData(data, Ctor, tag);
//创建虚拟dom组件
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// 提取侦听器,因为这些侦听器需要作为子组件侦听器而不是DOM侦听器
var listeners = data.on;
// 替换为带有.native修饰符的侦听器,以便在父组件修补程序期间对其进行处理
data.on = data.nativeOn;
if (isTrue(Ctor.options.abstract)) {
// 抽象组件只保留props、侦听器和插槽
var slot = data.slot;
data = {};
if (slot) {
data.slot = slot;
}
}
// 在占位符节点上安装组件管理挂钩
installComponentHooks(data);
// 返回一个占位符节点
var name = Ctor.options.name || tag;
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory
);
return vnode
}
function createComponentInstanceForVnode (
// 当前组件的vnode
vnode,
// 当前的vue实例 就是div#app,也就是当前组件的父vue实例
parent
) {
// 增加 component 特有options
var options = {
_isComponent: true,
_parentVnode: vnode,// 这里就是站位父VNode,也就是app.vue的占位符
parent: parent// vue实例
};
// 检查内联模板渲染函数
var inlineTemplate = vnode.data.inlineTemplate;
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render;
options.staticRenderFns = inlineTemplate.staticRenderFns;
}
return new vnode.componentOptions.Ctor(options)
}
// 安装组件钩子函数,等待patch过程时去执行
function installComponentHooks (data) {
var hooks = data.hook || (data.hook = {});
// 遍历hooksToMerge,不断向data.hook插入componentVNodeHooks对象中对应的钩子函数
for (var i = 0; i < hooksToMerge.length; i++) {
var key = hooksToMerge[i];
var existing = hooks[key];
var toMerge = componentVNodeHooks[key];
if (existing !== toMerge && !(existing && existing._merged)) {
hooks[key] = existing ? mergeHook$1(toMerge, existing) : toMerge;
}
}
}
function mergeHook$1 (f1, f2) {
var merged = function (a, b) {
f1(a, b);
f2(a, b);
};
merged._merged = true;
return merged
}
// 将组件v-modal(值和回调)转换为prop和event。
function transformModel (options, data) {
// 默认prop是value
var prop = (options.model && options.model.prop) || 'value';
// 默认event是Input
var event = (options.model && options.model.event) || 'input';
// 保存到props属性中
(data.attrs || (data.attrs = {}))[prop] = data.model.value;
// 将input事件添加到on对象中
var on = data.on || (data.on = {});
var existing = on[event];
var callback = data.model.callback;
if (isDef(existing)) {
if (
Array.isArray(existing)
? existing.indexOf(callback) === -1
: existing !== callback
) {
on[event] = [callback].concat(existing);
}
} else {
on[event] = callback;
}
}
/* */
var SIMPLE_NORMALIZE = 1;
var ALWAYS_NORMALIZE = 2;
// 包装器函数,用于提供更灵活的接口
function createElement (
context,
tag,
data,
children,
normalizationType,
alwaysNormalize
) {
// 首先检测data的类型,通过判断data是不是数组,以及是不是基本类型,来判断data是否传入
// 如果传入,则将所有的参数向前赋值
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children;
children = data;
data = undefined;
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE;
}
// 首先判断data是不是响应式的,vnode中的data不能是响应式的。如果是,则Vue抛出警告
return _createElement(context, tag, data, children, normalizationType)
}
function _createElement (
context,//context表示vnode上上线文环境
tag,
data,//vnode数据
children,//当前vnode子节点
normalizationType
) {
// 首先判断data是不是响应式的,vnode中的data不能是响应式的。如果是,则Vue抛出警告
if (isDef(data) && isDef((data).__ob__)) {
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()
}
//v-bind中的对象语法
if (isDef(data) && isDef(data.is)) {
tag = data.is;
}
if (!tag) {
// 如果是组件:则设置为falsy值
return createEmptyVNode()
}
// 如果data.key不属于基本类型,则发出警告
if (isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
{
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
);
}
}
// 支持单功能子项作为默认作用域插槽
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {};
data.scopedSlots = { default: children[0] };
children.length = 0;
}
// 当alwaysNormalize等于2的时候,调用normalizeChildren去处理children类数组
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children);
// 当alwaysNormalize等于1的时候,调用调用normalizeChildren去处理children类数组
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children);
}
var vnode, ns;
// 判断tag的类型,如果是string就创建普通dom
if (typeof tag === 'string') {
var Ctor;
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
if (config.isReservedTag(tag)) {
// 如果存在data.nativeOn并且data.tag不等于component时,发出警告
if (isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {
warn(
("The .native modifier for v-on is only valid on components but it was used on <" + tag + ">."),
context
);
}
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
);
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
//创建组件
vnode = createComponent(Ctor, data, context, children, tag);
} else {
// 未知或未列出的命名空间元素在运行时检查,因为当其父元素规范化子元素时,可能会为其分配命名空间
vnode = new VNode(
tag, data, children,
undefined, undefined, context
);
}
} else {
// 如果不是string就会调用createComponent创建组件
vnode = createComponent(tag, data, context, children);
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
//如果存在vnode和ns,则将ns赋值给vnode.ns
if (isDef(ns)) { applyNS(vnode, ns); }
if (isDef(data)) { registerDeepBindings(data); }
return vnode
} else {
return createEmptyVNode()
}
}
//通过递归的方法把ns赋值给vnode.ns
function applyNS (vnode, ns, force) {
vnode.ns = ns;
// 如果vnode.tag等于foreignObject则ns赋值为undefined
if (vnode.tag === 'foreignObject') {
//在foreignObject中使用默认命名空间
ns = undefined;
force = true;
}
//如果vnode有子节点则进行递归循环
if (isDef(vnode.children)) {
for (var i = 0, l = vnode.children.length; i < l; i++) {
var child = vnode.children[i];
if (isDef(child.tag) && (
isUndef(child.ns) || (isTrue(force) && child.tag !== 'svg'))) {
applyNS(child, ns, force);
}
}
}
}
// 确保父级在深度绑定的样式和类在插槽节点上使用
function registerDeepBindings (data) {
if (isObject(data.style)) {
//如果有data.style,则调用traverse进行深度监听
traverse(data.style);
}
//如果有data.class,则调用traverse进行深度监听
if (isObject(data.class)) {
traverse(data.class);
}
}
function initRender (vm) {
vm._vnode = null; // 子树的根
vm._staticTrees = null; // v-once缓存树
var options = vm.$options;
var parentVnode = vm.$vnode = options._parentVnode; // 父树中的占位符节点
var renderContext = parentVnode && parentVnode.context;
vm.$slots = resolveSlots(options._renderChildren, renderContext);
vm.$scopedSlots = emptyObject;
//将createElement fn绑定到此实例
//这样我们就可以在其中获得适当的渲染上下文。
//参数顺序:标记、数据、子项、规范化类型、alwaysNormalize
//内部版本由从模板编译的渲染函数使用
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
// 规范化始终应用于公共版本,用于用户编写的渲染函数。
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
// $attrs&$listeners已公开,以便更轻松地进行临时创建。它们需要是反应式的,以便使用它们的HOC始终得到更新
var parentData = parentVnode && parentVnode.data;
{
// Vue的data监听,也是通过defineReactive$$1方法
defineReactive$$1(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
!isUpdatingChildComponent && warn("$attrs is readonly.", vm);
}, true);
defineReactive$$1(vm, '$listeners', options._parentListeners || emptyObject, function () {
!isUpdatingChildComponent && warn("$listeners is readonly.", vm);
}, true);
}
}
var currentRenderingInstance = null;
function renderMixin (Vue) {
// 里面是一些渲染时候的帮助方法,全部挂载到Vue.prototype上,比如创建空节点、创建文本节点、toNumber、toString等等
installRenderHelpers(Vue.prototype);
// 使用异步函数来执行
Vue.prototype.$nextTick = function (fn) {
return nextTick(fn, this)
};
// 调用render函数
Vue.prototype._render = function () {
var vm = this;
var ref = vm.$options;
var render = ref.render;
var _parentVnode = ref._parentVnode;
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
);
}
// 设置父vnode。这允许渲染函数具有访问权限添加到占位符节点上的数据。
vm.$vnode = _parentVnode;
// render self
var vnode;
try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm;
vnode = render.call(vm._renderProxy, vm.$createElement);
} catch (e) {
handleError(e, vm, "render");
// 返回错误渲染结果,或上一个vnode,以防止渲染错误导致空白组件
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;
}
} finally {
currentRenderingInstance = null;
}
// 如果返回的数组只包含一个节点
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0];
}
// 如果渲染函数出错,则返回空vnode
if (!(vnode instanceof VNode)) {
if (Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
);
}
//给vnode赋值一个空节点
vnode = createEmptyVNode();
}
//设置父节点
vnode.parent = _parentVnode;
return vnode
};
}
/* 是为了保证能找到异步组件上定义的组件对象而定义的函数 */
function ensureCtor (comp, base) {
// 如果发现它是普通对象,则直接通过 Vue.extend 将其转换成组件的构造函数
if (
comp.__esModule ||
(hasSymbol && comp[Symbol.toStringTag] === 'Module')
) {
comp = comp.default;
}
return isObject(comp)
? base.extend(comp)
: comp
}
// 返回呈现的异步组件的占位符节点作为注释节点,但保留节点的所有原始信息,这些信息将用于异步服务器渲染和同步
function createAsyncPlaceholder (
factory,
data,
context,
children,
tag
) {
var node = createEmptyVNode();
node.asyncFactory = factory;
node.asyncMeta = { data: data, context: context, children: children, tag: tag };
return node
}
function resolveAsyncComponent (
factory,//factory:异步组件的函数
baseCtor
) {
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
// 工厂函数异步组件第二次执行这里时会返回factory.resolved
if (isDef(factory.resolved)) {
return factory.resolved
}
var owner = currentRenderingInstance;
if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
// already pending
factory.owners.push(owner);
}
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
if (owner && !isDef(factory.owners)) {
var owners = factory.owners = [owner];
var sync = true;
var timerLoading = null;
var timerTimeout = null;
(owner).$on('hook:destroyed', function () { return remove(owners, owner); });
// 遍历contexts里的所有元素,依次调用该元素的$forceUpdate()方法 该方法会强制渲染一次
var forceRender = function (renderCompleted) {
for (var i = 0, l = owners.length; i < l; i++) {
(owners[i]).$forceUpdate();
}
if (renderCompleted) {
owners.length = 0;
if (timerLoading !== null) {
clearTimeout(timerLoading);
timerLoading = null;
}
if (timerTimeout !== null) {
clearTimeout(timerTimeout);
timerTimeout = null;
}
}
};
//定义一个resolve函数
var resolve = once(function (res) {
// 缓存解析
factory.resolved = ensureCtor(res, baseCtor);
// 当这不是同步解析时调用回调
if (!sync) {
forceRender(true);
} else {
owners.length = 0;
}
});
//定义一个reject函数
var reject = once(function (reason) {
warn(
"Failed to resolve async component: " + (String(factory)) +
(reason ? ("\nReason: " + reason) : '')
);
if (isDef(factory.errorComp)) {
factory.error = true;
forceRender(true);
}
});
//执行factory()函数
var res = factory(resolve, reject);
if (isObject(res)) {
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject);
}
} else if (isPromise(res.component)) {
res.component.then(resolve, reject);
if (isDef(res.error)) {
factory.errorComp = ensureCtor(res.error, baseCtor);
}
if (isDef(res.loading)) {
factory.loadingComp = ensureCtor(res.loading, baseCtor);
if (res.delay === 0) {
factory.loading = true;
} else {
timerLoading = setTimeout(function () {
timerLoading = null;
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true;
forceRender(false);
}
}, res.delay || 200);
}
}
if (isDef(res.timeout)) {
timerTimeout = setTimeout(function () {
timerTimeout = null;
if (isUndef(factory.resolved)) {
reject(
"timeout (" + (res.timeout) + "ms)"
);
}
}, res.timeout);
}
}
}
sync = false;
// 在同步解决的情况下返回
return factory.loading
? factory.loadingComp
: factory.resolved
}
}
/*获取第一个组件的子节点 */
function getFirstComponentChild (children) {
//循环子节点,判断子节点是否位异步占位符
if (Array.isArray(children)) {
for (var i = 0; i < children.length; i++) {
var c = children[i];
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
return c
}
}
}
}
//初始化事件
function initEvents (vm) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
//初始化父附加事件
var listeners = vm.$options._parentListeners;
if (listeners) {
//修改组件的监听器
updateComponentListeners(vm, listeners);
}
}
var target;
//添加监听事件
function add (event, fn) {
target.$on(event, fn);
}
//移出自定义事件监听器
function remove$1 (event, fn) {
target.$off(event, fn);
}
// 在执行完回调之后,移除事件绑定
function createOnceHandler (event, fn) {
var _target = target;
return function onceHandler () {
var res = fn.apply(null, arguments);
if (res !== null) {
_target.$off(event, onceHandler);
}
}
}
//更新组件的监听器
function updateComponentListeners (
vm,
listeners,
oldListeners
) {
target = vm;
// 调用updateListeners来修改监听配置
updateListeners(listeners, oldListeners || {}, add, remove$1, createOnceHandler, vm);
target = undefined;
}
function eventsMixin (Vue) {
var hookRE = /^hook:/;
// 监听事件
Vue.prototype.$on = function (event, fn) {
var vm = this;
//当传入的监听事件为数组,则循环遍历调用$on,否则将监听事件和回调函数添加到事件处理中心_events对象中
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn);
}
} else {
// 之前已经有监听event事件,则将此次监听的回调函数添加到其数组中,否则创建一个新数组并添加fn
(vm._events[event] || (vm._events[event] = [])).push(fn);
if (hookRE.test(event)) {
vm._hasHookEvent = true;
}
}
return vm
};
//监听事件,只监听1次, Vue中的事件机制,Vue中的事件机制本身就是一个订阅-发布模式的实现
Vue.prototype.$once = function (event, fn) {
var vm = this;
// 定义监听事件的回调函数
function on () {
// 从事件中心移除监听事件的回调函数
vm.$off(event, on);
// 执行回调函数
fn.apply(vm, arguments);
}
// 这个赋值是在$off方法里会用到的
// 比如我们调用了vm.$off(fn)来移除fn回调函数,然而我们在调用$once的时候,实际执行的是vm.$on(event, on)
// 所以在event的回调函数数组里添加的是on函数,这个时候要移除fn,我们无法在回调函数数组里面找到fn函数移除,只能找到on函数
// 我们可以通过on.fn === fn来判断这种情况,并在回调函数数组里移除on函数
on.fn = fn;
// 通过$on方法注册事件,$once最终调用的是$on,并且回调函数是on
vm.$on(event, on);
return vm
};
// 移除自定义事件监听器。
// 如果没有提供参数,则移除所有的事件监听器;
// 如果只提供了事件,则移除该事件所有的监听器;
// 如果同时提供了事件与回调,则只移除这个回调的监听器。
Vue.prototype.$off = function (event, fn) {
var vm = this;
//调用this.$off()没有传参数,则清空事件处理中心缓存的事件及其回调
if (!arguments.length) {
vm._events = Object.create(null);
return vm
}
//当event为数组,则循环遍历调用$off,否则将监听事件和回调函数添加到事件处理中心_events对象中
if (Array.isArray(event)) {
for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
vm.$off(event[i$1], fn);
}
return vm
}
// 获取当前event里所有的回调函数
var cbs = vm._events[event];
// 如果不存在回调函数,则直接返回,因为没有可以移除监听的内容
if (!cbs) {
return vm
}
// 如果没有指定要移除的回调函数,则移除该事件下所有的回调函数
if (!fn) {
vm._events[event] = null;
return vm
}
// 指定了要移除的回调函数
var cb;
var i = cbs.length;
while (i--) {
cb = cbs[i];
// 在事件对应的回调函数数组里面找出要移除的回调函数,并从数组里移除
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1);
break
}
}
return vm
};
// 触发事件
Vue.prototype.$emit = function (event) {
var vm = this;
{
var lowerCaseEvent = event.toLowerCase();
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
"Event \"" + lowerCaseEvent + "\" is emitted in component " +
(formatComponentName(vm)) + " but the handler is registered for \"" + event + "\". " +
"Note that HTML attributes are case-insensitive and you cannot use " +
"v-on to listen to camelCase events when using in-DOM templates. " +
"You should probably use \"" + (hyphenate(event)) + "\" instead of \"" + event + "\"."
);
}
}
// 触发事件对应的回调函数列表
var cbs = vm._events[event];
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
// $emit方法可以传参,这些参数会在调用回调函数的时候传进去
var args = toArray(arguments, 1);
var info = "event handler for \"" + event + "\"";
// 遍历回调函数列表,调用每个回调函数
for (var i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info);
}
}
return vm
};
}