雖然vue3已經(jīng)出來(lái)很久了,但我覺(jué)得vue.js的源碼還是非常值得去學(xué)習(xí)一下。vue.js里面封裝的很多工具類(lèi)在我們平時(shí)工作項(xiàng)目中也會(huì)經(jīng)常用到小泉。所以我近期會(huì)對(duì)vue.js的源碼進(jìn)行解讀诫尽,分享值得去學(xué)習(xí)的代碼片段,這篇文章將會(huì)持續(xù)更新肩豁。
一、2400~4000代碼有哪些內(nèi)容辫呻?:
1.children 的規(guī)范化:normalizeArrayChildren
2.組件實(shí)例化:initInjections
3.slot插槽函數(shù):resolveSlots清钥,normalizeScopedSlots,normalizeScopedSlot放闺,proxyNormalSlot祟昭,renderSlot
4.Vue 的各類(lèi)渲染方法--輔助函數(shù):
markOnce;// 標(biāo)記v-once
toNumber;// 轉(zhuǎn)換成Number類(lèi)型
toString;//轉(zhuǎn)換成字符串
renderList;//生成列表VNode
renderSlot;//生成解析slot節(jié)點(diǎn)
looseEqual;
looseIndexOf;
renderStatic;//生成靜態(tài)元素
resolveFilter;// 獲取過(guò)濾器
checkKeyCodes;//檢查鍵盤(pán)事件keycode
bindObjectProps;//綁定對(duì)象屬性
createTextVNode;//創(chuàng)建文本VNod
createEmptyVNode;//創(chuàng)建空節(jié)點(diǎn)VNode
resolveScopedSlots;//獲取作用域插槽
bindObjectListeners;//處理v-on=’{}'到vnode data上
bindDynamicKeys;//處理動(dòng)態(tài)屬性名
prependModifier;//處理修飾符
二.2400~4000行代碼的重點(diǎn):
1.vue的事件機(jī)制
①.監(jiān)聽(tīng)事件:$on
②.監(jiān)聽(tīng)事件,只監(jiān)聽(tīng)1次:$once
③.移除自定義事件監(jiān)聽(tīng)器:$off
④.觸發(fā)事件: $emit
2.函數(shù)式組件的實(shí)現(xiàn)
createFunctionalComponent
3.組件的渲染和更新過(guò)程
componentVNodeHooks
在組件初始化的時(shí)候?qū)崿F(xiàn)init怖侦、prepatch篡悟、insert、destroy鉤子函數(shù)
三匾寝、2400~4000行的代碼解讀:
//children 的規(guī)范化搬葬,simpleNormalizeChildren和normalizeChildren都是用來(lái)把children由樹(shù)狀結(jié)構(gòu)變成一維數(shù)組
// 模板編譯器試圖通過(guò)在編譯時(shí)靜態(tài)分析模板來(lái)最小化規(guī)范化的需要
// 對(duì)于純HTML標(biāo)記,可以完全跳過(guò)規(guī)范化艳悔,因?yàn)樯傻某尸F(xiàn)函數(shù)保證返回Array<VNode>急凰。有兩種情況需要額外的規(guī)范化:
// 當(dāng)子級(jí)包含組件時(shí)-因?yàn)楣δ芙M件可能返回?cái)?shù)組而不是單個(gè)根。在這種情況下猜年,只需要一個(gè)簡(jiǎn)單的規(guī)范化—如果任何子對(duì)象是數(shù)組抡锈,
// 我們就用Array.prototype.concat將整個(gè)對(duì)象展平。它保證只有1級(jí)深度乔外,因?yàn)楣δ芙M件已經(jīng)規(guī)范化了它們自己的子級(jí)
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.當(dāng)子級(jí)包含總是生成嵌套數(shù)組的構(gòu)造時(shí)床三,例如<template>、<slot>杨幼、v-for撇簿,或者當(dāng)子級(jí)由用戶(hù)提供手寫(xiě)的呈現(xiàn)函數(shù)/JSX時(shí)聂渊。
// 在這種情況下,需要完全正乘奶保化汉嗽,以滿(mǎn)足所有可能類(lèi)型的兒童價(jià)值觀。
function normalizeChildren (children) {
return isPrimitive(children)
? [createTextVNode(children)]
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined
}
// node節(jié)點(diǎn)的判斷條件
function isTextNode (node) {
return isDef(node) && isDef(node.text) && isFalse(node.isComment)
}
//children 的規(guī)范化莲组,normalizeArrayChildren接收 2 個(gè)參數(shù)诊胞,children 表示要規(guī)范的子節(jié)點(diǎn),nestedIndex 表示嵌套的索引
function normalizeArrayChildren (children, nestedIndex) {
var res = [];
var i, c, lastIndex, last;
//遍歷children,
for (i = 0; i < children.length; i++) {
//將單個(gè)節(jié)點(diǎn)賦值給c
c = children[i];
//判斷c的類(lèi)型锹杈,如果是一個(gè)數(shù)組類(lèi)型撵孤,則遞歸調(diào)用 normalizeArrayChildren;
//否則通過(guò) createTextVNode 方法轉(zhuǎn)換成 VNode 類(lèi)型;
if (isUndef(c) || typeof c === 'boolean') { continue }
lastIndex = res.length - 1;
last = res[lastIndex];
//如果是一個(gè)數(shù)組類(lèi)型竭望,則遞歸調(diào)用 normalizeArrayChildren
if (Array.isArray(c)) {
if (c.length > 0) {
// 如果 children 是一個(gè)列表并且列表還存在嵌套的情況邪码,則根據(jù) 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)) {
//通過(guò) createTextVNode 方法轉(zhuǎn)換成 VNode 類(lèi)型
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賦值到當(dāng)前實(shí)例上 */
function initProvide (vm) {
//如果provide存在,當(dāng)它是函數(shù)時(shí)執(zhí)行該返回,否則直接將provide保存到Vue實(shí)例的_provided屬性上
var provide = vm.$options.provide;
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide;
}
}
//組件實(shí)例化咬清,初始化Inject參數(shù)闭专, initInjections在初始化data/props之前被調(diào)用,主要作用是初始化vue實(shí)例的inject
function initInjections (vm) {
//遍歷祖先節(jié)點(diǎn)旧烧,獲取對(duì)應(yīng)的inject
var result = resolveInject(vm.$options.inject, vm);
if (result) {
// toggleObserving是vue內(nèi)部對(duì)邏輯的一個(gè)優(yōu)化,就是禁止掉根組件 props的依賴(lài)收集
toggleObserving(false);
Object.keys(result).forEach(function (key) {
//將key編程響應(yīng)式影钉,這樣就可以訪問(wèn)該元素
{
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指當(dāng)前組件的實(shí)例
function resolveInject (inject, vm) {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
var result = Object.create(null);
//如果有符號(hào)類(lèi)型掘剪,調(diào)用Reflect.ownKeys()返回所有的key平委,再調(diào)用filter
var keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject);
//獲取所有的key,此時(shí)keys就是個(gè)字符串?dāng)?shù)組
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這個(gè)屬性夺谁,則將值保存到result[key]中
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey];
break
}
// 否則將source賦值給父Vue實(shí)例廉赔,直到找到對(duì)應(yīng)的providekey為止
source = source.$parent;
}
// 如果最后source不存在,即沒(méi)有從當(dāng)前實(shí)例或祖先實(shí)例的_provide找到privideKey這個(gè)key
if (!source) {
// 如果有定義defult,則使用默認(rèn)值
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轉(zhuǎn)化成一個(gè)slots對(duì)象匾鸥,處理組件slot蜡塌,返回slot插槽對(duì)象
* children指占位符Vnode里的內(nèi)容
* context指占位符Vnode所在的vue實(shí)例
*/
function resolveSlots (
children,
context
) {
// 判斷是否有children,即是否有插槽VNode
if (!children || !children.length) {
return {}
}
var slots = {};
// 遍歷每一個(gè)子節(jié)點(diǎn)
for (var i = 0, l = children.length; i < l; i++) {
var child = children[i];
// data為VNodeData勿负,保存父組件傳遞到子組件的props以及attrs等
var data = child.data;
//移出slot馏艾,刪除該節(jié)點(diǎn)attrs的slot
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot;
}
// 判斷是否為具名插槽,如果為具名插槽笆环,還需要 子組件 / 函數(shù)子組件 渲染上下文一致
// 當(dāng)需要向子組件的子組件傳遞具名插槽時(shí)攒至,不會(huì)保持插槽的名字
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數(shù)組
(slots.default || (slots.default = [])).push(child);
}
}
// 忽略?xún)H僅包含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函數(shù)的核心就是返回res對(duì)象,其key為slotTarget,value為fn */
//slots: 某節(jié)點(diǎn) data 屬性上 scopedSlots
//normalSlots: 當(dāng)前節(jié)點(diǎn)下的普通插槽
//prevSlots 當(dāng)前節(jié)點(diǎn)下的特殊插槽
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中沒(méi)有普通插槽
!hasNormalSlots &&
//prevSlots中沒(méi)有普通插槽
!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]);
}
}
}
// 對(duì)普通插槽進(jìn)行遍歷库菲,將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 內(nèi)部對(duì) Object.defineProperty 封裝好的 def() 方法進(jìn)行賦值的
def(res, '$stable', isStable);
def(res, '$key', key);
def(res, '$hasNormal', hasNormalSlots);
return res
}
//將scopeSlots對(duì)應(yīng)屬性和方法掛載到scopeSlots账忘,生成閉包,返回一個(gè)名為normalized的函數(shù),$scopedSlots對(duì)象中的值就是此函數(shù)
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]; }
}
/* */
/**
* 用于呈現(xiàn)v-for列表的運(yùn)行時(shí)幫助程序
*/
function renderList (
val,
render
) {
var ret, i, l, keys, key;
//如果val為數(shù)組鳖擒,則遍歷val
if (Array.isArray(val) || typeof val === 'string') {
ret = new Array(val.length);
// 調(diào)用傳入的函數(shù)溉浙,把值傳入,數(shù)組保存結(jié)果
for (i = 0, l = val.length; i < l; i++) {
ret[i] = render(val[i], i);
}
//如果val為數(shù)字類(lèi)型
} else if (typeof val === 'number') {
ret = new Array(val);
// 調(diào)用傳入的函數(shù)蒋荚,把值傳入戳稽,數(shù)組保存結(jié)果
for (i = 0; i < val; i++) {
ret[i] = render(i + 1, i);
}
//如果val為object類(lèi)型,則遍歷對(duì)象
} else if (isObject(val)) {
if (hasSymbol && val[Symbol.iterator]) {
ret = [];
var iterator = val[Symbol.iterator]();
var result = iterator.next();
// 調(diào)用傳入的函數(shù),把值傳入期升,數(shù)組保存結(jié)果
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
}
/* */
// 調(diào)用renderSlot用函數(shù)的返回值進(jìn)行渲染
// renderSlot函數(shù)會(huì)根據(jù)插槽名字找到對(duì)應(yīng)的作用域Slot包裝成的函數(shù)惊奇,
// 然后執(zhí)行它,把子組件內(nèi)的數(shù)據(jù){ child:child }傳進(jìn)去
function renderSlot (
name,//插槽名
fallbackRender,//插槽默認(rèn)內(nèi)容生成的 vnode 數(shù)組
props,// props 對(duì)象
bindObject //v-bind 綁定對(duì)象
) {
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
}
}
/* */
/**
*找到我們寫(xiě)的過(guò)濾器播赁,并將參數(shù)傳入進(jìn)去
*/
function resolveFilter (id) {
// resolveAsset用于獲取資源颂郎,也就是獲取組件的構(gòu)造函數(shù)
return resolveAsset(this.$options, 'filters', id, true) || identity
}
/* 檢查按下的鍵,是否和配置的鍵值對(duì)匹配 */
function isKeyNotMatch (expect, actual) {
if (Array.isArray(expect)) {
return expect.indexOf(actual) === -1
} else {
return expect !== actual
}
}
/**
* 用于檢查config.prototype中的鍵代碼的運(yùn)行時(shí)幫助程序容为,以Vue.prototype的形式公開(kāi)
*/
function checkKeyCodes (
eventKeyCode,
key,
builtInKeyCode,
eventKeyName,
builtInKeyName
) {
// 比如 key 傳入的是自定義名字 aaaa
// keyCode 從Vue 定義的 keyNames 獲取 aaaa 的實(shí)際數(shù)字
// keyName 從 Vue 定義的 keyCode 獲取 aaaa 的別名
// 并且以用戶(hù)定義的為準(zhǔn)乓序,可以覆蓋Vue 內(nèi)部定義的
var mappedKeyCode = config.keyCodes[key] || builtInKeyCode;
// 該鍵只在 Vue 內(nèi)部定義的 keyCode 中
if (builtInKeyName && eventKeyName && !config.keyCodes[key]) {
return isKeyNotMatch(builtInKeyName, eventKeyName)
// 該鍵只在 用戶(hù)自定義配置的 keyCode 中
} else if (mappedKeyCode) {
return isKeyNotMatch(mappedKeyCode, eventKeyCode)
//原始鍵名
} else if (eventKeyName) {
return hyphenate(eventKeyName) !== key
}
return eventKeyCode === undefined
}
/**
* 用于將v-bind=“object”合并到VNode數(shù)據(jù)中的運(yùn)行時(shí)幫助程序
*/
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
}
/* */
/**
* 生成靜態(tài)元素
*/
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
}
/**
* 標(biāo)記v-once
*/
function markOnce (
tree,
index,
key
) {
markStatic(tree, ("__once__" + index + (key ? ("_" + key) : "")), true);
return tree
}
// 標(biāo)記靜態(tài)元素
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);
}
}
//標(biāo)記靜態(tài)節(jié)點(diǎn)
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
}
/*//處理動(dòng)態(tài)屬性名 */
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
}
//處理修飾符
// 幫助程序?qū)⑿薷钠鬟\(yùn)行時(shí)標(biāo)記動(dòng)態(tài)追加到事件名稱(chēng)。
// 請(qǐng)確保僅在值已為字符串時(shí)追加坎背,否則將轉(zhuǎn)換為字符串并導(dǎo)致類(lèi)型檢查丟失替劈。
function prependModifier (value, symbol) {
return typeof value === 'string' ? symbol + value : value
}
/* 安裝渲染工具函數(shù),大多數(shù)服務(wù)于編譯器編譯出來(lái)的代碼 */
function installRenderHelpers (target) {
target._o = markOnce;// 標(biāo)記v-once
target._n = toNumber;// 轉(zhuǎn)換成Number類(lèi)型
target._s = toString;//轉(zhuǎn)換成字符串
target._l = renderList;//生成列表VNode
target._t = renderSlot;//生成解析slot節(jié)點(diǎn)
target._q = looseEqual;
target._i = looseIndexOf;
target._m = renderStatic;//生成靜態(tài)元素
target._f = resolveFilter;// 獲取過(guò)濾器
target._k = checkKeyCodes;//檢查鍵盤(pán)事件keycode
target._b = bindObjectProps;//綁定對(duì)象屬性
target._v = createTextVNode;//創(chuàng)建文本VNod
target._e = createEmptyVNode;//創(chuàng)建空節(jié)點(diǎn)VNode
target._u = resolveScopedSlots;//獲取作用域插槽
target._g = bindObjectListeners;//處理v-on=’{}'到vnode data上
target._d = bindDynamicKeys;//處理動(dòng)態(tài)屬性名
target._p = prependModifier;//處理修飾符
}
/* */
//創(chuàng)建一個(gè)包含渲染要素的函數(shù)
function FunctionalRenderContext (
data,//組件的數(shù)據(jù)
props,//父組件傳遞過(guò)來(lái)的數(shù)據(jù)
children,//引用該組件時(shí)定義的子節(jié)點(diǎn)
parent,
Ctor//組件的構(gòu)造對(duì)象(Vue.extend()里的那個(gè)Sub函數(shù))
) {
var this$1 = this;
var options = Ctor.options;
// 確保functional components中的createElement函數(shù)獲得唯一的上下文
var contextVm;
if (hasOwn(parent, '_uid')) {
contextVm = Object.create(parent);
contextVm._original = parent;
} else {
//確保能夠獲得對(duì)真實(shí)上下文實(shí)例的保留
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())
}
}));
// 對(duì)已編譯函數(shù)模板的支持
if (isCompiled) {
// 公開(kāi)renderStatic()的$options
this.$options = options;
// 預(yù)解析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); };
}
}
//渲染輔助函數(shù)
installRenderHelpers(FunctionalRenderContext.prototype);
//函數(shù)式組件的實(shí)現(xiàn)
function createFunctionalComponent (
Ctor,//Ctro:組件的構(gòu)造對(duì)象(Vue.extend()里的那個(gè)Sub函數(shù))
propsData, //propsData:父組件傳遞過(guò)來(lái)的數(shù)據(jù)(還未驗(yàn)證)
data,//data:組件的數(shù)據(jù)
contextVm, //contextVm:Vue實(shí)例
children //children:引用該組件時(shí)定義的子節(jié)點(diǎn)
) {
var options = Ctor.options;
var props = {};
var propOptions = options.props;
//如果propOptions非空(父組件向當(dāng)前組件傳入了信息),則遍歷propOptions
if (isDef(propOptions)) {
for (var key in propOptions) {
// 調(diào)用validateProp()依次進(jìn)行檢驗(yàn)
props[key] = validateProp(key, propOptions, propsData || emptyObject);
}
} else {
if (isDef(data.attrs)) { mergeProps(props, data.attrs); }
if (isDef(data.props)) { mergeProps(props, data.props); }
}
//創(chuàng)建一個(gè)函數(shù)的上下文
var renderContext = new FunctionalRenderContext(
data,
props,
children,
contextVm,
Ctor
);
//執(zhí)行render函數(shù)得滤,參數(shù)1為createElement陨献,參數(shù)2為renderContext,也就是我們?cè)诮M件內(nèi)定義的render函數(shù)
var vnode = options.render.call(null, renderContext._c, renderContext);
if (vnode instanceof VNode) {
// 為了避免復(fù)用節(jié)點(diǎn),fnContext 導(dǎo)致命名槽點(diǎn)不匹配的情況耿戚,
// 直接在設(shè)置 fnContext 之前克隆節(jié)點(diǎn)湿故,最后返回克隆好的 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
}
}
// 為了避免復(fù)用節(jié)點(diǎn),fnContext 導(dǎo)致命名槽點(diǎn)不匹配的情況膜蛔,
// 直接在設(shè)置 fnContext 之前克隆節(jié)點(diǎn)坛猪,最后返回克隆好的 vnode
function cloneAndMarkFunctionalResult (vnode, data, contextVm, options, renderContext) {
// 在設(shè)置fnContext之前克隆節(jié)點(diǎn),否則皂股,如果重復(fù)使用該節(jié)點(diǎn)
// (例如墅茉,它來(lái)自緩存的正常插槽),fnContext將導(dǎo)致不應(yīng)匹配的命名插槽呜呐。
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 的多個(gè)對(duì)象合并為一個(gè)單獨(dú)的對(duì)象;
function mergeProps (to, from) {
for (var key in from) {
to[camelize(key)] = from[key];
}
}
//component初始化和更新的方法就斤,
// 是組件初始化的時(shí)候?qū)崿F(xiàn)的幾個(gè)鉤子函數(shù),分別有 init蘑辑、prepatch洋机、insert、destroy
var componentVNodeHooks = {
// 當(dāng) vnode 為 keep-alive 組件時(shí)洋魂、存在實(shí)例且沒(méi)被銷(xiāo)毀绷旗,為了防止組件流動(dòng)喜鼓,
// 直接執(zhí)行了 prepatch。否則直接通過(guò)執(zhí)行 createComponentInstanceForVnode
// 創(chuàng)建一個(gè) Component 類(lèi)型的 vnode 實(shí)例衔肢,并進(jìn)行 $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 {
//根據(jù)Vnode生成VueComponent實(shí)例
var child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
//將VueComponent實(shí)例掛載到dom節(jié)點(diǎn)上庄岖,本文是掛載到<my-component></my-component>節(jié)點(diǎn)
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
},
// 將已有組件更新成最新的 vnode 上的數(shù)據(jù)
prepatch: function prepatch (oldVnode, vnode) {
var options = vnode.componentOptions;
var child = vnode.componentInstance = oldVnode.componentInstance;
updateChildComponent(
child,
options.propsData, // 更新props
options.listeners, // 更新listeners
vnode, // 創(chuàng)建父節(jié)點(diǎn)
options.children //創(chuàng)建子節(jié)點(diǎn)
);
},
//insert鉤子函數(shù)
insert: function insert (vnode) {
var context = vnode.context;
var componentInstance = vnode.componentInstance;
//判斷組件實(shí)例是否已經(jīng)被mounted,
if (!componentInstance._isMounted) {
// 直接將componentInstance作為參數(shù)執(zhí)行mounted鉤子函數(shù)
componentInstance._isMounted = true;
callHook(componentInstance, 'mounted');
}
//如果組件為kepp-alive內(nèi)置組件
if (vnode.data.keepAlive) {
//如果組件已經(jīng)mounted
if (context._isMounted) {
// 為了防止 keep-alive 子組件更新觸發(fā) activated 鉤子函數(shù),
// 直接就放棄了 walking tree 的更新機(jī)制角骤,而是直接將組件實(shí)例 componentInstance
// 丟到 activatedChildren 這個(gè)數(shù)組中
queueActivatedComponent(componentInstance);
} else {
// 否則直接出發(fā)activated鉤子函數(shù)進(jìn)行mounted
activateChildComponent(componentInstance, true);
}
}
},
// 組件銷(xiāo)毀函數(shù)
destroy: function destroy (vnode) {
var componentInstance = vnode.componentInstance;
if (!componentInstance._isDestroyed) {
// 如果不是 keep-alive 組件隅忿,直接執(zhí)行 $destory 銷(xiāo)毀組件實(shí)例,
if (!vnode.data.keepAlive) {
componentInstance.$destroy();
// 否則觸發(fā) deactivated 鉤子函數(shù)進(jìn)行銷(xiāo)毀
} 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;
// 普通選項(xiàng)對(duì)象:將其轉(zhuǎn)換為構(gòu)造函數(shù)
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
// 如果在此階段不是構(gòu)造函數(shù)或異步組件工廠則提示
if (typeof Ctor !== 'function') {
{
warn(("Invalid Component definition: " + (String(Ctor))), context);
}
return
}
// 異步組件
var asyncFactory;
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor;
// 處理了 3 種異步組件的創(chuàng)建方式
Ctor = resolveAsyncComponent(asyncFactory, baseCtor);
if (Ctor === undefined) {
// 異步組件 patch邦尊,創(chuàng)建一個(gè)注釋節(jié)點(diǎn)作為占位符
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {};
// 解析constructor上的options屬性
resolveConstructorOptions(Ctor);
//將組件v-modal數(shù)據(jù)轉(zhuǎn)換為props和events
if (isDef(data.model)) {
transformModel(Ctor.options, data);
}
//提取props
var propsData = extractPropsFromVNodeData(data, Ctor, tag);
//創(chuàng)建虛擬dom組件
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// 提取偵聽(tīng)器背桐,因?yàn)檫@些偵聽(tīng)器需要作為子組件偵聽(tīng)器而不是DOM偵聽(tīng)器
var listeners = data.on;
// 替換為帶有.native修飾符的偵聽(tīng)器,以便在父組件修補(bǔ)程序期間對(duì)其進(jìn)行處理
data.on = data.nativeOn;
if (isTrue(Ctor.options.abstract)) {
// 抽象組件只保留props胳赌、偵聽(tīng)器和插槽
var slot = data.slot;
data = {};
if (slot) {
data.slot = slot;
}
}
// 在占位符節(jié)點(diǎn)上安裝組件管理掛鉤
installComponentHooks(data);
// 返回一個(gè)占位符節(jié)點(diǎn)
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 (
// 當(dāng)前組件的vnode
vnode,
// 當(dāng)前的vue實(shí)例 就是div#app牢撼,也就是當(dāng)前組件的父vue實(shí)例
parent
) {
// 增加 component 特有options
var options = {
_isComponent: true,
_parentVnode: vnode,// 這里就是站位父VNode,也就是app.vue的占位符
parent: parent// vue實(shí)例
};
// 檢查內(nèi)聯(lián)模板渲染函數(shù)
var inlineTemplate = vnode.data.inlineTemplate;
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render;
options.staticRenderFns = inlineTemplate.staticRenderFns;
}
return new vnode.componentOptions.Ctor(options)
}
// 安裝組件鉤子函數(shù)疑苫,等待patch過(guò)程時(shí)去執(zhí)行
function installComponentHooks (data) {
var hooks = data.hook || (data.hook = {});
// 遍歷hooksToMerge熏版,不斷向data.hook插入componentVNodeHooks對(duì)象中對(duì)應(yīng)的鉤子函數(shù)
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(值和回調(diào))轉(zhuǎn)換為prop和event。
function transformModel (options, data) {
// 默認(rèn)prop是value
var prop = (options.model && options.model.prop) || 'value';
// 默認(rèn)event是Input
var event = (options.model && options.model.event) || 'input';
// 保存到props屬性中
(data.attrs || (data.attrs = {}))[prop] = data.model.value;
// 將input事件添加到on對(duì)象中
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;
// 包裝器函數(shù)捍掺,用于提供更靈活的接口
function createElement (
context,
tag,
data,
children,
normalizationType,
alwaysNormalize
) {
// 首先檢測(cè)data的類(lèi)型撼短,通過(guò)判斷data是不是數(shù)組,以及是不是基本類(lèi)型挺勿,來(lái)判斷data是否傳入
// 如果傳入曲横,則將所有的參數(shù)向前賦值
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children;
children = data;
data = undefined;
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE;
}
// 首先判斷data是不是響應(yīng)式的,vnode中的data不能是響應(yīng)式的不瓶。如果是禾嫉,則Vue拋出警告
return _createElement(context, tag, data, children, normalizationType)
}
function _createElement (
context,//context表示vnode上上線文環(huán)境
tag,
data,//vnode數(shù)據(jù)
children,//當(dāng)前vnode子節(jié)點(diǎn)
normalizationType
) {
// 首先判斷data是不是響應(yīng)式的,vnode中的data不能是響應(yīng)式的蚊丐。如果是熙参,則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中的對(duì)象語(yǔ)法
if (isDef(data) && isDef(data.is)) {
tag = data.is;
}
if (!tag) {
// 如果是組件:則設(shè)置為falsy值
return createEmptyVNode()
}
// 如果data.key不屬于基本類(lèi)型,則發(fā)出警告
if (isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
{
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
);
}
}
// 支持單功能子項(xiàng)作為默認(rèn)作用域插槽
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {};
data.scopedSlots = { default: children[0] };
children.length = 0;
}
// 當(dāng)alwaysNormalize等于2的時(shí)候麦备,調(diào)用normalizeChildren去處理children類(lèi)數(shù)組
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children);
// 當(dāng)alwaysNormalize等于1的時(shí)候孽椰,調(diào)用調(diào)用normalizeChildren去處理children類(lèi)數(shù)組
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children);
}
var vnode, ns;
// 判斷tag的類(lèi)型,如果是string就創(chuàng)建普通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時(shí)凛篙,發(fā)出警告
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))) {
//創(chuàng)建組件
vnode = createComponent(Ctor, data, context, children, tag);
} else {
// 未知或未列出的命名空間元素在運(yùn)行時(shí)檢查黍匾,因?yàn)楫?dāng)其父元素規(guī)范化子元素時(shí),可能會(huì)為其分配命名空間
vnode = new VNode(
tag, data, children,
undefined, undefined, context
);
}
} else {
// 如果不是string就會(huì)調(diào)用createComponent創(chuàng)建組件
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()
}
}
//通過(guò)遞歸的方法把ns賦值給vnode.ns
function applyNS (vnode, ns, force) {
vnode.ns = ns;
// 如果vnode.tag等于foreignObject則ns賦值為undefined
if (vnode.tag === 'foreignObject') {
//在foreignObject中使用默認(rèn)命名空間
ns = undefined;
force = true;
}
//如果vnode有子節(jié)點(diǎn)則進(jìn)行遞歸循環(huán)
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);
}
}
}
}
// 確保父級(jí)在深度綁定的樣式和類(lèi)在插槽節(jié)點(diǎn)上使用
function registerDeepBindings (data) {
if (isObject(data.style)) {
//如果有data.style,則調(diào)用traverse進(jìn)行深度監(jiān)聽(tīng)
traverse(data.style);
}
//如果有data.class,則調(diào)用traverse進(jìn)行深度監(jiān)聽(tīng)
if (isObject(data.class)) {
traverse(data.class);
}
}
function initRender (vm) {
vm._vnode = null; // 子樹(shù)的根
vm._staticTrees = null; // v-once緩存樹(shù)
var options = vm.$options;
var parentVnode = vm.$vnode = options._parentVnode; // 父樹(shù)中的占位符節(jié)點(diǎn)
var renderContext = parentVnode && parentVnode.context;
vm.$slots = resolveSlots(options._renderChildren, renderContext);
vm.$scopedSlots = emptyObject;
//將createElement fn綁定到此實(shí)例
//這樣我們就可以在其中獲得適當(dāng)?shù)匿秩旧舷挛摹? //參數(shù)順序:標(biāo)記锐涯、數(shù)據(jù)、子項(xiàng)填物、規(guī)范化類(lèi)型全庸、alwaysNormalize
//內(nèi)部版本由從模板編譯的渲染函數(shù)使用
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
// 規(guī)范化始終應(yīng)用于公共版本秀仲,用于用戶(hù)編寫(xiě)的渲染函數(shù)融痛。
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
// $attrs&$listeners已公開(kāi)壶笼,以便更輕松地進(jìn)行臨時(shí)創(chuàng)建。它們需要是反應(yīng)式的雁刷,以便使用它們的HOC始終得到更新
var parentData = parentVnode && parentVnode.data;
{
// Vue的data監(jiān)聽(tīng)覆劈,也是通過(guò)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) {
// 里面是一些渲染時(shí)候的幫助方法,全部掛載到Vue.prototype上沛励,比如創(chuàng)建空節(jié)點(diǎn)责语、創(chuàng)建文本節(jié)點(diǎn)、toNumber目派、toString等等
installRenderHelpers(Vue.prototype);
// 使用異步函數(shù)來(lái)執(zhí)行
Vue.prototype.$nextTick = function (fn) {
return nextTick(fn, this)
};
// 調(diào)用render函數(shù)
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
);
}
// 設(shè)置父vnode坤候。這允許渲染函數(shù)具有訪問(wèn)權(quán)限添加到占位符節(jié)點(diǎn)上的數(shù)據(jù)。
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");
// 返回錯(cuò)誤渲染結(jié)果企蹭,或上一個(gè)vnode白筹,以防止渲染錯(cuò)誤導(dǎo)致空白組件
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;
}
// 如果返回的數(shù)組只包含一個(gè)節(jié)點(diǎn)
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0];
}
// 如果渲染函數(shù)出錯(cuò),則返回空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賦值一個(gè)空節(jié)點(diǎn)
vnode = createEmptyVNode();
}
//設(shè)置父節(jié)點(diǎn)
vnode.parent = _parentVnode;
return vnode
};
}
/* 是為了保證能找到異步組件上定義的組件對(duì)象而定義的函數(shù) */
function ensureCtor (comp, base) {
// 如果發(fā)現(xiàn)它是普通對(duì)象谅摄,則直接通過(guò) Vue.extend 將其轉(zhuǎn)換成組件的構(gòu)造函數(shù)
if (
comp.__esModule ||
(hasSymbol && comp[Symbol.toStringTag] === 'Module')
) {
comp = comp.default;
}
return isObject(comp)
? base.extend(comp)
: comp
}
// 返回呈現(xiàn)的異步組件的占位符節(jié)點(diǎn)作為注釋節(jié)點(diǎn)徒河,但保留節(jié)點(diǎn)的所有原始信息,這些信息將用于異步服務(wù)器渲染和同步
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:異步組件的函數(shù)
baseCtor
) {
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
// 工廠函數(shù)異步組件第二次執(zhí)行這里時(shí)會(huì)返回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里的所有元素,依次調(diào)用該元素的$forceUpdate()方法 該方法會(huì)強(qiáng)制渲染一次
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;
}
}
};
//定義一個(gè)resolve函數(shù)
var resolve = once(function (res) {
// 緩存解析
factory.resolved = ensureCtor(res, baseCtor);
// 當(dāng)這不是同步解析時(shí)調(diào)用回調(diào)
if (!sync) {
forceRender(true);
} else {
owners.length = 0;
}
});
//定義一個(gè)reject函數(shù)
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);
}
});
//執(zhí)行factory()函數(shù)
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
}
}
/*獲取第一個(gè)組件的子節(jié)點(diǎn) */
function getFirstComponentChild (children) {
//循環(huán)子節(jié)點(diǎn)送漠,判斷子節(jié)點(diǎn)是否位異步占位符
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) {
//修改組件的監(jiān)聽(tīng)器
updateComponentListeners(vm, listeners);
}
}
var target;
//添加監(jiān)聽(tīng)事件
function add (event, fn) {
target.$on(event, fn);
}
//移出自定義事件監(jiān)聽(tīng)器
function remove$1 (event, fn) {
target.$off(event, fn);
}
// 在執(zhí)行完回調(diào)之后顽照,移除事件綁定
function createOnceHandler (event, fn) {
var _target = target;
return function onceHandler () {
var res = fn.apply(null, arguments);
if (res !== null) {
_target.$off(event, onceHandler);
}
}
}
//更新組件的監(jiān)聽(tīng)器
function updateComponentListeners (
vm,
listeners,
oldListeners
) {
target = vm;
// 調(diào)用updateListeners來(lái)修改監(jiān)聽(tīng)配置
updateListeners(listeners, oldListeners || {}, add, remove$1, createOnceHandler, vm);
target = undefined;
}
function eventsMixin (Vue) {
var hookRE = /^hook:/;
// 監(jiān)聽(tīng)事件
Vue.prototype.$on = function (event, fn) {
var vm = this;
//當(dāng)傳入的監(jiān)聽(tīng)事件為數(shù)組,則循環(huán)遍歷調(diào)用$on,否則將監(jiān)聽(tīng)事件和回調(diào)函數(shù)添加到事件處理中心_events對(duì)象中
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn);
}
} else {
// 之前已經(jīng)有監(jiān)聽(tīng)event事件闽寡,則將此次監(jiān)聽(tīng)的回調(diào)函數(shù)添加到其數(shù)組中代兵,否則創(chuàng)建一個(gè)新數(shù)組并添加fn
(vm._events[event] || (vm._events[event] = [])).push(fn);
if (hookRE.test(event)) {
vm._hasHookEvent = true;
}
}
return vm
};
//監(jiān)聽(tīng)事件,只監(jiān)聽(tīng)1次, Vue中的事件機(jī)制,Vue中的事件機(jī)制本身就是一個(gè)訂閱-發(fā)布模式的實(shí)現(xiàn)
Vue.prototype.$once = function (event, fn) {
var vm = this;
// 定義監(jiān)聽(tīng)事件的回調(diào)函數(shù)
function on () {
// 從事件中心移除監(jiān)聽(tīng)事件的回調(diào)函數(shù)
vm.$off(event, on);
// 執(zhí)行回調(diào)函數(shù)
fn.apply(vm, arguments);
}
// 這個(gè)賦值是在$off方法里會(huì)用到的
// 比如我們調(diào)用了vm.$off(fn)來(lái)移除fn回調(diào)函數(shù)爷狈,然而我們?cè)谡{(diào)用$once的時(shí)候植影,實(shí)際執(zhí)行的是vm.$on(event, on)
// 所以在event的回調(diào)函數(shù)數(shù)組里添加的是on函數(shù),這個(gè)時(shí)候要移除fn淆院,我們無(wú)法在回調(diào)函數(shù)數(shù)組里面找到fn函數(shù)移除何乎,只能找到on函數(shù)
// 我們可以通過(guò)on.fn === fn來(lái)判斷這種情況,并在回調(diào)函數(shù)數(shù)組里移除on函數(shù)
on.fn = fn;
// 通過(guò)$on方法注冊(cè)事件土辩,$once最終調(diào)用的是$on支救,并且回調(diào)函數(shù)是on
vm.$on(event, on);
return vm
};
// 移除自定義事件監(jiān)聽(tīng)器。
// 如果沒(méi)有提供參數(shù)拷淘,則移除所有的事件監(jiān)聽(tīng)器各墨;
// 如果只提供了事件,則移除該事件所有的監(jiān)聽(tīng)器启涯;
// 如果同時(shí)提供了事件與回調(diào)贬堵,則只移除這個(gè)回調(diào)的監(jiān)聽(tīng)器恃轩。
Vue.prototype.$off = function (event, fn) {
var vm = this;
//調(diào)用this.$off()沒(méi)有傳參數(shù),則清空事件處理中心緩存的事件及其回調(diào)
if (!arguments.length) {
vm._events = Object.create(null);
return vm
}
//當(dāng)event為數(shù)組黎做,則循環(huán)遍歷調(diào)用$off,否則將監(jiān)聽(tīng)事件和回調(diào)函數(shù)添加到事件處理中心_events對(duì)象中
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
}
// 獲取當(dāng)前event里所有的回調(diào)函數(shù)
var cbs = vm._events[event];
// 如果不存在回調(diào)函數(shù)叉跛,則直接返回,因?yàn)闆](méi)有可以移除監(jiān)聽(tīng)的內(nèi)容
if (!cbs) {
return vm
}
// 如果沒(méi)有指定要移除的回調(diào)函數(shù)蒸殿,則移除該事件下所有的回調(diào)函數(shù)
if (!fn) {
vm._events[event] = null;
return vm
}
// 指定了要移除的回調(diào)函數(shù)
var cb;
var i = cbs.length;
while (i--) {
cb = cbs[i];
// 在事件對(duì)應(yīng)的回調(diào)函數(shù)數(shù)組里面找出要移除的回調(diào)函數(shù)筷厘,并從數(shù)組里移除
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1);
break
}
}
return vm
};
// 觸發(fā)事件
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 + "\"."
);
}
}
// 觸發(fā)事件對(duì)應(yīng)的回調(diào)函數(shù)列表
var cbs = vm._events[event];
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
// $emit方法可以傳參,這些參數(shù)會(huì)在調(diào)用回調(diào)函數(shù)的時(shí)候傳進(jìn)去
var args = toArray(arguments, 1);
var info = "event handler for \"" + event + "\"";
// 遍歷回調(diào)函數(shù)列表宏所,調(diào)用每個(gè)回調(diào)函數(shù)
for (var i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info);
}
}
return vm
};
}