虽然vue3已经出来很久了,但我觉得vue.js的源码还是非常值得去学习一下。vue.js里面封装的很多工具类在我们平时工作项目中也会经常用到。所以我近期会对vue.js的源码进行解读,分享值得去学习的代码片段,这篇文章将会持续更新。
一、6200~结束代码有哪些内容?:
patch、patchVnode 函数:更新节点
_update:将vnode转换成dom,渲染在视图中
updateDirectives:获取指令钩子,和对不同钩子进行不同处理
e.stopImmediatePropagation():该方法作用在当前节点及事件链的所有后续节点上,目的是在执行完当前事件处理程序后,停止当前节点及所有后续节点的同类事件处理程序的运行
getOuterHTML
解决获取该DOM本身以及包含的HTML代码,没有outerHTML属性会返回undefine的问题
processElement:处理key、ref、内外部插槽、绑定属性、组件的判断
processRef:处理ref
processFor:处理v-for指令
parseFor:解析 v-for 表达式
processIf:处理el的条件 v-if v-else v-else-if
processOnce:处理v-once
processSlotContent:处理传递给组件的插槽内容
processAttrs:处理元素的属性列表 包括指令和非指令
二.6200~结束代码的重点:
1.patch组件的更新
组件更新的过程核心就是新旧 vnode diff,对新旧节点相同以及不同的情况分别做不同的处理。新旧节点不同的更新流程是创建新节点->更新父占位符节点->删除旧节点;而新旧节点相同的更新流程是去获取它们的 children,根据不同情况做不同的更新逻辑。最复杂的情况是新旧节点相同且它们都存在子节点,那么会执行 updateChildren 逻辑,根据是否是sameVnode或者依据key尽可能地复用DOM节点
2.过滤器filter的解析:
parseFilters:解析filter
wrapFilter:转换过滤器格式
3.v-bind的函数:
属性添加函数:getRawBindingAttr
属性获取函数:getBindingAttr
属性操作函数:getAndRemoveAttr
4.vue模板编译
createCompilerCreator
baseCompile
5.vue的编译器:
Vue 的编译器都做了什么?
①将组件的 html 模版解析成 AST 对象
②优化,遍历 AST,为每个节点做静态标记,标记其是否为静态节点,然后进一步标记出静态根节点,这样在后续更新的过程中就可以跳过这些静态节点了;标记静态根用于生成渲染函数阶段,生成静态根节点的渲染函数
③从 AST 生成运行渲染函数,即大家说的 render,其实还有一个,就是 staticRenderFns 数组,里面存放了所有的静态节点的渲染函数
6.过渡动画钩子函数
先执行 beforeEnterHook 钩子,并将 DOM 节点 el 传入,随即判断是否希望通过 css 来控制动画,如果是 true 则,执行 addTransitionClass() 方法为节点加上 startClass 和 activeClass。
然后执行 nextFrame() 进入下一帧,下一帧主要是移除掉上一帧增加好的 class;随即判断过渡是否取消,如未取消,则加上 toClass 过渡类;之后如果用户没有通过 enterHook 钩子函数来控制动画,此时若用户指定了 duration 时间,则执行 setTimeout 进行 duration 时长的延时,否则执行 whenTransitionEnds 决定 cb 的执行时机
三、6200~结束的代码解读:
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */);
}
// 调用init钩子后,如果vnode是子组件它应该创建一个子实例并挂载它。这个子组件也设置了占位符vnode的榆树。
//在这种情况下,我们只需要返回元素就可以了。
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
}
function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);
vnode.data.pendingInsert = null;
}
vnode.elm = vnode.componentInstance.$el;
if (isPatchable(vnode)) {
// div#app的创建时会调用invokeCreateHooks
invokeCreateHooks(vnode, insertedVnodeQueue);
setScope(vnode);
} else {
// 根空的组件。跳过所有元素相关的模块,除了ref
registerRef(vnode);
// 确保调用了插入钩子
insertedVnodeQueue.push(vnode);
}
}
function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i;
// 重新激活的内部转换组件不触发,因为内部节点创建的钩子没有被调用一次。
var innerNode = vnode;
while (innerNode.componentInstance) {
innerNode = innerNode.componentInstance._vnode;
if (isDef(i = innerNode.data) && isDef(i = i.transition)) {
for (i = 0; i < cbs.activate.length; ++i) {
cbs.activate[i](emptyNode, innerNode);
}
insertedVnodeQueue.push(innerNode);
break
}
}
// 与新创建的组件不同,重新激活的keep-alive组件不插入自己
insert(parentElm, vnode.elm, refElm);
}
// 通过insertBefore或者appendChild添加元素
function insert (parent, elm, ref$$1) {
if (isDef(parent)) {
if (isDef(ref$$1)) {
if (nodeOps.parentNode(ref$$1) === parent) {
nodeOps.insertBefore(parent, elm, ref$$1);
}
} else {
nodeOps.appendChild(parent, elm);
}
}
}
/*****************************************************************************************************************************/
// 创建vnode对应 的子节点并添加到vnode对应的DOM上
function createChildren (vnode, children, insertedVnodeQueue) {
// 如果children是数组
if (Array.isArray(children)) {
{
// 检查是否存在相同key
checkDuplicateKeys(children);
}
for (var i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
}
} else if (isPrimitive(vnode.text)) {
// 如果vnode.是原始值text
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));
}
}
function isPatchable (vnode) {
while (vnode.componentInstance) {
vnode = vnode.componentInstance._vnode;
}
return isDef(vnode.tag)
}
// 调用执行所有子组件相关的 create 钩子
function invokeCreateHooks (vnode, insertedVnodeQueue) {
// 遍历执行cbs.create里面的函数,触发模块中的钩子函数
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, vnode);
}
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {
//触发hook中的create钩子函数
if (isDef(i.create)) { i.create(emptyNode, vnode); }
//将hook中的insert钩子函数添加到insertedVnodeQueue中
if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
}
}
// 为作用域CSS设置作用域id属性。这是作为一种特殊情况实现的,以避免正常属性修补过程的开销
function setScope (vnode) {
var i;
if (isDef(i = vnode.fnScopeId)) {
nodeOps.setStyleScope(vnode.elm, i);
} else {
var ancestor = vnode;
while (ancestor) {
if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
nodeOps.setStyleScope(vnode.elm, i);
}
ancestor = ancestor.parent;
}
}
// 对于插槽内容,它们还应该从主机实例获取scopeId.
if (isDef(i = activeInstance) &&
i !== vnode.context &&
i !== vnode.fnContext &&
isDef(i = i.$options._scopeId)
) {
nodeOps.setStyleScope(vnode.elm, i);
}
}
// 将新节点的数据,一一创建节点,更新Dom
function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {
for (; startIdx <= endIdx; ++startIdx) {
createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx);
}
}
// 递归调用自己和子节点的remove钩子和destroy钩子
function invokeDestroyHook (vnode) {
var i, j;
var data = vnode.data;
if (isDef(data)) {
// destroy钩子函数
if (isDef(i = data.hook) && isDef(i = i.destroy)) { i(vnode); }
for (i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](vnode); }
}
// 递归处理子节点
if (isDef(i = vnode.children)) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j]);
}
}
}
//将老节点全部删除
function removeVnodes (vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
var ch = vnodes[startIdx];
if (isDef(ch)) {
if (isDef(ch.tag)) {
//调用移除事件回调函数并移除节点
removeAndInvokeRemoveHook(ch);
// invokeDestroyHook 是执行 module 的 destory 钩子函数以及 vnode 的 destory 钩子函数,并对它的子 vnode 递归调用 invokeDestroyHook 函数
invokeDestroyHook(ch);
} else {
// 就是调用平台的 DOM API 去把真正的 DOM 节点移除
removeNode(ch.elm);
}
}
}
}
// 递归处理了子节点的属性并删除 dom 节点
function removeAndInvokeRemoveHook (vnode, rm) {
if (isDef(rm) || isDef(vnode.data)) {
var i;
var listeners = cbs.remove.length + 1;
if (isDef(rm)) {
// 有一个递归传递的rm回调函数,用于增加侦听器计数
rm.listeners += listeners;
} else {
// directly removing
rm = createRmCb(vnode.elm, listeners);
}
// 递归调用子组件根节点上的钩子
if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {
removeAndInvokeRemoveHook(i, rm);
}
for (i = 0; i < cbs.remove.length; ++i) {
cbs.remove[i](vnode, rm);
}
if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
i(vnode, rm);
} else {
rm();
}
} else {
removeNode(vnode.elm);
}
}
// 如果新老节点都有子节点,但是不相同的时候就会调用 updateChildren,这个函数通过diff算法尽可能的复用先前的DOM节点
// 以新Node 为标杆 oldNode 为 真实DoM的映射(尽可能的利用已有的oLdNode的资源)最大程度的减少真实dom的操作
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
var oldStartIdx = 0;
var newStartIdx = 0;
var oldEndIdx = oldCh.length - 1;
var oldStartVnode = oldCh[0];
var oldEndVnode = oldCh[oldEndIdx];
var newEndIdx = newCh.length - 1;
var newStartVnode = newCh[0];
var newEndVnode = newCh[newEndIdx];
var oldKeyToIdx, idxInOld, vnodeToMove, refElm;
// removeOnly是一个特殊标志,仅由<transition group>使用,以确保在离开转换期间被删除的元素保持在正确的相对位置
var canMove = !removeOnly;
//检查重复节点
{
checkDuplicateKeys(newCh);
}
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx];
// oldStartVnode和newStartVnode进行比对,如果相似,则进行patch,然后新旧头索引都后移
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
// oldEndVnode和newEndVnode进行比对,如果相似,则进行patch,然后新旧尾索引前移
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
// oldStartVnode和newEndVnode进行比对,如果相似,则进行patch,将旧节点移位到最后,然后旧头索引后移,尾索引前移
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx);
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
// oldEndVnode和newStartVnode进行比对,处理和上面类似,只不过改为左移
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
// 通过createKeyToOldIdx创建key-index的映射,如果新节点在旧节点中不存在,我们将它插入到旧头索引节点前,然后新头索引向后,
// 如果新节点在就旧节点组中存在,先找到对应的旧节点,然后patch,并将旧节点组中对应节点设置为undefined,代表已经遍历过了,
// 不再遍历,否则可能存在重复插入的问题
if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); }
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
} else {
vnodeToMove = oldCh[idxInOld];
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
oldCh[idxInOld] = undefined;
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
}
}
newStartVnode = newCh[++newStartIdx];
}
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
} else if (newStartIdx > newEndIdx) {
removeVnodes(oldCh, oldStartIdx, oldEndIdx);
}
}
// 检查vnode 数组中是否有vnode的key 重复,如果重复则给出警告
function checkDuplicateKeys (children) {
var seenKeys = {};
for (var i = 0; i < children.length; i++) {
var vnode = children[i];
var key = vnode.key;
if (isDef(key)) {
if (seenKeys[key]) {
warn(
("Duplicate keys detected: '" + key + "'. This may cause an update error."),
vnode.context
);
} else {
seenKeys[key] = true;
}
}
}
}
// sameVnode 是对比新旧节点是否相同的函数
function findIdxInOld (node, oldCh, start, end) {
for (var i = start; i < end; i++) {
var c = oldCh[i];
if (isDef(c) && sameVnode(node, c)) { return i }
}
}
// patchVnode 函数是对比 两个虚拟 dom 不同的地方, 同时 也是 递归 调用 updateChildren 的 函数
function patchVnode (
oldVnode,// 旧节点
vnode,// 新节点
insertedVnodeQueue,// 插入节点的队列
ownerArray,// 节点 数组
index, // 当前 节点的
removeOnly // 只有在 patch 函数中被传入,当老节点不是真实的 dom 节点,当新老节点是相同节点的时候
) {
// 如果新节点和旧节点 相等(使用了 同一个地址,直接返回不进行修改)
// 这里就是 当 props 没有改变的时候,子组件不会做渲染,而是直接复用
if (oldVnode === vnode) {
return
}
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode);
}
var elm = vnode.elm = oldVnode.elm;
//当前节点 是 注释节点(被 v-if )了,或者是一个 异步函数节点,那不执行
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue);
} else {
vnode.isAsyncPlaceholder = true;
}
return
}
// 当前节点 是一个静态节点的时候,或者 标记了 once 的时候,那不执行
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance;
return
}
var i;
var data = vnode.data;
// 调用 prepatch 的钩子函数
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode);
}
var oldCh = oldVnode.children;
var ch = vnode.children;
// 调用 update 钩子函数
if (isDef(data) && isPatchable(vnode)) {
// 这里 的 update 钩子函数式 vnode 本身的钩子函数
for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); }
// 这里的 update 钩子函数 是 用户传过来的 钩子函数
if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); }
}
if (isUndef(vnode.text)) {
// 是否存在子节点,然后新老节点的子节点是否相同,否就执行 updateChildren 函数深入对比
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
} else if (isDef(ch)) {
{
checkDuplicateKeys(ch);
}
// 查看新老节点是否存在,新节点存在但是老节点不存在,增加,新节点不存在但是老节点存在,删除
if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); }
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
removeVnodes(oldCh, 0, oldCh.length - 1);
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '');
}
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text);
}
// 检查 新老节点的 text 属性是否一致,否的话,就执行 postpatch 钩子函数更新
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); }
}
}
// 执行 虚拟 dom 的 insert 钩子函数
function invokeInsertHook (vnode, queue, initial) {
// invokeInsertHook函数会循环通过insert方法依次调用收集的hook,在insert方法中就会触发mounted
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue;
} else {
for (var i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i]);
}
}
}
var hydrationBailed = false;
var isRenderedModule = makeMap('attrs,class,staticClass,staticStyle,key');
// 将虚拟dom和真是dom进行映射,然后将oldVnode设置为对应的虚拟dom,找到oldVnode.elm的父节点
// 根据vnode创建一个真实dom节点并插入到该父节点中oldVnode.elm的位置
function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {
var i;
var tag = vnode.tag;
var data = vnode.data;
var children = vnode.children;
inVPre = inVPre || (data && data.pre);
vnode.elm = elm;
if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
vnode.isAsyncPlaceholder = true;
return true
}
// assert node match
{
if (!assertNodeMatch(elm, vnode, inVPre)) {
return false
}
}
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.init)) { i(vnode, true /* hydrating */); }
if (isDef(i = vnode.componentInstance)) {
// child component. it should have hydrated its own tree.
initComponent(vnode, insertedVnodeQueue);
return true
}
}
if (isDef(tag)) {
if (isDef(children)) {
// 空元素,允许客户端拾取并填充子元素
if (!elm.hasChildNodes()) {
createChildren(vnode, children, insertedVnodeQueue);
} else {
// v-html and domProps: innerHTML
if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) {
if (i !== elm.innerHTML) {
/* istanbul ignore if */
if (typeof console !== 'undefined' &&
!hydrationBailed
) {
hydrationBailed = true;
console.warn('Parent: ', elm);
console.warn('server innerHTML: ', i);
console.warn('client innerHTML: ', elm.innerHTML);
}
return false
}
} else {
// 迭代和比较子列表
var childrenMatch = true;
var childNode = elm.firstChild;
for (var i$1 = 0; i$1 < children.length; i$1++) {
if (!childNode || !hydrate(childNode, children[i$1], insertedVnodeQueue, inVPre)) {
childrenMatch = false;
break
}
childNode = childNode.nextSibling;
}
// 如果childNode不为空,则意味着实际的childNodes列表比虚拟子节点列表长。
if (!childrenMatch || childNode) {
/* istanbul ignore if */
if (typeof console !== 'undefined' &&
!hydrationBailed
) {
hydrationBailed = true;
console.warn('Parent: ', elm);
console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children);
}
return false
}
}
}
}
if (isDef(data)) {
var fullInvoke = false;
for (var key in data) {
if (!isRenderedModule(key)) {
fullInvoke = true;
invokeCreateHooks(vnode, insertedVnodeQueue);
break
}
}
if (!fullInvoke && data['class']) {
// ensure collecting deps for deep class bindings for future updates
traverse(data['class']);
}
}
} else if (elm.data !== vnode.text) {
elm.data = vnode.text;
}
return true
}
function assertNodeMatch (node, vnode, inVPre) {
if (isDef(vnode.tag)) {
return vnode.tag.indexOf('vue-component') === 0 || (
!isUnknownElement$$1(vnode, inVPre) &&
vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())
)
} else {
return node.nodeType === (vnode.isComment ? 8 : 3)
}
}
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// oldVnode 表示旧的 VNode 节点,它也可以不存在或者是一个 DOM 对象
// vnode 表示执行 _render 后返回的 VNode 的节点
// hydrating 表示是否是服务端渲染
// removeOnly 是给 transition-group 用的
// 判断 vnode 是否存在,我们的例子是返回了一个vnode的所以跳过
if (isUndef(vnode)) {
if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
return
}
var isInitialPatch = false;
var insertedVnodeQueue = [];
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true;
createElm(vnode, insertedVnodeQueue);
} else {
var isRealElement = isDef(oldVnode.nodeType);
// 第一次执行 patch 函数时传递的 oldVnode 参数是 vm.$el ,也就是要被替换的 DOM 节点
// 更新调用 patch 函数时传递的 oldVnode 参数是 prevVnode ,所以是一个虚拟 DOM
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
} else {
if (isRealElement) {
// 服务端渲染
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR);
hydrating = true;
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true);
return oldVnode
} else {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
);
}
}
// 把 oldVnode 转换成 VNode 对象
// 在emptyNodeAt中我们将oldVnode传给了oldVnode.elm
oldVnode = emptyNodeAt(oldVnode);
}
// // oldElm拿到原来的 oldVnode
var oldElm = oldVnode.elm;
// 拿到父节点body
var parentElm = nodeOps.parentNode(oldElm);
// createElm的作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点中,接下来分析 createElm
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
);
// 这是父占位节点,是和组件相关的,这里不会执行
if (isDef(vnode.parent)) {
var ancestor = vnode.parent;
var patchable = isPatchable(vnode);
while (ancestor) {
for (var i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor);
}
ancestor.elm = vnode.elm;
if (patchable) {
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, ancestor);
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
var insert = ancestor.data.hook.insert;
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (var i$2 = 1; i$2 < insert.fns.length; i$2++) {
insert.fns[i$2]();
}
}
} else {
registerRef(ancestor);
}
ancestor = ancestor.parent;
}
}
// 判断之前定义的 parentElm 是否存在,有则删除掉对应的节点
// 我们之前在 const parentElm = nodeOps.parentNode(oldElm) 中拿到了id=app 的父元素body
// 之后删除<div id="app"></div>完成新旧节点替换工作。
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
}
}
// 我们根据之前递归 createElm 生成的 vnode 插入顺序队列,执行相关的 insert 钩子函数
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
}
/* */
var directives = {
create: updateDirectives,
update: updateDirectives,
destroy: function unbindDirectives (vnode) {
updateDirectives(vnode, emptyNode);
}
};
// updateDirectives函数的作用:获取指令钩子,和对不同钩子进行不同处理
function updateDirectives (oldVnode, vnode) {
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode);
}
}
// 主要是将vnode转换成dom,渲染在视图中
function _update (oldVnode, vnode) {
//判断旧节点是不是空节点,是的话表示新建/初始化组件
var isCreate = oldVnode === emptyNode;
//判断新节点是不是空节点,是的话表示销毁组件
var isDestroy = vnode === emptyNode;
//获取旧节点上的所有自定义指令
var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
//获取新节点上的所有自定义指令
var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);
//保存inserted钩子函数
var dirsWithInsert = [];
//保存componentUpdated钩子函数
var dirsWithPostpatch = [];
var key, oldDir, dir;
//遍历所有新节点上的自定义指令
for (key in newDirs) {
oldDir = oldDirs[key];
dir = newDirs[key];
//如果旧节点中没有对应的指令,一般都是初始化的时候运行
if (!oldDir) {
//对该节点执行指令的bind钩子函数
callHook$1(dir, 'bind', vnode, oldVnode);
//dir.def是我们所定义的指令的五个钩子函数的集合
//如果我们的指令中存在inserted钩子函数
if (dir.def && dir.def.inserted) {
//把该指令存入dirsWithInsert中
dirsWithInsert.push(dir);
}
} else {
//如果旧节点中有对应的指令,一般都是组件更新的时候运行
//那么这里进行更新操作,运行update钩子(如果有的话)
//将旧值保存下来,供其他地方使用(仅在 update 和 componentUpdated 钩子中可用)
dir.oldValue = oldDir.value;
dir.oldArg = oldDir.arg;
//对该节点执行指令的update钩子函数
callHook$1(dir, 'update', vnode, oldVnode);
//dir.def是我们所定义的指令的五个钩子函数的集合
//如果我们的指令中存在componentUpdated钩子函数
if (dir.def && dir.def.componentUpdated) {
//把该指令存入dirsWithPostpatch中
dirsWithPostpatch.push(dir);
}
}
}
// 如果dirsWithInsert的长度不为0,也就是在初始化的时候,且至少有一个指令中有inserted钩子函数
if (dirsWithInsert.length) {
//封装回调函数
var callInsert = function () {
//遍历所有指令的inserted钩子
for (var i = 0; i < dirsWithInsert.length; i++) {
//对节点执行指令的inserted钩子函数
callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
}
};
if (isCreate) {
//如果是新建/初始化组件,使用mergeVNodeHook绑定insert属性,等待后面调用
mergeVNodeHook(vnode, 'insert', callInsert);
} else {
//如果是更新组件,直接调用函数,遍历inserted钩子
callInsert();
}
}
//如果dirsWithPostpatch的长度不为0,也就是在组件更新的时候,且至少有一个指令中有componentUpdated钩子函数
if (dirsWithPostpatch.length) {
//使用mergeVNodeHook绑定postpatch属性,等待后面子组建全部更新完成调用
mergeVNodeHook(vnode, 'postpatch', function () {
for (var i = 0; i < dirsWithPostpatch.length; i++) {
//对节点执行指令的componentUpdated钩子函数
callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
}
});
}
//如果不是新建/初始化组件,也就是说是更新组件
if (!isCreate) {
//遍历旧节点中的指令
for (key in oldDirs) {
//如果新节点中没有这个指令
if (!newDirs[key]) {
//从旧节点中解绑,isDestroy表示组件是不是注销了
//对旧节点执行指令的unbind钩子函数
callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
}
}
}
}
var emptyModifiers = Object.create(null);
// 遍历本节点所有的指令,逐个从组件中获取,把获取的钩子添加到 遍历到的当前指令上
function normalizeDirectives$1 (
dirs,
vm
) {
var res = Object.create(null);
if (!dirs) {
// $flow-disable-line
return res
}
var i, dir;
for (i = 0; i < dirs.length; i++) {
dir = dirs[i];
if (!dir.modifiers) {
// $flow-disable-line
dir.modifiers = emptyModifiers;
}
res[getRawDirName(dir)] = dir;
dir.def = resolveAsset(vm.$options, 'directives', dir.name, true);
}
// $flow-disable-line
return res
}
// 获取原始名称(包含前缀、参数、修饰符)
function getRawDirName (dir) {
return dir.rawName || ((dir.name) + "." + (Object.keys(dir.modifiers || {}).join('.')))
}
// 调用指令钩子函数,格式化钩子函数
//callHook$1有五个参数,第一个参数是指令对象,第二个参数是钩子函数名称,第三个参数新节点,
//第四个参数是旧节点,第五个参数是是否为注销组件,默认为undefined,只在组件注销时使用
//在这个函数里,会根据我们传递的钩子函数名称,运行我们自定义组件时,所声明的钩子函数
function callHook$1 (dir, hook, vnode, oldVnode, isDestroy) {
var fn = dir.def && dir.def[hook];
if (fn) {
try {
fn(vnode.elm, dir, vnode, oldVnode, isDestroy);
} catch (e) {
handleError(e, vnode.context, ("directive " + (dir.name) + " " + hook + " hook"));
}
}
}
var baseModules = [
ref,
directives
];
/* 更新属性 */
function updateAttrs (oldVnode, vnode) {
var opts = vnode.componentOptions;
if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) {
return
}
if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {
return
}
var key, cur, old;
var elm = vnode.elm;
var oldAttrs = oldVnode.data.attrs || {};
var attrs = vnode.data.attrs || {};
// 克隆观察到的对象
if (isDef(attrs.__ob__)) {
attrs = vnode.data.attrs = extend({}, attrs);
}
for (key in attrs) {
cur = attrs[key];
old = oldAttrs[key];
if (old !== cur) {
setAttr(elm, key, cur, vnode.data.pre);
}
}
//在IE9中,设置type可以重置输入的值
//IE/Edge在设置最大值之前将进度值降低为1
if ((isIE || isEdge) && attrs.value !== oldAttrs.value) {
setAttr(elm, 'value', attrs.value);
}
for (key in oldAttrs) {
if (isUndef(attrs[key])) {
if (isXlink(key)) {
elm.removeAttributeNS(xlinkNS, getXlinkProp(key));
} else if (!isEnumeratedAttr(key)) {
elm.removeAttribute(key);
}
}
}
}
// 设置属性
function setAttr (el, key, value, isInPre) {
if (isInPre || el.tagName.indexOf('-') > -1) {
//设置el元素的key属性为value
baseSetAttr(el, key, value);
} else if (isBooleanAttr(key)) {
// 设置属性为空值
if (isFalsyAttrValue(value)) {
el.removeAttribute(key);
} else {
//allowfullscreen是iframe的布尔属性,但Flash期望在标签上使用值为"true"
value = key === 'allowfullscreen' && el.tagName === 'EMBED'
? 'true'
: key;
el.setAttribute(key, value);
}
// 如果key是这三个中的一个:contenteditable,draggable,spellcheck
} else if (isEnumeratedAttr(key)) {
el.setAttribute(key, convertEnumeratedValue(key, value));
} else if (isXlink(key)) {
if (isFalsyAttrValue(value)) {
el.removeAttributeNS(xlinkNS, getXlinkProp(key));
} else {
el.setAttributeNS(xlinkNS, key, value);
}
} else {
// 直接调用baseSetAttr设置属性
baseSetAttr(el, key, value);
}
}
function baseSetAttr (el, key, value) {
//如果value是null或false,则删除属性
if (isFalsyAttrValue(value)) {
el.removeAttribute(key);
} else {
// 当在textarea设置占位符阻塞第一个输入事件时触发输入事件,并立即移除阻塞器
if (
isIE && !isIE9 &&
el.tagName === 'TEXTAREA' &&
key === 'placeholder' && value !== '' && !el.__ieph
) {
var blocker = function (e) {
// 该方法作用在当前节点及事件链的所有后续节点上,目的是在执行完当前事件处理程序后,停止当前节点及所有后续节点的同类事件处理程序的运行
e.stopImmediatePropagation();
//移出监听
el.removeEventListener('input', blocker);
};
//添加监听
el.addEventListener('input', blocker);
// $flow-disable-line
el.__ieph = true; /* IE placeholder patched */
}
//直接调用原生DOMAPI setAttribute设置属性
el.setAttribute(key, value);
}
}
var attrs = {
create: updateAttrs,
update: updateAttrs
};
/* 更新样式 */
function updateClass (oldVnode, vnode) {
var el = vnode.elm;
var data = vnode.data;
var oldData = oldVnode.data;
if (
isUndef(data.staticClass) &&
isUndef(data.class) && (
isUndef(oldData) || (
isUndef(oldData.staticClass) &&
isUndef(oldData.class)
)
)
) {
return
}
var cls = genClassForVnode(vnode);
// handle transition classes
var transitionClass = el._transitionClasses;
if (isDef(transitionClass)) {
cls = concat(cls, stringifyClass(transitionClass));
}
// set the class
if (cls !== el._prevClass) {
el.setAttribute('class', cls);
el._prevClass = cls;
}
}
var klass = {
create: updateClass,
update: updateClass
};
//过滤器解析
var validDivisionCharRE = /[\w).+\-_$\]]/;
// filter允许用在两个地方,一个是双括号插值,一个是v-bind表达式后面,如果解析到这两种情况,执行parseFilters解析filter
function parseFilters (exp) {
var inSingle = false;
var inDouble = false;
var inTemplateString = false;
var inRegex = false;
var curly = 0;
var square = 0;
var paren = 0;
var lastFilterIndex = 0;
var c, prev, i, expression, filters;
// for循环传入的value字符串
for (i = 0; i < exp.length; i++) {
prev = c;
c = exp.charCodeAt(i);
if (inSingle) {
if (c === 0x27 && prev !== 0x5C) { inSingle = false; }
} else if (inDouble) {
if (c === 0x22 && prev !== 0x5C) { inDouble = false; }
} else if (inTemplateString) {
if (c === 0x60 && prev !== 0x5C) { inTemplateString = false; }
} else if (inRegex) {
if (c === 0x2f && prev !== 0x5C) { inRegex = false; }
} else if (
c === 0x7C && // pipe
exp.charCodeAt(i + 1) !== 0x7C &&
exp.charCodeAt(i - 1) !== 0x7C &&
!curly && !square && !paren
) {
if (expression === undefined) {
// first filter, end of expression
lastFilterIndex = i + 1;
expression = exp.slice(0, i).trim();
} else {
pushFilter();
}
} else {
switch (c) {
case 0x22: inDouble = true; break // "
case 0x27: inSingle = true; break // '
case 0x60: inTemplateString = true; break // `
case 0x28: paren++; break // (
case 0x29: paren--; break // )
case 0x5B: square++; break // [
case 0x5D: square--; break // ]
case 0x7B: curly++; break // {
case 0x7D: curly--; break // }
}
if (c === 0x2f) { // /
var j = i - 1;
var p = (void 0);
// find first non-whitespace prev char
for (; j >= 0; j--) {
p = exp.charAt(j);
if (p !== ' ') { break }
}
if (!p || !validDivisionCharRE.test(p)) {
inRegex = true;
}
}
}
}
// 如果expression 为空,则说明没有没有filter函数,或者是写法出了问题。某些符号没闭合
if (expression === undefined) {
expression = exp.slice(0, i).trim();
} else if (lastFilterIndex !== 0) {
pushFilter();
}
// 初始化filters变量。将过滤器函数推入filters数组中
function pushFilter () {
(filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim());
lastFilterIndex = i + 1;
}
// 如果有过滤器函数,则结合expression生成最终的表达式
if (filters) {
for (i = 0; i < filters.length; i++) {
expression = wrapFilter(expression, filters[i]);
}
}
return expression
}
// 转换过滤器格式,ilter函数结合expression生成最终的表达式
function wrapFilter (exp, filter) {
var i = filter.indexOf('(');
if (i < 0) {
return ("_f(\"" + filter + "\")(" + exp + ")")
} else {
var name = filter.slice(0, i);
var args = filter.slice(i + 1);
return ("_f(\"" + name + "\")(" + exp + (args !== ')' ? ',' + args : args))
}
}
/* */
// 基础警告
function baseWarn (msg, range) {
console.error(("[Vue compiler]: " + msg));
}
//循环过滤数组或者对象的值,根据key循环 过滤对象或者数组[key]值,如果不存在则丢弃,如果有相同多个的key值,返回多个值的数组
function pluckModuleFunction (
modules,
key
) {
return modules
? modules.map(function (m) { return m[key]; }).filter(function (_) { return _; })
: []
}
//在虚拟dom中添加prop属性
function addProp (el, name, value, range, dynamic) {
(el.props || (el.props = [])).push(rangeSetItem({ name: name, value: value, dynamic: dynamic }, range));
el.plain = false;
}
//添加attrs属性
function addAttr (el, name, value, range, dynamic) {
var attrs = dynamic
? (el.dynamicAttrs || (el.dynamicAttrs = []))
: (el.attrs || (el.attrs = []));
attrs.push(rangeSetItem({ name: name, value: value, dynamic: dynamic }, range));
el.plain = false;
}
// 添加原始attr(在预转换中使用)
function addRawAttr (el, name, value, range) {
el.attrsMap[name] = value;
el.attrsList.push(rangeSetItem({ name: name, value: value }, range));
}
//为虚拟dom 添加一个 指令directives属性 对象
function addDirective (
el,
name,
rawName,
value,
arg,
isDynamicArg,
modifiers,
range
) {
(el.directives || (el.directives = [])).push(rangeSetItem({
name: name,
rawName: rawName,
value: value,
arg: arg,
isDynamicArg: isDynamicArg,
modifiers: modifiers
}, range));
el.plain = false;
}
// 给修饰符的属性,加上标记
function prependModifierMarker (symbol, name, dynamic) {
return dynamic
? ("_p(" + name + ",\"" + symbol + "\")")
: symbol + name // 将事件标记为捕获
}
// 为虚拟dom添加events 事件对象属性
function addHandler (
el,
name,
value,
modifiers,
important,
warn,
range,
dynamic
) {
// modifiers 是一个对象,如果传递的参数为空,则给一个冻结的空对象
modifiers = modifiers || emptyObject;
// warn prevent and passive modifier
/* istanbul ignore if */
if (
warn &&
modifiers.prevent && modifiers.passive
) {
warn(
'passive and prevent can\'t be used together. ' +
'Passive handler can\'t prevent default event.',
range
);
}
// 标准化 click.right 和 click.middle,它们实际上不会被真正的触发,从技术讲他们是它们
// 是特定于浏览器的,但至少目前位置只有浏览器才具有右键和中间键的点击
if (modifiers.right) {
if (dynamic) {
name = "(" + name + ")==='click'?'contextmenu':(" + name + ")";
} else if (name === 'click') {
// 动态属性
name = 'contextmenu';
// 删除修饰符中的 right 属性
delete modifiers.right;
}
} else if (modifiers.middle) {
if (dynamic) {
// 动态属性,name => mouseup 或者 ${name}
name = "(" + name + ")==='click'?'mouseup':(" + name + ")";
} else if (name === 'click') {
name = 'mouseup';
}
}
// 处理 capture、once、passive 这三个修饰符,通过给 name 添加不同的标记来标记这些修饰符
// 给带有 capture 修饰符的属性,加上 ! 标记
if (modifiers.capture) {
delete modifiers.capture;
name = prependModifierMarker('!', name, dynamic);
}
// once 修饰符加 ~ 标记
if (modifiers.once) {
delete modifiers.once;
name = prependModifierMarker('~', name, dynamic);
}
// passive 修饰符加 & 标记
if (modifiers.passive) {
delete modifiers.passive;
name = prependModifierMarker('&', name, dynamic);
}
var events;
if (modifiers.native) {
delete modifiers.native;
events = el.nativeEvents || (el.nativeEvents = {});
} else {
events = el.events || (el.events = {});
}
var newHandler = rangeSetItem({ value: value.trim(), dynamic: dynamic }, range);
if (modifiers !== emptyObject) {
newHandler.modifiers = modifiers;
}
var handlers = events[name];
/* istanbul ignore if */
if (Array.isArray(handlers)) {
important ? handlers.unshift(newHandler) : handlers.push(newHandler);
} else if (handlers) {
events[name] = important ? [newHandler, handlers] : [handlers, newHandler];
} else {
events[name] = newHandler;
}
el.plain = false;
}
//v-bind的属性添加函数
function getRawBindingAttr (
el,
name
) {
return el.rawAttrsMap[':' + name] ||
el.rawAttrsMap['v-bind:' + name] ||
el.rawAttrsMap[name]
}
//v-bind的属性获取函数
function getBindingAttr (
el,
name,
getStatic
) {
var dynamicValue =
getAndRemoveAttr(el, ':' + name) ||
getAndRemoveAttr(el, 'v-bind:' + name);
if (dynamicValue != null) {
return parseFilters(dynamicValue)
} else if (getStatic !== false) {
var staticValue = getAndRemoveAttr(el, name);
if (staticValue != null) {
return JSON.stringify(staticValue)
}
}
}
//注意:这只是从数组(attrsList)中删除了属性r,以便它
//不被processAttrs处理
//默认情况下,它不从映射(attrsMap)中删除它,因为映射是
//在代码生成期间需要
// 移除传进来的属性name,并且返回获取到 属性的值
function getAndRemoveAttr (
el,
name,
removeFromMap
) {
var val;
if ((val = el.attrsMap[name]) != null) {
var list = el.attrsList;
for (var i = 0, l = list.length; i < l; i++) {
if (list[i].name === name) {
list.splice(i, 1);
break
}
}
}
if (removeFromMap) {
delete el.attrsMap[name];
}
return val
}
//删除属性
function getAndRemoveAttrByRegex (
el,
name
) {
// attrsList 类型为 Array<ASTAttr>
var list = el.attrsList;
// 对 attrsList 进行遍历,若有满足 RegExp 的则直接返回当前对应的 attr
// 若参数 name 传进来的是 slotRE = /^v-slot(:|$)|^#/
// 那么匹配到 'v-slot' 或者 'v-slot:xxx' 则会返回其对应的 attr
for (var i = 0, l = list.length; i < l; i++) {
var attr = list[i];
if (name.test(attr.name)) {
list.splice(i, 1);
return attr
}
}
}
function rangeSetItem (
item,
range
) {
if (range) {
if (range.start != null) {
item.start = range.start;
}
if (range.end != null) {
item.end = range.end;
}
}
return item
}
/* */
/**
*为虚拟dom添加model属性
*/
function genComponentModel (
el,
value,
modifiers
) {
var ref = modifiers || {};
var number = ref.number;
var trim = ref.trim;
var baseValueExpression = '$$v';
var valueExpression = baseValueExpression;
if (trim) {
valueExpression =
"(typeof " + baseValueExpression + " === 'string'" +
"? " + baseValueExpression + ".trim()" +
": " + baseValueExpression + ")";
}
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}
var assignment = genAssignmentCode(value, valueExpression);
el.model = {
value: ("(" + value + ")"),
expression: JSON.stringify(value),
callback: ("function (" + baseValueExpression + ") {" + assignment + "}")
};
}
/**
* 用于生成v-模型值赋值代码的跨平台代码生成助手
*/
function genAssignmentCode (
value,
assignment
) {
// 对 v-model 对应的 value 做了解析
var res = parseModel(value);
if (res.key === null) {
return (value + "=" + assignment)
} else {
return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")
}
}
var len, str, chr, index$1, expressionPos, expressionEndPos;
//转义字符串对象拆分字符串对象 把后一位key分离出来
function parseModel (val) {
val = val.trim();
len = val.length;
if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {
index$1 = val.lastIndexOf('.');
if (index$1 > -1) {
return {
exp: val.slice(0, index$1),
key: '"' + val.slice(index$1 + 1) + '"'
}
} else {
return {
exp: val,
key: null
}
}
}
str = val;
index$1 = expressionPos = expressionEndPos = 0;
while (!eof()) {
chr = next();
/* istanbul ignore if */
if (isStringStart(chr)) {
parseString(chr);
} else if (chr === 0x5B) {
parseBracket(chr);
}
}
return {
exp: val.slice(0, expressionPos),
key: val.slice(expressionPos + 1, expressionEndPos)
}
}
// 返回字符串第一个字符的 Unicode 编码
function next () {
return str.charCodeAt(++index$1)
}
function eof () {
return index$1 >= len
}
function isStringStart (chr) {
return chr === 0x22 || chr === 0x27
}
function parseBracket (chr) {
var inBracket = 1;
expressionPos = index$1;
while (!eof()) {
chr = next();
if (isStringStart(chr)) {
parseString(chr);
continue
}
if (chr === 0x5B) { inBracket++; }
if (chr === 0x5D) { inBracket--; }
if (inBracket === 0) {
expressionEndPos = index$1;
break
}
}
}
function parseString (chr) {
var stringQuote = chr;
while (!eof()) {
chr = next();
if (chr === stringQuote) {
break
}
}
}
/*******************************************************************************************************************************************/
var warn$1;
// 在某些情况下,使用的事件必须在运行时确定
//在编译过程中使用了一些保留的标记
var RANGE_TOKEN = '__r';
var CHECKBOX_RADIO_TOKEN = '__c';
//v-model指令的初始化
// v-model本质上就是一个语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理
function model (
el,
dir,
_warn
) {
warn$1 = _warn;
//值
var value = dir.value;
// 修饰符
var modifiers = dir.modifiers;
// 标签名
var tag = el.tag;
var type = el.attrsMap.type;
{
// type="file"的输入是只读的,并设置输入的值
// value将抛出一个错误
if (tag === 'input' && type === 'file') {
warn$1(
"<" + (el.tag) + " v-model=\"" + value + "\" type=\"file\">:\n" +
"File inputs are read only. Use a v-on:change listener instead.",
el.rawAttrsMap['v-model']
);
}
}
if (el.component) {
genComponentModel(el, value, modifiers);
// 组件v-model不需要额外的运行时
return false
} else if (tag === 'select') {
genSelect(el, value, modifiers);
} else if (tag === 'input' && type === 'checkbox') {
genCheckboxModel(el, value, modifiers);
} else if (tag === 'input' && type === 'radio') {
genRadioModel(el, value, modifiers);
} else if (tag === 'input' || tag === 'textarea') {
genDefaultModel(el, value, modifiers);
} else if (!config.isReservedTag(tag)) {
genComponentModel(el, value, modifiers);
// component v-model doesn't need extra runtime
return false
} else {
warn$1(
"<" + (el.tag) + " v-model=\"" + value + "\">: " +
"v-model is not supported on this element type. " +
'If you are working with contenteditable, it\'s recommended to ' +
'wrap a library dedicated for that purpose inside a custom component.',
el.rawAttrsMap['v-model']
);
}
// ensure runtime directive metadata
return true
}
//复选框的v-model
function genCheckboxModel (
el,
value,
modifiers
) {
var number = modifiers && modifiers.number;
//获取元素值的属性
var valueBinding = getBindingAttr(el, 'value') || 'null';
var trueValueBinding = getBindingAttr(el, 'true-value') || 'true';
var falseValueBinding = getBindingAttr(el, 'false-value') || 'false';
// 当trueValueBinding为true,在节点添加checked属性
addProp(el, 'checked',
"Array.isArray(" + value + ")" +
"?_i(" + value + "," + valueBinding + ")>-1" + (
trueValueBinding === 'true'
? (":(" + value + ")")
: (":_q(" + value + "," + trueValueBinding + ")")
)
);
// 在节点添加change事件
addHandler(el, 'change',
"var $$a=" + value + "," +
'$$el=$event.target,' +
"$$c=$$el.checked?(" + trueValueBinding + "):(" + falseValueBinding + ");" +
'if(Array.isArray($$a)){' +
"var $$v=" + (number ? '_n(' + valueBinding + ')' : valueBinding) + "," +
'$$i=_i($$a,$$v);' +
"if($$el.checked){$$i<0&&(" + (genAssignmentCode(value, '$$a.concat([$$v])')) + ")}" +
"else{$$i>-1&&(" + (genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')) + ")}" +
"}else{" + (genAssignmentCode(value, '$$c')) + "}",
null, true
);
}
//单选框的v-model
function genRadioModel (
el,
value,
modifiers
) {
//获取数字修饰符
var number = modifiers && modifiers.number;
//获取元素值的属性
var valueBinding = getBindingAttr(el, 'value') || 'null';
// 如果是number类型,则添加checked属性
valueBinding = number ? ("_n(" + valueBinding + ")") : valueBinding;
addProp(el, 'checked', ("_q(" + value + "," + valueBinding + ")"));
//在节点添加change监听事件
addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true);
}
//选择器的v-model
function genSelect (
el,
value,
modifiers
) {
//获取数字修饰符
var number = modifiers && modifiers.number;
var selectedVal = "Array.prototype.filter" +
".call($event.target.options,function(o){return o.selected})" +
".map(function(o){var val = \"_value\" in o ? o._value : o.value;" +
"return " + (number ? '_n(val)' : 'val') + "})";
var assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]';
var code = "var $$selectedVal = " + selectedVal + ";";
code = code + " " + (genAssignmentCode(value, assignment));
addHandler(el, 'change', code, null, true);
}
// input标签 和textarea标签
function genDefaultModel (
el,
value,
modifiers
) {
// 获取type值,比如text,如果未指定则为undefined
var type = el.attrsMap.type;
// 如果v-bind:value与v-model冲突,则警告
//除了v-bind:type的输入
{
// 获取动态绑定的value值
var value$1 = el.attrsMap['v-bind:value'] || el.attrsMap[':value'];
//获取动态绑定的type值
var typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type'];
if (value$1 && !typeBinding) {
//如果动态绑定了value 且没有绑定type,则报错
var binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value';
warn$1(
binding + "=\"" + value$1 + "\" conflicts with v-model on the same element " +
'because the latter already expands to a value binding internally',
el.rawAttrsMap[binding]
);
}
}
var ref = modifiers || {};
//获取lazy修饰符
var lazy = ref.lazy;
//获取number修饰符
var number = ref.number;
//获取trim修饰符
var trim = ref.trim;
var needCompositionGuard = !lazy && type !== 'range';
//如果有lazy修饰符则绑定为change事件,否则绑定input事件
var event = lazy
? 'change'
: type === 'range'
? RANGE_TOKEN
: 'input';
var valueExpression = '$event.target.value';
//如果有trim修饰符,则在值后面加上trim()
if (trim) {
valueExpression = "$event.target.value.trim()";
}
//如果有number修饰符,则加上_n函数,就是全局的toNumber函数
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}
//返回一个表达式
var code = genAssignmentCode(value, valueExpression);
if (needCompositionGuard) {
//如果需要composing配合,则在前面加上一段if语句
code = "if($event.target.composing)return;" + code;
}
//添加一个value的prop
addProp(el, 'value', ("(" + value + ")"));
//添加event事件
addHandler(el, event, code, null, true);
if (trim || number) {
addHandler(el, 'blur', '$forceUpdate()');
}
}
// 规范化只能在运行时确定的v-model事件标记。
//将事件放在数组的第一个很重要,因为
//整个要点是确保v-model回调函数在之前被调用
// user-attached处理程序。
function normalizeEvents (on) {
/* istanbul ignore if */
if (isDef(on[RANGE_TOKEN])) {
// IE浏览器 input[type=range]只支持' change '事件
var event = isIE ? 'change' : 'input';
on[event] = [].concat(on[RANGE_TOKEN], on[event] || []);
delete on[RANGE_TOKEN];
}
/* istanbul ignore if */
if (isDef(on[CHECKBOX_RADIO_TOKEN])) {
on.change = [].concat(on[CHECKBOX_RADIO_TOKEN], on.change || []);
delete on[CHECKBOX_RADIO_TOKEN];
}
}
var target$1;
// 创建once的处理事件
function createOnceHandler$1 (event, handler, capture) {
// 在闭包中保存当前的目标元素
var _target = target$1;
return function onceHandler () {
var res = handler.apply(null, arguments);
if (res !== null) {
// 移出oncede的处理事件
remove$2(event, onceHandler, capture, _target);
}
}
}
// Firefox <= 53(特别是ESR 52)具有错误的Event.timeStamp
//实现,并且不会在事件传播之间触发微任务,因此安全的排除
var useMicrotaskFix = isUsingMicroTask && !(isFF && Number(isFF[1]) <= 53);
function add$1 (
name,
handler,
capture,
passive
) {
// 内部点击事件触发补丁,事件处理程序在补丁期间附加到外部元素,并再次触发。这
//发生的原因是浏览器在事件传播之间触发微任务滴答声。解决方案很简单:当附加处理程序时保存时间戳,
//只有传递给它的事件被触发时,处理器才会被触发
//在它被附加之后。
if (useMicrotaskFix) {
var attachedTimestamp = currentFlushTimestamp;
var original = handler;
handler = original._wrapper = function (e) {
if (
e.target === e.currentTarget ||
// 事件在处理程序附件之后触发
e.timeStamp >= attachedTimestamp ||
// 支持有bug事件的环境。时间戳的实现
// #9462 iOS 9错误:事件。timeStamp在history.pushState之后为0
// #9681 QtWebEngine事件。timeStamp为负值
e.timeStamp <= 0 ||
// #9448如果在多页的另一个文档中触发了保释事件
//电子/nw.js应用程序,自事件。timeStamp将使用不同的
//开始参考
e.target.ownerDocument !== document
) {
return original.apply(this, arguments)
}
};
}
// 使用addEventListener来实现事件绑定
target$1.addEventListener(
name,
handler,
supportsPassive
? { capture: capture, passive: passive }
: capture
);
}
// 移出事件监听
function remove$2 (
name,
handler,
capture,
_target
) {
(_target || target$1).removeEventListener(
name,
handler._wrapper || handler,
capture
);
}
//事件更新,先判断没有旧事件,就直接添加新事件,如果有旧事件先移出旧事件,在添加新事件
function updateDOMListeners (oldVnode, vnode) {
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return
}
var on = vnode.data.on || {};
var oldOn = oldVnode.data.on || {};
target$1 = vnode.elm;
normalizeEvents(on);
updateListeners(on, oldOn, add$1, remove$2, createOnceHandler$1, vnode.context);
target$1 = undefined;
}
var events = {
create: updateDOMListeners,
update: updateDOMListeners
};
/* */
var svgContainer;
function updateDOMProps (oldVnode, vnode) {
if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {
return
}
var key, cur;
// 虚拟节点
var elm = vnode.elm;
// 第一次渲染的时候没有旧节点,返回{}
var oldProps = oldVnode.data.domProps || {};
var props = vnode.data.domProps || {};
// 克隆观察到的对象
if (isDef(props.__ob__)) {
// 当前节点的props
props = vnode.data.domProps = extend({}, props);
}
// key就是节点中v-model绑定的值
for (key in oldProps) {
// 旧节点存在key而新节点中不存在的话就赋给新节点一个值为空的key
if (!(key in props)) {
elm[key] = '';
}
}
for (key in props) {
// 获取当前节点中key的值
cur = props[key];
// ignore children if the node has textContent or innerHTML,
// as these will throw away existing DOM nodes and cause removal errors
// on subsequent patches (#3360)
if (key === 'textContent' || key === 'innerHTML') {
if (vnode.children) { vnode.children.length = 0; }
if (cur === oldProps[key]) { continue }
// #6601 work around Chrome version <= 55 bug where single textNode
// replaced by innerHTML/textContent retains its parentNode property
if (elm.childNodes.length === 1) {
elm.removeChild(elm.childNodes[0]);
}
}
// 传进来的value,执行这一步
if (key === 'value' && elm.tagName !== 'PROGRESS') {
// 将值存储为_value
//非字符串值将被字符串化
elm._value = cur;
// 当值相同时,避免重新设置光标位置
var strCur = isUndef(cur) ? '' : String(cur);
// 更新数据
if (shouldUpdateValue(elm, strCur)) {
elm.value = strCur;
}
// 对传进来的不是value的key进行其他操作
} else if (key === 'innerHTML' && isSVG(elm.tagName) && isUndef(elm.innerHTML)) {
// IE不支持SVG元素的innerHTML
svgContainer = svgContainer || document.createElement('div');
svgContainer.innerHTML = "<svg>" + cur + "</svg>";
var svg = svgContainer.firstChild;
while (elm.firstChild) {
elm.removeChild(elm.firstChild);
}
while (svg.firstChild) {
elm.appendChild(svg.firstChild);
}
} else if (
//如果旧的和新的VDOM状态相同,则跳过更新。
// ' value '被单独处理,因为DOM值可能是临时的
//与VDOM状态不同步,由于焦点,合成和修改。
//这个#4521通过跳过不必要的' checked '更新。
cur !== oldProps[key]
) {
try {
elm[key] = cur;
} catch (e) {}
}
}
}
// check platforms/web/util/attrs.js acceptValue
function shouldUpdateValue (elm, checkVal) {
return (!elm.composing && (
elm.tagName === 'OPTION' ||
isNotInFocusAndDirty(elm, checkVal) ||
isDirtyWithModifiers(elm, checkVal)
))
}
function isNotInFocusAndDirty (elm, checkVal) {
// 当文本框(。Number和。trim)失去焦点,其值为不等于更新的值
var notInFocus = true;
// #6157
// 在访问文档时解决IE错误。在iframe中的activeElement
try { notInFocus = document.activeElement !== elm; } catch (e) {}
return notInFocus && elm.value !== checkVal
}
function isDirtyWithModifiers (elm, newVal) {
var value = elm.value;
// v-model运行时注入
var modifiers = elm._vModifiers;
if (isDef(modifiers)) {
if (modifiers.number) {
return toNumber(value) !== toNumber(newVal)
}
if (modifiers.trim) {
return value.trim() !== newVal.trim()
}
}
return value !== newVal
}
var domProps = {
create: updateDOMProps,
update: updateDOMProps
};
//把style 字符串 转换成对象 比如'width:100px;height:200px;' 转化成 {width:100px,height:200px}
var parseStyleText = cached(function (cssText) {
var res = {};
//匹配字符串中的 ;符号。但是不属于 (;)的 符号 如果是括号中的;不能匹配出来
var listDelimiter = /;(?![^(]*\))/g;
//:+任何字符串
var propertyDelimiter = /:(.+)/;
cssText.split(listDelimiter).forEach(function (item) {
if (item) {
var tmp = item.split(propertyDelimiter);
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim());
}
});
return res
});
// 在同一个vnode上合并静态和动态样式数据
function normalizeStyleData (data) {
var style = normalizeStyleBinding(data.style);
//静态样式在编译过程中被预处理为一个对象
// and总是一个新鲜对象,所以合并到它是安全的
return data.staticStyle
? extend(data.staticStyle, style)
: style
}
// 将可能的数组/字符串值规范化为Object
function normalizeStyleBinding (bindingStyle) {
if (Array.isArray(bindingStyle)) {
return toObject(bindingStyle)
}
if (typeof bindingStyle === 'string') {
return parseStyleText(bindingStyle)
}
return bindingStyle
}
/**
* 父组件样式应该在子组件样式之后
*以便父组件的样式可以覆盖它
*/
function getStyle (vnode, checkChild) {
var res = {};
var styleData;
if (checkChild) {
var childNode = vnode;
while (childNode.componentInstance) {
childNode = childNode.componentInstance._vnode;
if (
childNode && childNode.data &&
// 在childNode上合并静态和动态样式数据
(styleData = normalizeStyleData(childNode.data))
) {
extend(res, styleData);
}
}
}
// 在vnode上合并静态和动态样式数据
if ((styleData = normalizeStyleData(vnode.data))) {
extend(res, styleData);
}
var parentNode = vnode;
while ((parentNode = parentNode.parent)) {
// 在parentNode上合并静态和动态样式数据
if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) {
extend(res, styleData);
}
}
return res
}
/* */
var cssVarRE = /^--/;
var importantRE = /\s*!important$/;
var setProp = function (el, name, val) {
/* istanbul ignore if */
if (cssVarRE.test(name)) {
el.style.setProperty(name, val);
} else if (importantRE.test(val)) {
// 为一个声明了css样式的对象设置一个新的属性值
el.style.setProperty(hyphenate(name), val.replace(importantRE, ''), 'important');
} else {
var normalizedName = normalize(name);
if (Array.isArray(val)) {
// 一个一个地设置它们,浏览器只会设置它能识别的
for (var i = 0, len = val.length; i < len; i++) {
el.style[normalizedName] = val[i];
}
} else {
el.style[normalizedName] = val;
}
}
};
var vendorNames = ['Webkit', 'Moz', 'ms'];
var emptyStyle;
var normalize = cached(function (prop) {
emptyStyle = emptyStyle || document.createElement('div').style;
prop = camelize(prop);
if (prop !== 'filter' && (prop in emptyStyle)) {
return prop
}
var capName = prop.charAt(0).toUpperCase() + prop.slice(1);
for (var i = 0; i < vendorNames.length; i++) {
var name = vendorNames[i] + capName;
if (name in emptyStyle) {
return name
}
}
});
function updateStyle (oldVnode, vnode) {
var data = vnode.data;
var oldData = oldVnode.data;
if (isUndef(data.staticStyle) && isUndef(data.style) &&
isUndef(oldData.staticStyle) && isUndef(oldData.style)
) {
return
}
var cur, name;
var el = vnode.elm;
var oldStaticStyle = oldData.staticStyle;
var oldStyleBinding = oldData.normalizedStyle || oldData.style || {};
// 如果存在静态样式,则在执行normalizeStyleData时样式绑定已经合并到其中
var oldStyle = oldStaticStyle || oldStyleBinding;
var style = normalizeStyleBinding(vnode.data.style) || {};
//将正则化样式存储在不同的键下如果它是响应式的,确保克隆它
vnode.data.normalizedStyle = isDef(style.__ob__)
? extend({}, style)
: style;
var newStyle = getStyle(vnode, true);
for (name in oldStyle) {
if (isUndef(newStyle[name])) {
setProp(el, name, '');
}
}
for (name in newStyle) {
cur = newStyle[name];
if (cur !== oldStyle[name]) {
// Ie9设置为空没有效果,必须使用空字符串
setProp(el, name, cur == null ? '' : cur);
}
}
}
var style = {
create: updateStyle,
update: updateStyle
};
/* */
var whitespaceRE = /\s+/;
/**
*添加与SVG兼容的类,因为不支持classList
* SVG元素在IE中
*/
function addClass (el, cls) {
/* istanbul ignore if */
if (!cls || !(cls = cls.trim())) {
return
}
/* istanbul ignore else */
if (el.classList) {
if (cls.indexOf(' ') > -1) {
cls.split(whitespaceRE).forEach(function (c) { return el.classList.add(c); });
} else {
el.classList.add(cls);
}
} else {
var cur = " " + (el.getAttribute('class') || '') + " ";
if (cur.indexOf(' ' + cls + ' ') < 0) {
el.setAttribute('class', (cur + cls).trim());
}
}
}
/**
* 删除与SVG兼容的类,因为不支持classList
* SVG元素在IE中
*/
function removeClass (el, cls) {
/* istanbul ignore if */
if (!cls || !(cls = cls.trim())) {
return
}
/* istanbul ignore else */
if (el.classList) {
if (cls.indexOf(' ') > -1) {
cls.split(whitespaceRE).forEach(function (c) { return el.classList.remove(c); });
} else {
el.classList.remove(cls);
}
if (!el.classList.length) {
el.removeAttribute('class');
}
} else {
var cur = " " + (el.getAttribute('class') || '') + " ";
var tar = ' ' + cls + ' ';
while (cur.indexOf(tar) >= 0) {
cur = cur.replace(tar, ' ');
}
cur = cur.trim();
if (cur) {
el.setAttribute('class', cur);
} else {
el.removeAttribute('class');
}
}
}
// 扩展属性
function resolveTransition (def$$1) {
if (!def$$1) {
return
}
/* istanbul ignore else */
if (typeof def$$1 === 'object') {
var res = {};
if (def$$1.css !== false) {
// class对象扩展到空对象res上
extend(res, autoCssTransition(def$$1.name || 'v'));
}
// 将def上的属性扩展到res对象上
extend(res, def$$1);
return res
} else if (typeof def$$1 === 'string') {
return autoCssTransition(def$$1)
}
}
// 生成包含6个需要使用到的class对象
var autoCssTransition = cached(function (name) {
return {
enterClass: (name + "-enter"),
enterToClass: (name + "-enter-to"),
enterActiveClass: (name + "-enter-active"),
leaveClass: (name + "-leave"),
leaveToClass: (name + "-leave-to"),
leaveActiveClass: (name + "-leave-active")
}
});
var hasTransition = inBrowser && !isIE9;
var TRANSITION = 'transition';
var ANIMATION = 'animation';
// Transition property/event sniffing
var transitionProp = 'transition';
var transitionEndEvent = 'transitionend';
var animationProp = 'animation';
var animationEndEvent = 'animationend';
if (hasTransition) {
/* istanbul ignore if */
if (window.ontransitionend === undefined &&
window.onwebkittransitionend !== undefined
) {
transitionProp = 'WebkitTransition';
transitionEndEvent = 'webkitTransitionEnd';
}
if (window.onanimationend === undefined &&
window.onwebkitanimationend !== undefined
) {
animationProp = 'WebkitAnimation';
animationEndEvent = 'webkitAnimationEnd';
}
}
// binding to window is necessary to make hot reload work in IE in strict mode
var raf = inBrowser
? window.requestAnimationFrame
? window.requestAnimationFrame.bind(window)
: setTimeout
: /* istanbul ignore next */ function (fn) { return fn(); };
// 参数为 fn,即下一帧需要执行的方法
function nextFrame (fn) {
raf(function () {
raf(fn);
});
}
function addTransitionClass (el, cls) {
var transitionClasses = el._transitionClasses || (el._transitionClasses = []);
if (transitionClasses.indexOf(cls) < 0) {
transitionClasses.push(cls);
addClass(el, cls);
}
}
function removeTransitionClass (el, cls) {
if (el._transitionClasses) {
remove(el._transitionClasses, cls);
}
removeClass(el, cls);
}
// 通过 getTransitionInfo 获取到 transition 的一些信息,
// 如 type、timeout、propCount,并为 el 元素绑定上 onEnd。
function whenTransitionEnds (
el,
expectedType,
cb
) {
var ref = getTransitionInfo(el, expectedType);
var type = ref.type;
var timeout = ref.timeout;
var propCount = ref.propCount;
if (!type) { return cb() }
var event = type === TRANSITION ? transitionEndEvent : animationEndEvent;
var ended = 0;
var end = function () {
el.removeEventListener(event, onEnd);
cb();
};
var onEnd = function (e) {
if (e.target === el) {
if (++ended >= propCount) {
end();
}
}
};
setTimeout(function () {
if (ended < propCount) {
end();
}
}, timeout + 1);
el.addEventListener(event, onEnd);
}
var transformRE = /\b(transform|all)(,|$)/;
function getTransitionInfo (el, expectedType) {
var styles = window.getComputedStyle(el);
// JSDOM may return undefined for transition properties
var transitionDelays = (styles[transitionProp + 'Delay'] || '').split(', ');
var transitionDurations = (styles[transitionProp + 'Duration'] || '').split(', ');
var transitionTimeout = getTimeout(transitionDelays, transitionDurations);
var animationDelays = (styles[animationProp + 'Delay'] || '').split(', ');
var animationDurations = (styles[animationProp + 'Duration'] || '').split(', ');
var animationTimeout = getTimeout(animationDelays, animationDurations);
var type;
var timeout = 0;
var propCount = 0;
/* istanbul ignore if */
if (expectedType === TRANSITION) {
if (transitionTimeout > 0) {
type = TRANSITION;
timeout = transitionTimeout;
propCount = transitionDurations.length;
}
} else if (expectedType === ANIMATION) {
if (animationTimeout > 0) {
type = ANIMATION;
timeout = animationTimeout;
propCount = animationDurations.length;
}
} else {
timeout = Math.max(transitionTimeout, animationTimeout);
type = timeout > 0
? transitionTimeout > animationTimeout
? TRANSITION
: ANIMATION
: null;
propCount = type
? type === TRANSITION
? transitionDurations.length
: animationDurations.length
: 0;
}
var hasTransform =
type === TRANSITION &&
transformRE.test(styles[transitionProp + 'Property']);
return {
type: type,
timeout: timeout,
propCount: propCount,
hasTransform: hasTransform
}
}
function getTimeout (delays, durations) {
/* istanbul ignore next */
while (delays.length < durations.length) {
delays = delays.concat(delays);
}
return Math.max.apply(null, durations.map(function (d, i) {
return toMs(d) + toMs(delays[i])
}))
}
// Chromium的旧版本(低于61.0.3163.100)采用浮点数
//以一种与区域相关的方式,使用逗号而不是点。
//如果逗号没有被点替换,输入将被舍入(即acting . 作为一个floor函数)导致意外的行为
function toMs (s) {
return Number(s.slice(0, -1).replace(',', '.')) * 1000
}
/* */
function enter (vnode, toggleDisplay) {
var el = vnode.elm;
//现在调用leave回调函数
if (isDef(el._leaveCb)) {
el._leaveCb.cancelled = true;
el._leaveCb();
}
var data = resolveTransition(vnode.data.transition);
if (isUndef(data)) {
return
}
/* istanbul ignore if */
if (isDef(el._enterCb) || el.nodeType !== 1) {
return
}
var css = data.css;
var type = data.type;
var enterClass = data.enterClass;
var enterToClass = data.enterToClass;
var enterActiveClass = data.enterActiveClass;
var appearClass = data.appearClass;
var appearToClass = data.appearToClass;
var appearActiveClass = data.appearActiveClass;
var beforeEnter = data.beforeEnter;
var enter = data.enter;
var afterEnter = data.afterEnter;
var enterCancelled = data.enterCancelled;
var beforeAppear = data.beforeAppear;
var appear = data.appear;
var afterAppear = data.afterAppear;
var appearCancelled = data.appearCancelled;
var duration = data.duration;
// activeInstance将始终是管理这个的<transition>组件</transition>
//转换。要检查的一个边界情况是放置<转场>时
//作为子组件的根节点。那样的话,我们得核实一下
// <transition>'s parent for appear check</transition>
var context = activeInstance;
var transitionNode = activeInstance.$vnode;
while (transitionNode && transitionNode.parent) {
context = transitionNode.context;
transitionNode = transitionNode.parent;
}
// 上下文实例没有 mounted 或者 vnode 不是根节点插入的
var isAppear = !context._isMounted || !vnode.isRootInsert;
if (isAppear && !appear && appear !== '') {
return
}
// 过渡 class 处理
// 如果有定义appear且有对应的appearClass
var startClass = isAppear && appearClass
// 执行定义的appearClass
? appearClass
// 否则还是执行enterClass
: enterClass;
var activeClass = isAppear && appearActiveClass
? appearActiveClass
: enterActiveClass;
var toClass = isAppear && appearToClass
? appearToClass
: enterToClass;
// 钩子函数处理
var beforeEnterHook = isAppear
? (beforeAppear || beforeEnter)
: beforeEnter;
var enterHook = isAppear
? (typeof appear === 'function' ? appear : enter)
: enter;
var afterEnterHook = isAppear
? (afterAppear || afterEnter)
: afterEnter;
var enterCancelledHook = isAppear
? (appearCancelled || enterCancelled)
: enterCancelled;
// 获取 enter 动画执行时间
var explicitEnterDuration = toNumber(
isObject(duration)
? duration.enter
: duration
);
if (explicitEnterDuration != null) {
checkDuration(explicitEnterDuration, 'enter', vnode);
}
// 用户是否想介入控制 css 动画
var expectsCSS = css !== false && !isIE9;
var userWantsControl = getHookArgumentsLength(enterHook);
var cb = el._enterCb = once(function () {
// 动画受 css 控制,则移除掉 toClass 和 activeClass
if (expectsCSS) {
// 为当前元素 el 增加指定的 class
removeTransitionClass(el, toClass);
removeTransitionClass(el, activeClass);
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, startClass);
}
// 判定过渡是否取消,若取消了,直接移除 startClass 并执行 enterCancelledHook
enterCancelledHook && enterCancelledHook(el);
} else {
afterEnterHook && afterEnterHook(el);
}
el._enterCb = null;
});
if (!vnode.data.show) {
// 对 insert 钩子函数进行合并
mergeVNodeHook(vnode, 'insert', function () {
var parent = el.parentNode;
var pendingNode = parent && parent._pending && parent._pending[vnode.key];
if (pendingNode &&
pendingNode.tag === vnode.tag &&
pendingNode.elm._leaveCb
) {
pendingNode.elm._leaveCb();
}
enterHook && enterHook(el, cb);
});
}
//执行 beforeEnterHook 钩子,并将 DOM 节点 el 传入
beforeEnterHook && beforeEnterHook(el);
// 判断是否希望通过 css 来控制动画,如果是 true 则,执行 addTransitionClass() 方法为节点加上 startClass 和 activeClass
if (expectsCSS) {
addTransitionClass(el, startClass);
addTransitionClass(el, activeClass);
// 执行 nextFrame() 进入下一帧,下一帧主要是移除掉上一帧增加好的 class
nextFrame(function () {
removeTransitionClass(el, startClass);
// 判断过渡是否取消,如未取消,则加上 toClass 过渡类
if (!cb.cancelled) {
addTransitionClass(el, toClass);
if (!userWantsControl) {
// 如果用户没有通过 enterHook 钩子函数来控制动画,此时若用户指定了 duration 时间,则执行 setTimeout 进行 duration 时长的延时
if (isValidDuration(explicitEnterDuration)) {
setTimeout(cb, explicitEnterDuration);
} else {
// 执行 whenTransitionEnds 决定 cb 的执行时机
whenTransitionEnds(el, type, cb);
}
}
}
});
}
if (vnode.data.show) {
toggleDisplay && toggleDisplay();
enterHook && enterHook(el, cb);
}
if (!expectsCSS && !userWantsControl) {
cb();
}
}
function leave (vnode, rm) {
var el = vnode.elm;
// 如果进入enter时,_leaveCb没执行,立刻执行
if (isDef(el._enterCb)) {
// 执行了_leaveCb的标记
el._enterCb.cancelled = true;
// cb._leaveCb执行后会变成null
el._enterCb();
}
var data = resolveTransition(vnode.data.transition);
if (isUndef(data) || el.nodeType !== 1) {
return rm()
}
/* istanbul ignore if */
if (isDef(el._leaveCb)) {
return
}
var css = data.css;
var type = data.type;
var leaveClass = data.leaveClass;
var leaveToClass = data.leaveToClass;
var leaveActiveClass = data.leaveActiveClass;
var beforeLeave = data.beforeLeave;
var leave = data.leave;
var afterLeave = data.afterLeave;
var leaveCancelled = data.leaveCancelled;
var delayLeave = data.delayLeave;
var duration = data.duration;
var expectsCSS = css !== false && !isIE9;
// 传入enter钩子
// 如果钩子里enter函数的参数大于1,说明有传入done函数,表示用户想要自己控制
// 这也是为什么enter里动画结束后需要调用done函数
var userWantsControl = getHookArgumentsLength(leave);
var explicitLeaveDuration = toNumber(
isObject(duration)
? duration.leave
: duration
);
if (isDef(explicitLeaveDuration)) {
checkDuration(explicitLeaveDuration, 'leave', vnode);
}
// 这里定义了el._enterCb函数,对应leave里就是el._leaveCb
var cb = el._leaveCb = once(function () {
if (el.parentNode && el.parentNode._pending) {
el.parentNode._pending[vnode.key] = null;
}
if (expectsCSS) {
// 移除v-leave-to
removeTransitionClass(el, leaveToClass);
// 移除v-leave-active
removeTransitionClass(el, leaveActiveClass);
}
// 如果在leave的状态里,enter状态的cb函数没执行,则执行enterCancelled钩子
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, leaveClass);
}
leaveCancelled && leaveCancelled(el);
} else {
rm();
// 否则正常的执行afterEnter钩子
afterLeave && afterLeave(el);
}
el._leaveCb = null;
});
if (delayLeave) {
delayLeave(performLeave);
} else {
performLeave();
}
function performLeave () {
// the delayed leave may have already been cancelled
if (cb.cancelled) {
return
}
// record leaving element
if (!vnode.data.show && el.parentNode) {
(el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key)] = vnode;
}
beforeLeave && beforeLeave(el);
if (expectsCSS) {
addTransitionClass(el, leaveClass);
addTransitionClass(el, leaveActiveClass);
nextFrame(function () {
removeTransitionClass(el, leaveClass);
if (!cb.cancelled) {
addTransitionClass(el, leaveToClass);
if (!userWantsControl) {
if (isValidDuration(explicitLeaveDuration)) {
setTimeout(cb, explicitLeaveDuration);
} else {
whenTransitionEnds(el, type, cb);
}
}
}
});
}
leave && leave(el, cb);
if (!expectsCSS && !userWantsControl) {
cb();
}
}
}
// 仅在dev模式下使用
function checkDuration (val, name, vnode) {
if (typeof val !== 'number') {
warn(
"<transition> explicit " + name + " duration is not a valid number - " +
"got " + (JSON.stringify(val)) + ".",
vnode.context
);
} else if (isNaN(val)) {
warn(
"<transition> explicit " + name + " duration is NaN - " +
'the duration expression might be incorrect.',
vnode.context
);
}
}
function isValidDuration (val) {
return typeof val === 'number' && !isNaN(val)
}
/**
将转换钩子的参数长度规范化。问题可能是:
* 一个合并的钩子(调用者)与原来的。fns
一个封装的组件方法(check ._length)
* 一个普通函数(.length)
*/
// 用于判断 enterHook
function getHookArgumentsLength (fn) {
if (isUndef(fn)) {
return false
}
var invokerFns = fn.fns;
if (isDef(invokerFns)) {
return getHookArgumentsLength(
Array.isArray(invokerFns)
? invokerFns[0]
: invokerFns
)
} else {
return (fn._length || fn.length) > 1
}
}
// 在 create 和 activate 的时候执行 enter()
function _enter (_, vnode) {
if (vnode.data.show !== true) {
enter(vnode);
}
}
var transition = inBrowser ? {
create: _enter,
activate: _enter,
remove: function remove$$1 (vnode, rm) {
// remove 的时候执行 leave()
if (vnode.data.show !== true) {
leave(vnode, rm);
} else {
rm();
}
}
} : {};
var platformModules = [
attrs,
klass,
events,
domProps,
style,
transition
];
/* */
//指令模块应该最后应用
//内置模块已经被应用。
var modules = platformModules.concat(baseModules);
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
/**
*不类型检查这个文件,因为flow不喜欢附加
*元素的属性。
*/
/* istanbul ignore if */
if (isIE9) {
// http://www.matts411.com/post/internet-explorer-9-oninput/
document.addEventListener('selectionchange', function () {
var el = document.activeElement;
if (el && el.vmodel) {
trigger(el, 'input');
}
});
}
var directive = {
inserted: function inserted (el, binding, vnode, oldVnode) {
if (vnode.tag === 'select') {
// #6903
if (oldVnode.elm && !oldVnode.elm._vOptions) {
mergeVNodeHook(vnode, 'postpatch', function () {
directive.componentUpdated(el, binding, vnode);
});
} else {
setSelected(el, binding, vnode.context);
}
el._vOptions = [].map.call(el.options, getValue);
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
el._vModifiers = binding.modifiers;
if (!binding.modifiers.lazy) {
el.addEventListener('compositionstart', onCompositionStart);
el.addEventListener('compositionend', onCompositionEnd);
//当Safari < 10.2 & UIWebView不触发compositionend时
//在确定构图选择之前切换焦点
//这也修复了一些浏览器的问题,如iOS Chrome
//触发“change”而不是“input”自动完成。
el.addEventListener('change', onCompositionEnd);
/* istanbul ignore if */
if (isIE9) {
el.vmodel = true;
}
}
}
},
componentUpdated: function componentUpdated (el, binding, vnode) {
if (vnode.tag === 'select') {
setSelected(el, binding, vnode.context);
//如果v-for渲染的选项发生了变化,
//该值可能与渲染选项不同步。
//检测这种情况并过滤掉不再匹配的值
//在DOM中的选项。
var prevOptions = el._vOptions;
var curOptions = el._vOptions = [].map.call(el.options, getValue);
if (curOptions.some(function (o, i) { return !looseEqual(o, prevOptions[i]); })) {
//触发改变事件
//没有找到匹配的选项,至少一个值
var needReset = el.multiple
? binding.value.some(function (v) { return hasNoMatchingOption(v, curOptions); })
: binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions);
if (needReset) {
trigger(el, 'change');
}
}
}
}
};
function setSelected (el, binding, vm) {
actuallySetSelected(el, binding, vm);
/* istanbul ignore if */
if (isIE || isEdge) {
setTimeout(function () {
actuallySetSelected(el, binding, vm);
}, 0);
}
}
function actuallySetSelected (el, binding, vm) {
var value = binding.value;
var isMultiple = el.multiple;
if (isMultiple && !Array.isArray(value)) {
warn(
"<select multiple v-model=\"" + (binding.expression) + "\"> " +
"expects an Array value for its binding, but got " + (Object.prototype.toString.call(value).slice(8, -1)),
vm
);
return
}
var selected, option;
for (var i = 0, l = el.options.length; i < l; i++) {
option = el.options[i];
if (isMultiple) {
selected = looseIndexOf(value, getValue(option)) > -1;
if (option.selected !== selected) {
option.selected = selected;
}
} else {
if (looseEqual(getValue(option), value)) {
if (el.selectedIndex !== i) {
el.selectedIndex = i;
}
return
}
}
}
if (!isMultiple) {
el.selectedIndex = -1;
}
}
function hasNoMatchingOption (value, options) {
return options.every(function (o) { return !looseEqual(o, value); })
}
function getValue (option) {
return '_value' in option
? option._value
: option.value
}
function onCompositionStart (e) {
e.target.composing = true;
}
function onCompositionEnd (e) {
//防止无缘无故触发输入事件
if (!e.target.composing) { return }
e.target.composing = false;
trigger(e.target, 'input');
}
function trigger (el, type) {
var e = document.createEvent('HTMLEvents');
e.initEvent(type, true, true);
el.dispatchEvent(e);
}
/* */
// 递归地搜索在组件根内部定义的可能转换
function locateNode (vnode) {
return vnode.componentInstance && (!vnode.data || !vnode.data.transition)
? locateNode(vnode.componentInstance._vnode)
: vnode
}
// 控制 el 的 display 属性
var show = {
bind: function bind (el, ref, vnode) {
var value = ref.value;
vnode = locateNode(vnode);
var transition$$1 = vnode.data && vnode.data.transition;
var originalDisplay = el.__vOriginalDisplay =
el.style.display === 'none' ? '' : el.style.display;
if (value && transition$$1) {
vnode.data.show = true;
enter(vnode, function () {
el.style.display = originalDisplay;
});
} else {
el.style.display = value ? originalDisplay : 'none';
}
},
update: function update (el, ref, vnode) {
var value = ref.value;
var oldValue = ref.oldValue;
/* istanbul ignore if */
if (!value === !oldValue) { return }
vnode = locateNode(vnode);
var transition$$1 = vnode.data && vnode.data.transition;
if (transition$$1) {
vnode.data.show = true;
if (value) {
enter(vnode, function () {
el.style.display = el.__vOriginalDisplay;
});
} else {
leave(vnode, function () {
el.style.display = 'none';
});
}
} else {
el.style.display = value ? el.__vOriginalDisplay : 'none';
}
},
unbind: function unbind (
el,
binding,
vnode,
oldVnode,
isDestroy
) {
if (!isDestroy) {
el.style.display = el.__vOriginalDisplay;
}
}
};
// 平台指令
var platformDirectives = {
model: directive,
show: show
};
/*过渡Props对象*/
var transitionProps = {
name: String,
appear: Boolean,
css: Boolean,
mode: String,
type: String,
enterClass: String,
leaveClass: String,
enterToClass: String,
leaveToClass: String,
enterActiveClass: String,
leaveActiveClass: String,
appearClass: String,
appearActiveClass: String,
appearToClass: String,
duration: [Number, String, Object]
};
//如果子组件也是一个抽象组件,例如:<keep-alive></keep-alive>
//我们想要递归地检索要渲染的真实组件
function getRealChild (vnode) {
var compOptions = vnode && vnode.componentOptions;
if (compOptions && compOptions.Ctor.options.abstract) {
return getRealChild(getFirstComponentChild(compOptions.children))
} else {
return vnode
}
}
// 从组件实例上获取过渡需要的 data 数据
function extractTransitionData (comp) {
var data = {};
var options = comp.$options;
// 遍历当前组件的 options.propsData 并赋值给 data
for (var key in options.propsData) {
data[key] = comp[key];
}
//提取监听器并直接传递给转换方法
var listeners = options._parentListeners;
// 遍历父组件的事件并将其赋值给 data
for (var key$1 in listeners) {
data[camelize(key$1)] = listeners[key$1];
}
return data
}
// 占位提示
function placeholder (h, rawChild) {
if (/\d-keep-alive$/.test(rawChild.tag)) {
return h('keep-alive', {
props: rawChild.componentOptions.propsData
})
}
}
// 往上一层一层寻找父节点是否也拥有 transition 属性
function hasParentTransition (vnode) {
while ((vnode = vnode.parent)) {
if (vnode.data.transition) {
return true
}
}
}
// 判断子对象是否相同
function isSameChild (child, oldChild) {
return oldChild.key === child.key && oldChild.tag === child.tag
}
var isNotTextNode = function (c) { return c.tag || isAsyncPlaceholder(c); };
var isVShowDirective = function (d) { return d.name === 'show'; };
var Transition = {
name: 'transition',
props: transitionProps,
abstract: true,
render: function render (h) {
var this$1 = this;
// 首先从默认插槽中获取到 <transition> 包裹的子节点
var children = this.$slots.default;
if (!children) {
return
}
// 对其进行 filter 过滤,将文本节点以及空格给过滤掉
children = children.filter(isNotTextNode);
/* istanbul ignore if */
if (!children.length) {
return
}
// 如果不存在子节点则直接 return,如果子节点为多个,则报错,因为 <transition> 组件只能有一个子节点
if (children.length > 1) {
warn(
'<transition> can only be used on a single element. Use ' +
'<transition-group> for lists.',
this.$parent
);
}
var mode = this.mode;
// 判断 mode 是否为 in-out 和 out-in 两种模式,如果不是,直接报错
if (mode && mode !== 'in-out' && mode !== 'out-in'
) {
warn(
'invalid <transition> mode: ' + mode,
this.$parent
);
}
// rawChild 为 <transition> 组件包裹的第一个 vnode 子节点
var rawChild = children[0];
//如果这是一个组件的根节点和组件的
//父容器节点也有过渡。
if (hasParentTransition(this.$vnode)) {
return rawChild
}
//将转换数据应用到child
//使用getRealChild()来忽略抽象组件,例如keep-alive
var child = getRealChild(rawChild);
/* istanbul ignore if */
if (!child) {
return rawChild
}
if (this._leaving) {
return placeholder(h, rawChild)
}
//确保一个键对于vnode类型和这个转换是唯一的组件实例。此键将用于删除挂起的离开节点在进入。
var id = "__transition-" + (this._uid) + "-";
child.key = child.key == null
? child.isComment
? id + 'comment'
: id + child.tag
: isPrimitive(child.key)
? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
: child.key;
var data = (child.data || (child.data = {})).transition = extractTransitionData(this);
var oldRawChild = this._vnode;
var oldChild = getRealChild(oldRawChild);
//标记v-show过渡模块可以将控制权交给指令
if (child.data.directives && child.data.directives.some(isVShowDirective)) {
child.data.show = true;
}
// 对新旧 child 进行比较,并对部分钩子函数进行 hook merge 等操作
if (
oldChild &&
oldChild.data &&
!isSameChild(child, oldChild) &&
!isAsyncPlaceholder(oldChild) &&
// 组件根节点是一个注释节点
!(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)
) {
//将旧的子转换数据替换为新的子转换数据对于动态转换很重要!
var oldData = oldChild.data.transition = extend({}, data);
// 处理过渡模式
if (mode === 'out-in') {
// 当leave结束时返回占位符节点和队列更新
this._leaving = true;
mergeVNodeHook(oldData, 'afterLeave', function () {
this$1._leaving = false;
this$1.$forceUpdate();
});
return placeholder(h, rawChild)
} else if (mode === 'in-out') {
if (isAsyncPlaceholder(child)) {
return oldRawChild
}
var delayedLeave;
var performLeave = function () { delayedLeave(); };
mergeVNodeHook(data, 'afterEnter', performLeave);
mergeVNodeHook(data, 'enterCancelled', performLeave);
mergeVNodeHook(oldData, 'delayLeave', function (leave) { delayedLeave = leave; });
}
}
return rawChild
}
};
/* */
var props = extend({
tag: String,
moveClass: String
}, transitionProps);
delete props.mode;
var TransitionGroup = {
props: props,
beforeMount: function beforeMount () {
var this$1 = this;
var update = this._update;
this._update = function (vnode, hydrating) {
var restoreActiveInstance = setActiveInstance(this$1);
// force removing pass
this$1.__patch__(
this$1._vnode,
this$1.kept,
false, // hydrating
true // removeOnly (!important, avoids unnecessary moves)
);
this$1._vnode = this$1.kept;
restoreActiveInstance();
update.call(this$1, vnode, hydrating);
};
},
render: function render (h) {
var tag = this.tag || this.$vnode.data.tag || 'span';
var map = Object.create(null);
var prevChildren = this.prevChildren = this.children;
var rawChildren = this.$slots.default || [];
var children = this.children = [];
var transitionData = extractTransitionData(this);
for (var i = 0; i < rawChildren.length; i++) {
var c = rawChildren[i];
if (c.tag) {
if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {
children.push(c);
map[c.key] = c
;(c.data || (c.data = {})).transition = transitionData;
} else {
var opts = c.componentOptions;
var name = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag;
warn(("<transition-group> children must be keyed: <" + name + ">"));
}
}
}
if (prevChildren) {
var kept = [];
var removed = [];
for (var i$1 = 0; i$1 < prevChildren.length; i$1++) {
var c$1 = prevChildren[i$1];
c$1.data.transition = transitionData;
c$1.data.pos = c$1.elm.getBoundingClientRect();
if (map[c$1.key]) {
kept.push(c$1);
} else {
removed.push(c$1);
}
}
this.kept = h(tag, null, kept);
this.removed = removed;
}
return h(tag, null, children)
},
updated: function updated () {
var children = this.prevChildren;
var moveClass = this.moveClass || ((this.name || 'v') + '-move');
if (!children.length || !this.hasMove(children[0].elm, moveClass)) {
return
}
// we divide the work into three loops to avoid mixing DOM reads and writes
// in each iteration - which helps prevent layout thrashing.
children.forEach(callPendingCbs);
children.forEach(recordPosition);
children.forEach(applyTranslation);
// force reflow to put everything in position
// assign to this to avoid being removed in tree-shaking
// $flow-disable-line
this._reflow = document.body.offsetHeight;
children.forEach(function (c) {
if (c.data.moved) {
var el = c.elm;
var s = el.style;
addTransitionClass(el, moveClass);
s.transform = s.WebkitTransform = s.transitionDuration = '';
el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {
if (e && e.target !== el) {
return
}
if (!e || /transform$/.test(e.propertyName)) {
el.removeEventListener(transitionEndEvent, cb);
el._moveCb = null;
removeTransitionClass(el, moveClass);
}
});
}
});
},
methods: {
hasMove: function hasMove (el, moveClass) {
/* istanbul ignore if */
if (!hasTransition) {
return false
}
/* istanbul ignore if */
if (this._hasMove) {
return this._hasMove
}
// Detect whether an element with the move class applied has
// CSS transitions. Since the element may be inside an entering
// transition at this very moment, we make a clone of it and remove
// all other transition classes applied to ensure only the move class
// is applied.
var clone = el.cloneNode();
if (el._transitionClasses) {
el._transitionClasses.forEach(function (cls) { removeClass(clone, cls); });
}
addClass(clone, moveClass);
clone.style.display = 'none';
this.$el.appendChild(clone);
var info = getTransitionInfo(clone);
this.$el.removeChild(clone);
return (this._hasMove = info.hasTransform)
}
}
};
// 判断每个节点前一帧的过渡动画是否执行完毕,如果没有执行完,则提前执行 _moveCb() 和 _enterCb()
function callPendingCbs (c) {
/* istanbul ignore if */
if (c.elm._moveCb) {
c.elm._moveCb();
}
/* istanbul ignore if */
if (c.elm._enterCb) {
c.elm._enterCb();
}
}
// 记录每个节点的新位置
function recordPosition (c) {
c.data.newPos = c.elm.getBoundingClientRect();
}
// 分别获取节点新旧位置,并计算差值,若存在差值,
// 则通过设置节点的 transform 属性将需要移动的节点位置偏移到之前的位置,为列表 move 做准备
function applyTranslation (c) {
var oldPos = c.data.pos;
var newPos = c.data.newPos;
var dx = oldPos.left - newPos.left;
var dy = oldPos.top - newPos.top;
if (dx || dy) {
c.data.moved = true;
var s = c.elm.style;
s.transform = s.WebkitTransform = "translate(" + dx + "px," + dy + "px)";
s.transitionDuration = '0s';
}
}
var platformComponents = {
Transition: Transition,
TransitionGroup: TransitionGroup
};
/* */
// install platform specific utils
Vue.config.mustUseProp = mustUseProp;
Vue.config.isReservedTag = isReservedTag;
Vue.config.isReservedAttr = isReservedAttr;
Vue.config.getTagNamespace = getTagNamespace;
Vue.config.isUnknownElement = isUnknownElement;
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives);
extend(Vue.options.components, platformComponents);
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop;
// public mount method
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
setTimeout(function () {
if (config.devtools) {
if (devtools) {
devtools.emit('init', Vue);
} else {
console[console.info ? 'info' : 'log'](
'Download the Vue Devtools extension for a better development experience:\n' +
'https://github.com/vuejs/vue-devtools'
);
}
}
if (config.productionTip !== false &&
typeof console !== 'undefined'
) {
console[console.info ? 'info' : 'log'](
"You are running Vue in development mode.\n" +
"Make sure to turn on production mode when deploying for production.\n" +
"See more tips at https://vuejs.org/guide/deployment.html"
);
}
}, 0);
}
/* */
var defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;
var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g;
var buildRegex = cached(function (delimiters) {
var open = delimiters[0].replace(regexEscapeRE, '\\$&');
var close = delimiters[1].replace(regexEscapeRE, '\\$&');
return new RegExp(open + '((?:.|\\n)+?)' + close, 'g')
});
function parseText (
text,
delimiters
) {
var tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE;
if (!tagRE.test(text)) {
return
}
var tokens = [];
var rawTokens = [];
var lastIndex = tagRE.lastIndex = 0;
var match, index, tokenValue;
while ((match = tagRE.exec(text))) {
index = match.index;
// push text token
if (index > lastIndex) {
rawTokens.push(tokenValue = text.slice(lastIndex, index));
tokens.push(JSON.stringify(tokenValue));
}
// tag token
var exp = parseFilters(match[1].trim());
tokens.push(("_s(" + exp + ")"));
rawTokens.push({ '@binding': exp });
lastIndex = index + match[0].length;
}
if (lastIndex < text.length) {
rawTokens.push(tokenValue = text.slice(lastIndex));
tokens.push(JSON.stringify(tokenValue));
}
return {
expression: tokens.join('+'),
tokens: rawTokens
}
}
/* */
function transformNode (el, options) {
var warn = options.warn || baseWarn;
var staticClass = getAndRemoveAttr(el, 'class');
if (staticClass) {
var res = parseText(staticClass, options.delimiters);
if (res) {
warn(
"class=\"" + staticClass + "\": " +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div class="{{ val }}">, use <div :class="val">.',
el.rawAttrsMap['class']
);
}
}
if (staticClass) {
el.staticClass = JSON.stringify(staticClass);
}
var classBinding = getBindingAttr(el, 'class', false /* getStatic */);
if (classBinding) {
el.classBinding = classBinding;
}
}
function genData (el) {
var data = '';
if (el.staticClass) {
data += "staticClass:" + (el.staticClass) + ",";
}
if (el.classBinding) {
data += "class:" + (el.classBinding) + ",";
}
return data
}
var klass$1 = {
staticKeys: ['staticClass'],
transformNode: transformNode,
genData: genData
};
/* */
function transformNode$1 (el, options) {
var warn = options.warn || baseWarn;
var staticStyle = getAndRemoveAttr(el, 'style');
if (staticStyle) {
/* istanbul ignore if */
{
var res = parseText(staticStyle, options.delimiters);
if (res) {
warn(
"style=\"" + staticStyle + "\": " +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div style="{{ val }}">, use <div :style="val">.',
el.rawAttrsMap['style']
);
}
}
el.staticStyle = JSON.stringify(parseStyleText(staticStyle));
}
var styleBinding = getBindingAttr(el, 'style', false /* getStatic */);
if (styleBinding) {
el.styleBinding = styleBinding;
}
}
function genData$1 (el) {
var data = '';
if (el.staticStyle) {
data += "staticStyle:" + (el.staticStyle) + ",";
}
if (el.styleBinding) {
data += "style:(" + (el.styleBinding) + "),";
}
return data
}
var style$1 = {
staticKeys: ['staticStyle'],
transformNode: transformNode$1,
genData: genData$1
};
/* */
var decoder;
var he = {
decode: function decode (html) {
decoder = decoder || document.createElement('div');
decoder.innerHTML = html;
return decoder.textContent
}
};
/* */
var isUnaryTag = makeMap(
'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
'link,meta,param,source,track,wbr'
);
// 你可以有意保留的元素(它们会自动关闭)
var canBeLeftOpenTag = makeMap(
'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source'
);
// HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3
// Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content
var isNonPhrasingTag = makeMap(
'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' +
'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' +
'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' +
'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' +
'title,tr,track'
);
/**
* 不需要对该文件进行类型检查,因为它主要是供应商代码。
*/
// 用于解析标记和属性的正则表达式
var attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
var dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
var ncname = "[a-zA-Z_][\\-\\.0-9_a-zA-Z" + (unicodeRegExp.source) + "]*";
var qnameCapture = "((?:" + ncname + "\\:)?" + ncname + ")";
var startTagOpen = new RegExp(("^<" + qnameCapture));
var startTagClose = /^\s*(\/?)>/;
var endTag = new RegExp(("^<\\/" + qnameCapture + "[^>]*>"));
var doctype = /^<!DOCTYPE [^>]+>/i;
// escape -避免在页面内联时作为HTML注释传递
var comment = /^<!\--/;
var conditionalComment = /^<!\[/;
// 特殊元素(可以包含任何内容)
var isPlainTextElement = makeMap('script,style,textarea', true);
var reCache = {};
var decodingMap = {
'<': '<',
'>': '>',
'"': '"',
'&': '&',
' ': '\n',
'	': '\t',
''': "'"
};
var encodedAttr = /&(?:lt|gt|quot|amp|#39);/g;
var encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#39|#10|#9);/g;
// #5992
var isIgnoreNewlineTag = makeMap('pre,textarea', true);
var shouldIgnoreFirstNewline = function (tag, html) { return tag && isIgnoreNewlineTag(tag) && html[0] === '\n'; };
function decodeAttr (value, shouldDecodeNewlines) {
var re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr;
return value.replace(re, function (match) { return decodingMap[match]; })
}
// 处理 template 匹配标签,再传入 start,end,chars 等方法
function parseHTML (html, options) {
var stack = [];
var expectHTML = options.expectHTML;
var isUnaryTag$$1 = options.isUnaryTag || no;
var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;
var index = 0; //解析的索引
var last, lastTag;// 相当于栈顶指针,不过是用tagName来做栈顶指针的
while (html) {
last = html;
// Make sure we're not in a plaintext content element like script/style
if (!lastTag || !isPlainTextElement(lastTag)) {
var textEnd = html.indexOf('<');
if (textEnd === 0) {
if (comment.test(html)) {
//说明是注释开头
var commentEnd = html.indexOf('-->');
if (commentEnd >= 0) {
if (options.shouldKeepComment) {
// 保存注释 <!--四个字符
options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3);
}
advance(commentEnd + 3);
continue
}
}
// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
if (conditionalComment.test(html)) {
var conditionalEnd = html.indexOf(']>');
if (conditionalEnd >= 0) {
advance(conditionalEnd + 2);
continue
}
}
// Doctype:
var doctypeMatch = html.match(doctype);
if (doctypeMatch) {
advance(doctypeMatch[0].length);
continue
}
/* 把带空格和纯文本放一起讨论 把空格和文本都设成text 传入char */
// 如果匹配到结尾标签
var endTagMatch = html.match(endTag);
if (endTagMatch) {
var curIndex = index;
advance(endTagMatch[0].length);
parseEndTag(endTagMatch[1], curIndex, index);
continue
}
// Start tag:
var startTagMatch = parseStartTag();
if (startTagMatch) {
handleStartTag(startTagMatch);
if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
advance(1);
}
continue
}
}
var text = (void 0), rest = (void 0), next = (void 0);
if (textEnd >= 0) {
rest = html.slice(textEnd);
while (
!endTag.test(rest) &&
!startTagOpen.test(rest) &&
!comment.test(rest) &&
!conditionalComment.test(rest)
) {
// < in plain text, be forgiving and treat it as text
next = rest.indexOf('<', 1);
if (next < 0) { break }
textEnd += next;
rest = html.slice(textEnd);
}
text = html.substring(0, textEnd);
}
if (textEnd < 0) {
text = html;
}
if (text) {
advance(text.length);
}
if (options.chars && text) {
options.chars(text, index - text.length, index);
}
} else {
var endTagLength = 0;
var stackedTag = lastTag.toLowerCase();
var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'));
var rest$1 = html.replace(reStackedTag, function (all, text, endTag) {
endTagLength = endTag.length;
if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
text = text
.replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298
.replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
}
if (shouldIgnoreFirstNewline(stackedTag, text)) {
text = text.slice(1);
}
if (options.chars) {
options.chars(text);
}
return ''
});
index += html.length - rest$1.length;
html = rest$1;
parseEndTag(stackedTag, index - endTagLength, index);
}
if (html === last) {
options.chars && options.chars(html);
if (!stack.length && options.warn) {
options.warn(("Mal-formatted tag at end of template: \"" + html + "\""), { start: index + html.length });
}
break
}
}
// Clean up any remaining tags
parseEndTag();
function advance (n) {
index += n;
html = html.substring(n);
}
function parseStartTag () {
// 匹配开始标签的左边开头部分
var start = html.match(startTagOpen);
if (start) {
var match = {
tagName: start[1],
attrs: [],
start: index
};
advance(start[0].length);
// 循环解析开始标签上的每一个属性键值对
var end, attr;
while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {
attr.start = index;
advance(attr[0].length);
attr.end = index;
match.attrs.push(attr);
}
// 匹配到开始标签的闭合部分,至此开始标签解析结束
if (end) {
match.unarySlash = end[1];
advance(end[0].length);
match.end = index;
return match
}
}
}
// 格式化attr,把标签入栈,方便匹配,调用start函数,创建AST
function handleStartTag (match) {
var tagName = match.tagName;
var unarySlash = match.unarySlash;
if (expectHTML) {
if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
parseEndTag(lastTag);
}
if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {
parseEndTag(tagName);
}
}
// 判断是否闭合标签, 如果是自闭和的, 那么不入checkStack栈
var unary = isUnaryTag$$1(tagName) || !!unarySlash;
var l = match.attrs.length;
var attrs = new Array(l);
for (var i = 0; i < l; i++) {
var args = match.attrs[i];
var value = args[3] || args[4] || args[5] || '';
var shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'
? options.shouldDecodeNewlinesForHref
: options.shouldDecodeNewlines;
attrs[i] = {
name: args[1],
value: decodeAttr(value, shouldDecodeNewlines)
};
if (options.outputSourceRange) {
attrs[i].start = args.start + args[0].match(/^\s*/).length;
attrs[i].end = args.end;
}
}
if (!unary) {
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end });
lastTag = tagName;
}
if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end);
}
}
function parseEndTag (tagName, start, end) {
var pos, lowerCasedTagName;
if (start == null) { start = index; }
if (end == null) { end = index; }
// 对endTag通过checkStack进行匹配, 如果匹配到了则出栈, 维护栈顶指针,并调用end方法
if (tagName) {
// 开始再checkStack中匹配
lowerCasedTagName = tagName.toLowerCase();
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos].lowerCasedTag === lowerCasedTagName) {
break
}
}
} else {
// 如果没有提供标签名称
pos = 0;
}
}
// 匹配成功, 但是不一定匹配正确, 只有栈顶元素和你匹配成功才算正确
if (pos >= 0) {
// 关闭所有打开的元素,向上堆栈
for (var i = stack.length - 1; i >= pos; i--) {
if (i > pos || !tagName &&
options.warn
) {
options.warn(
("tag <" + (stack[i].tag) + "> has no matching end tag."),
{ start: stack[i].start, end: stack[i].end }
);
}
// 匹配成功, 调用end函数把它从AST栈中弹出
if (options.end) {
options.end(stack[i].tag, start, end);
}
}
//从堆栈中移除打开的元素
stack.length = pos;
lastTag = pos && stack[pos - 1].tag;
} else if (lowerCasedTagName === 'br') {
if (options.start) {
options.start(tagName, [], true, start, end);
}
} else if (lowerCasedTagName === 'p') {
if (options.start) {
options.start(tagName, [], false, start, end);
}
if (options.end) {
options.end(tagName, start, end);
}
}
}
}
/* */
var onRE = /^@|^v-on:/;
var dirRE = /^v-|^@|^:|^#/;
var forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
var forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/;
var stripParensRE = /^\(|\)$/g;
var dynamicArgRE = /^\[.*\]$/;
var argRE = /:(.*)$/;
var bindRE = /^:|^\.|^v-bind:/;
var modifierRE = /\.[^.\]]+(?=[^\]]*$)/g;
var slotRE = /^v-slot(:|$)|^#/;
var lineBreakRE = /[\r\n]/;
var whitespaceRE$1 = /[ \f\t\r\n]+/g;
var invalidAttributeRE = /[\s"'<>\/=]/;
var decodeHTMLCached = cached(he.decode);
var emptySlotScopeToken = "_empty_";
// 可配置的状态
var warn$2;
var delimiters;
var transforms;
var preTransforms;
var postTransforms;
var platformIsPreTag;
var platformMustUseProp;
var platformGetTagNamespace;
var maybeComponent;
// 构造一个 AST 元素节点,每解析一个标签就要生成一个这样的 AST 元素节点,除了根元素,其它节点一般都有一个 parent 元素
function createASTElement (
tag,
attrs,
parent
) {
return {
type: 1,
tag: tag,
attrsList: attrs,
attrsMap: makeAttrsMap(attrs),
rawAttrsMap: {},
parent: parent,
children: []
}
}
/**
* 转换HTML字符串到AST(抽象语法树)
*/
function parse (
template,
options
) {
warn$2 = options.warn || baseWarn;
platformIsPreTag = options.isPreTag || no;
platformMustUseProp = options.mustUseProp || no;
platformGetTagNamespace = options.getTagNamespace || no;
// 是否是保留标签
var isReservedTag = options.isReservedTag || no;
// el.component非空 或者 不是预留的标签 就认为是组件
maybeComponent = function (el) { return !!(
el.component ||
el.attrsMap[':is'] ||
el.attrsMap['v-bind:is'] ||
!(el.attrsMap.is ? isReservedTag(el.attrsMap.is) : isReservedTag(el.tag))
); };
// pluckModuleFunction 从options.modules中摘出函数transformNode, 返回值是一个 函数数组
transforms = pluckModuleFunction(options.modules, 'transformNode');
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');
delimiters = options.delimiters;
// 栈 未闭合的元素先压栈 等待闭合标签再出栈
var stack = [];
var preserveWhitespace = options.preserveWhitespace !== false;
var whitespaceOption = options.whitespace;
// 根节点, 只设置一次
var root;
// 父节点, 一开始为空
var currentParent;
var inVPre = false;
var inPre = false;
var warned = false;
// 辅助警告提示类,由于在生成模板过程是在循环体里面,为避免重复警告提示,定义这个只要提示一次就不再提示的警告方法
function warnOnce (msg, range) {
if (!warned) {
warned = true;
warn$2(msg, range);
}
}
function closeElement (element) {
// 若当前元素不是pre元素,则删除元素尾部的空白文本节点
trimEndingWhitespace(element);
// 如果当前标签没有v-pre并且没有编译过,则编译一下
if (!inVPre && !element.processed) {
element = processElement(element, options);
}
// 树管理
if (!stack.length && element !== root) {
// 允许根元素带有v-if, v-else-if和v-else
if (root.if && (element.elseif || element.else)) {
{
checkRootConstraints(element);
}
addIfCondition(root, {
exp: element.elseif,
block: element
});
} else {
warnOnce(
"Component template should contain exactly one root element. " +
"If you are using v-if on multiple elements, " +
"use v-else-if to chain them instead.",
{ start: element.start }
);
}
}
// 如果不是根节点且不是script或style之类被禁止的标签的话
if (currentParent && !element.forbidden) {
// 如果当前标签绑定有v-else-if或v-else,则需要解析一下
if (element.elseif || element.else) {
processIfConditions(element, currentParent);
} else {
// 如果当前标签是一个作用域插槽
if (element.slotScope) {
//范围槽
//将它保留在子列表中,以便v-else(-if)条件可以
//找到它作为prev节点。
var name = element.slotTarget || '"default"'
;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element;
}
// 把当前元素加入到父级元素的子节点列表中,从而创建AST的父子层级关系
currentParent.children.push(element);
// 同时也将当前节点的父级节点标记为当前的父级节点
element.parent = currentParent;
}
}
//最后的孩子清理
//过滤出限定范围的槽
element.children = element.children.filter(function (c) { return !(c).slotScope; });
// 再次删除末尾的空白节点
trimEndingWhitespace(element);
// 检查之前的状态
if (element.pre) {
inVPre = false;
}
if (platformIsPreTag(element.tag)) {
inPre = false;
}
// 应用转化后
for (var i = 0; i < postTransforms.length; i++) {
postTransforms[i](element, options);
}
}
// 若当前元素不是pre元素,则删除元素尾部的空白文本节点
function trimEndingWhitespace (el) {
// 删除末尾的空白节点
if (!inPre) {
var lastNode;
while (
// 节点存在
(lastNode = el.children[el.children.length - 1]) &&
// 是文本节点
lastNode.type === 3 &&
// 文本节点的内容是空白符
lastNode.text === ' '
) {
// 弹出该元素
el.children.pop();
}
}
}
// 检查当前元素是否符合作为根元素的要求
function checkRootConstraints (el) {
// 通过判断 el.tag === 'slot' || el.tag === 'template' 来判断根元素是否是slot 标签或 template 标签
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(
"Cannot use <" + (el.tag) + "> as component root element because it may " +
'contain multiple nodes.',
{ start: el.start }
);
}
// 判断当前元素是否使用了 v-for 指令,因为v-for 指令会渲染多个节点所以根元素是不允许使用 v-for 指令的
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.',
el.rawAttrsMap['v-for']
);
}
}
// 对模板解析
parseHTML(template, {
warn: warn$2,
expectHTML: options.expectHTML,
isUnaryTag: options.isUnaryTag,
canBeLeftOpenTag: options.canBeLeftOpenTag,
// 是否应该对属性值进行一次编码
shouldDecodeNewlines: options.shouldDecodeNewlines,
// 是否应该对a标签的href进行一次编码
shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
// 是否需要保留注释
shouldKeepComment: options.comments,
outputSourceRange: options.outputSourceRange,
// 解析过程中的回调函数,生成 AST
start: function start (tag, attrs, unary, start$1, end) {
// 检查名称空间。
//如果有父节点,则继承父节点
var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);
// 处理IE svg错误
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs);
}
// 当解析到标签开始位置时会执行这个钩子函数,将标签名和对应的属性传过来
var element = createASTElement(tag, attrs, currentParent);
if (ns) {
element.ns = ns;
}
{
if (options.outputSourceRange) {
element.start = start$1;
element.end = end;
element.rawAttrsMap = element.attrsList.reduce(function (cumulated, attr) {
cumulated[attr.name] = attr;
return cumulated
}, {});
}
// 检测非法属性并提示
attrs.forEach(function (attr) {
if (invalidAttributeRE.test(attr.name)) {
warn$2(
"Invalid dynamic argument expression: attribute names cannot contain " +
"spaces, quotes, <, >, / or =.",
{
start: attr.start + attr.name.indexOf("["),
end: attr.start + attr.name.length
}
);
}
});
}
// 如果当前标签是一个<style>...</style>或<script></script>、<script type="type/javascript"></script>的话
// 提示用户这是一个在模板中被禁止使用的标签,因为模板仅仅只是用来描述状态与页面的呈现的,不应该包含样式和脚本标签
if (isForbiddenTag(element) && !isServerRendering()) {
element.forbidden = true;
warn$2(
'Templates should only be responsible for mapping the state to the ' +
'UI. Avoid placing tags with side-effects in your templates, such as ' +
"<" + tag + ">" + ', as they will not be parsed.',
{ start: element.start }
);
}
// apply pre-transforms
for (var i = 0; i < preTransforms.length; i++) {
element = preTransforms[i](element, options) || element;
}
// 如果inVPre为false,可能还没有解析当前标签是否标记了v-pre
if (!inVPre) {
processPre(element);
// 如果解析过后发现elem上标记有pre=true,说明标签确实标记了v-pre
if (element.pre) {
inVPre = true;
}
}
// 当然,除了vue的指令v-pre之外,我们html也自带一个pre标签,
// 如果标签名是pre,那也要将inPre标记为true
if (platformIsPreTag(element.tag)) {
inPre = true;
}
if (inVPre) {
// 如果一个标签被标记了v-pre,那我们只需要把attrList中剩余的属性复制到elem的attrs中去即可
// 因为attrList中的其他属性都在刚刚进行预处理的时候已经处理并从attrList中删除了
processRawAttrs(element);
} else if (!element.processed) {
// 如果还有没有处理的结构指令,如v-for、v-if等,就处理一下
processFor(element);
processIf(element);
processOnce(element);
}
// 如果不存在根节点,则当前节点就是根节点
if (!root) {
root = element;
{
checkRootConstraints(root);
}
}
// 判断当前节点是不是一个自闭标签,如果是一个自闭标签,那么直接结束当前标签解析
// 如果是不是自闭标签,我们需要记录下当前节点当做是下个节点的父级元素,并加这个元素压入栈中
if (!unary) {
currentParent = element;
stack.push(element);
} else {
closeElement(element);
}
},
end: function end (tag, start, end$1) {
// 当前标签已经解析结束了,将标签从栈中弹出
var element = stack[stack.length - 1];
// 此时栈顶元素便是我们下一个元素的父级
stack.length -= 1;
currentParent = stack[stack.length - 1];
if (options.outputSourceRange) {
element.end = end$1;
}
// 关闭标签
closeElement(element);
},
chars: function chars (text, start, end) {
// 如果不存在父级节点,那么我们可以得知,
// 这个解析出来的文本,要么就是在根节点之外,要么,压根就没有根节点,所给的tpl直接就是一段文本
if (!currentParent) {
{
// 如果解析出来的文本跟传入的模板完全相同,那么,说明直传进来一个文本内容,警告提示
if (text === template) {
warnOnce(
'Component template requires a root element, rather than just text.',
{ start: start }
);
} else if ((text = text.trim())) {
warnOnce(
("text \"" + text + "\" outside root element will be ignored."),
{ start: start }
);
}
}
return
}
// 在IE浏览器中的textarea的placeholder有一个bug,浏览器会将placeholder的内容会被作为textarea的文本节点放入到textarea中
if (isIE &&
currentParent.tag === 'textarea' &&
currentParent.attrsMap.placeholder === text
) {
return
}
var children = currentParent.children;
if (inPre || text.trim()) {
text = isTextTag(currentParent) ? text : decodeHTMLCached(text);
} else if (!children.length) {
// 在开始标记之后删除只包含空格的节点
text = '';
} else if (whitespaceOption) {
if (whitespaceOption === 'condense') {
// 在压缩模式下,如果空白节点包含
//换行,否则压缩为一个空格
text = lineBreakRE.test(text) ? '' : ' ';
} else {
text = ' ';
}
} else {
// 其他情况:看看是不是需要保留空格
text = preserveWhitespace ? ' ' : '';
}
if (text) {
// 如果不是在pre标签中且删除空白的选项是condense,则删除文本中的换行符
if (!inPre && whitespaceOption === 'condense') {
// 将连续的空格压缩为单个空格
text = text.replace(whitespaceRE$1, ' ');
}
var res;
var child;
// 如果当前节点没有v-pre属性且是一个空白符并且可以解析出动态变量
if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
child = {
type: 2,
expression: res.expression,
tokens: res.tokens,
text: text
};
} else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
child = {
type: 3,
text: text
};
}
if (child) {
if (options.outputSourceRange) {
child.start = start;
child.end = end;
}
// 将创建的文本节点或表达式节点加入到父级节点的children中
children.push(child);
}
}
},
comment: function comment (text, start, end) {
// 禁止将任何东西作为同胞添加到根节点
// 只有在根节点下创建注释才有效,只要不在根节点内部的注释都会被忽略
if (currentParent) {
var child = {
type: 3,
text: text,
isComment: true
};
if (options.outputSourceRange) {
child.start = start;
child.end = end;
}
currentParent.children.push(child);
}
}
});
return root
}
// 解析v-pre
function processPre (el) {
if (getAndRemoveAttr(el, 'v-pre') != null) {
el.pre = true;
}
}
// 将属性作为无绑定属性做解析
function processRawAttrs (el) {
var list = el.attrsList;
var len = list.length;
if (len) {
var attrs = el.attrs = new Array(len);
for (var i = 0; i < len; i++) {
attrs[i] = {
name: list[i].name,
value: JSON.stringify(list[i].value)
};
if (list[i].start != null) {
attrs[i].start = list[i].start;
attrs[i].end = list[i].end;
}
}
} else if (!el.pre) {
// 没有属性的pre块中的非根节点
el.plain = true;
}
}
// 处理:key、ref、内外部插槽、绑定属性、组件的判断
function processElement (
element,
options
) {
processKey(element);
//确定这是否是一个普通元素
//删除结构属性
element.plain = (
!element.key &&
!element.scopedSlots &&
!element.attrsList.length
);
// 获取元素的ref属性值
processRef(element);
// 处理插槽
processSlotContent(element);
// 处理<slot></slot>标签, 获取slot标签的name属性值
processSlotOutlet(element);
// 处理<component>标签的 is 和 inline-template属性
processComponent(element);
// 循环transforms数组,逐个对element进行转化处理
for (var i = 0; i < transforms.length; i++) {
// 对element实施转换处理
element = transforms[i](element, options) || element;
}
// 处理元素的属性列表 包括指令和非指令
processAttrs(element);
return element
}
// 获取el的key值
function processKey (el) {
// 获取key的表达式
var exp = getBindingAttr(el, 'key');
if (exp) {
{
// 非生产环境
if (el.tag === 'template') {
// template上不能带key
warn$2(
"<template> cannot be keyed. Place the key on real elements instead.",
// 获取el的key属性
getRawBindingAttr(el, 'key')
);
}
if (el.for) {
// v-for的解析结果中包含这两个迭代器
var iterator = el.iterator2 || el.iterator1;
var parent = el.parent;
// el父元素是<transition-group>, 它是vue内置的组件
if (iterator && iterator === exp && parent && parent.tag === 'transition-group') {
// <transition-group> 的子节点不能用v-for的index作为key
warn$2(
"Do not use v-for index as key on <transition-group> children, " +
"this is the same as not using keys.",
getRawBindingAttr(el, 'key'),
true /* tip */
);
}
}
}
// 为el.key赋值
el.key = exp;
}
}
// 处理ref
// 如果在普通的 DOM 元素上使用, 引用指向的就是 DOM 元素; 如果用在子组件上, 引用就指向组件
function processRef (el) {
// 获取el的ref属性值
var ref = getBindingAttr(el, 'ref');
if (ref) {
el.ref = ref;
// 如果el或其父节点有for, el.refInFor为true
el.refInFor = checkInFor(el);
}
}
// 处理v-for指令
function processFor (el) {
var exp;
if ((exp = getAndRemoveAttr(el, 'v-for'))) {
// 解析v-for表达式
var res = parseFor(exp);
if (res) {
// 将源对象res中的属性混入目标对象el
extend(el, res);
} else {
// 无效的 v-for 表达式
warn$2(
("Invalid v-for expression: " + exp),
el.rawAttrsMap['v-for']
);
}
}
}
// 解析 v-for 表达式 解析出 迭代器 别名 循环的变量
function parseFor (exp) {
// 匹配正则
var inMatch = exp.match(forAliasRE);
if (!inMatch) { return }
var res = {};
res.for = inMatch[2].trim();
var alias = inMatch[1].trim().replace(stripParensRE, '');
var iteratorMatch = alias.match(forIteratorRE);
if (iteratorMatch) {
res.alias = alias.replace(forIteratorRE, '').trim();
res.iterator1 = iteratorMatch[1].trim();
if (iteratorMatch[2]) {
res.iterator2 = iteratorMatch[2].trim();
}
} else {
res.alias = alias;
}
return res
}
// 处理el的条件 v-if v-else v-else-if
function processIf (el) {
var exp = getAndRemoveAttr(el, 'v-if');
if (exp) {
el.if = exp;
// 为el增加if条件
addIfCondition(el, {
exp: exp,
block: el
});
} else {
if (getAndRemoveAttr(el, 'v-else') != null) {
el.else = true;
}
var elseif = getAndRemoveAttr(el, 'v-else-if');
if (elseif) {
el.elseif = elseif;
}
}
}
// 处理if条件(el有 v-else 或 v-else-if)
function processIfConditions (el, parent) {
// 寻找el的前一个元素
var prev = findPrevElement(parent.children);
if (prev && prev.if) {
// 为pre添加if条件
addIfCondition(prev, {
exp: el.elseif,
block: el
});
} else {
// v-else-if v-else 没有对应的 v-if
warn$2(
"v-" + (el.elseif ? ('else-if="' + el.elseif + '"') : 'else') + " " +
"used on element <" + (el.tag) + "> without corresponding v-if.",
el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else']
);
}
}
// 返回children数组中后边第一个type为1的元素
// 逐步往children数组中添加子节点,数组最后一个元素就是当前处理元素的前一个节点
function findPrevElement (children) {
var i = children.length;
while (i--) {
if (children[i].type === 1) {
return children[i]
} else {
if (children[i].text !== ' ') {
// v-if 与 v-else / v-else-if之间的文本将被忽略
warn$2(
"text \"" + (children[i].text.trim()) + "\" between v-if and v-else(-if) " +
"will be ignored.",
children[i]
);
}
// 从数组后边删除一个元素
children.pop();
}
}
}
// 为el元素增加 if条件
function addIfCondition (el, condition) {
if (!el.ifConditions) {
// 初始为空数组
el.ifConditions = [];
}
// 为数组添加新元素
el.ifConditions.push(condition);
}
// 处理v-once, 设置el.once属性
function processOnce (el) {
var once$$1 = getAndRemoveAttr(el, 'v-once');
if (once$$1 != null) {
el.once = true;
}
}
// 处理传递给组件的插槽内容
// v-slot取代了 被废弃但未被移除的 slot 和 slot-scope
function processSlotContent (el) {
var slotScope;
// scope只能用在<template>标签中 slot-scope可以用在其他普通元素
if (el.tag === 'template') {
// 匹配绑定在 template 上的 v-slot 指令,这里会匹配到对应 v-slot 的 attr(类型为 ASTAttr)
slotScope = getAndRemoveAttr(el, 'scope');
// 若 slotBinding 存在,则继续进行 slotName 的正则匹配
// 随即将匹配出来的 name 赋值给 slotTarget,dynamic 赋值给 slotTargetDynamic
// slotScope 赋值为 slotBinding.value 或者 '_empty_'
if (slotScope) {
warn$2(
"the \"scope\" attribute for scoped slots have been deprecated and " +
"replaced by \"slot-scope\" since 2.5. The new \"slot-scope\" attribute " +
"can also be used on plain elements in addition to <template> to " +
"denote scoped slots.",
el.rawAttrsMap['scope'],
true
);
}
el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope');
} else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
/* istanbul ignore if */
if (el.attrsMap['v-for']) {
warn$2(
"Ambiguous combined usage of slot-scope and v-for on <" + (el.tag) + "> " +
"(v-for takes higher priority). Use a wrapper <template> for the " +
"scoped slot to make it clearer.",
el.rawAttrsMap['slot-scope'],
true
);
}
el.slotScope = slotScope;
}
// slot="xxx"
var slotTarget = getBindingAttr(el, 'slot');
if (slotTarget) {
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget;
el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot']);
//将slot保存为本地shadow DOM compat的属性
//仅用于非作用域槽
if (el.tag !== 'template' && !el.slotScope) {
addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'));
}
}
// v-slot语法
{
if (el.tag === 'template') {
// v-slot on component 表示默认插槽
var slotBinding = getAndRemoveAttrByRegex(el, slotRE);
if (slotBinding) {
{
if (el.slotTarget || el.slotScope) {
warn$2(
"Unexpected mixed usage of different slot syntaxes.",
el
);
}
if (el.parent && !maybeComponent(el.parent)) {
warn$2(
"<template v-slot> can only appear at the root level inside " +
"the receiving component",
el
);
}
}
var ref = getSlotName(slotBinding);
var name = ref.name;
var dynamic = ref.dynamic;
el.slotTarget = name;
el.slotTargetDynamic = dynamic;
// 将其强制放入限定范围的槽中进行性能测试
el.slotScope = slotBinding.value || emptySlotScopeToken;
}
} else {
// 组件上的V-slot表示默认槽位
var slotBinding$1 = getAndRemoveAttrByRegex(el, slotRE);
// 将组件的 children 添加到其默认插槽中去
if (slotBinding$1) {
{
if (!maybeComponent(el)) {
warn$2(
"v-slot can only be used on components or <template>.",
slotBinding$1
);
}
if (el.slotScope || el.slotTarget) {
warn$2(
"Unexpected mixed usage of different slot syntaxes.",
el
);
}
if (el.scopedSlots) {
warn$2(
"To avoid scope ambiguity, the default slot should also use " +
"<template> syntax when there are other named slots.",
slotBinding$1
);
}
}
// 将组件的子组件添加到其默认槽中
var slots = el.scopedSlots || (el.scopedSlots = {});
var ref$1 = getSlotName(slotBinding$1);
var name$1 = ref$1.name;
// 匹配拿到 slotBinding 中 name,dynamic 的值
var dynamic$1 = ref$1.dynamic;
// 获取 slots 中 key 对应匹配出来 name 的 slot
// 然后再其下面创建一个标签名为 template 的 ASTElement,attrs 为空数组,parent 为当前节点
var slotContainer = slots[name$1] = createASTElement('template', [], el);
// 这里 name、dynamic 统一赋值给 slotContainer 的 slotTarget、slotTargetDynamic,而不是 el
slotContainer.slotTarget = name$1;
slotContainer.slotTargetDynamic = dynamic$1;
// 将当前节点的 children 添加到 slotContainer 的 children 属性中
slotContainer.children = el.children.filter(function (c) {
if (!c.slotScope) {
c.parent = slotContainer;
return true
}
});
slotContainer.slotScope = slotBinding$1.value || emptySlotScopeToken;
// 移除从scopedSlots返回的子元素
el.children = [];
// 标记el非普通,这样数据就生成了
el.plain = false;
}
}
}
}
function getSlotName (binding) {
// 'v-slot:item' 匹配获取到 'item'
var name = binding.name.replace(slotRE, '');
if (!name) {
if (binding.name[0] !== '#') {
name = 'default';
} else {
warn$2(
"v-slot shorthand syntax requires a slot name.",
binding
);
}
}
// 返回一个 key 包含 name,dynamic 的对象
// 'v-slot:[item]' 匹配然后 replace 后获取到 name = '[item]'
// 进而进行动态参数进行匹配 dynamicArgRE.test(name) 结果为 true
return dynamicArgRE.test(name)
// // 截取变量,如 '[item]' 截取后变成 'item'
? { name: name.slice(1, -1), dynamic: true }
: { name: ("\"" + name + "\""), dynamic: false }
}
//处理插槽
function processSlotOutlet (el) {
if (el.tag === 'slot') {
el.slotName = getBindingAttr(el, 'name');
// <slot>标签不能有key值
if (el.key) {
warn$2(
"`key` does not work on <slot> because slots are abstract outlets " +
"and can possibly expand into multiple elements. " +
"Use the key on a wrapping element instead.",
getRawBindingAttr(el, 'key')
);
}
}
}
// 处理<component>标签,它可以有两个属性 is 和 inline-template
// 当 inline-template 这个特殊的 attribute 出现在一个子组件上时,
// 这个组件将会使用其里面的内容作为模板, 而不是将其作为被分发(插槽)的内容。这使得模板的撰写工作更加灵活。
function processComponent (el) {
var binding;
// 获取is属性的值, 赋值给el.component
if ((binding = getBindingAttr(el, 'is'))) {
el.component = binding;
}
// 获取inline-template属性的值, 辅助给el.inlineTemplate
if (getAndRemoveAttr(el, 'inline-template') != null) {
el.inlineTemplate = true;
}
}
// 处理元素的属性列表 包括指令和非指令
function processAttrs (el) {
var list = el.attrsList;
var i, l, name, rawName, value, modifiers, syncGen, isDynamic;
//遍历每个属性
for (i = 0, l = list.length; i < l; i++) {
name = rawName = list[i].name;
value = list[i].value;
// 如果该属性以v-、@或:开头,表示这是Vue内部指令
if (dirRE.test(name)) {
// mark element as dynamic
el.hasBindings = true;
// //获取修饰符,比如:{native: true,prevent: true}
modifiers = parseModifiers(name.replace(dirRE, ''));
// support .foo shorthand syntax for the .prop modifier
if (modifiers) {
name = name.replace(modifierRE, '');
}
// /bindRD等于/^:|^v-bind:/ ,即该属性是v-bind指令时
if (bindRE.test(name)) { // v-bind
// 去掉指令特性,获取特性名
name = name.replace(bindRE, '');
//对一些表达式做解析
value = parseFilters(value);
isDynamic = dynamicArgRE.test(name);
if (isDynamic) {
name = name.slice(1, -1);
}
if (
value.trim().length === 0
) {
warn$2(
("The value for a v-bind expression cannot be empty. Found in \"v-bind:" + name + "\"")
);
}
//是否绑定到DOM对象上
if (modifiers) {
if (modifiers.prop && !isDynamic) {
name = camelize(name);
if (name === 'innerHtml') { name = 'innerHTML'; }
}
if (modifiers.camel && !isDynamic) {
name = camelize(name);
}
if (modifiers.sync) {
syncGen = genAssignmentCode(value, "$event");
if (!isDynamic) {
addHandler(
el,
("update:" + (camelize(name))),
syncGen,
null,
false,
warn$2,
list[i]
);
if (hyphenate(name) !== camelize(name)) {
addHandler(
el,
("update:" + (hyphenate(name))),
syncGen,
null,
false,
warn$2,
list[i]
);
}
} else {
// handler w/ dynamic event name
addHandler(
el,
("\"update:\"+(" + name + ")"),
syncGen,
null,
false,
warn$2,
list[i],
true // dynamic
);
}
}
}
//如果isProp为true
if ((modifiers && modifiers.prop) || (
!el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
)) {
//则调用addProp()
addProp(el, name, value, list[i], isDynamic);
} else {
//否则调用addAttr(),addAttr()函数用于在AST对象上新增一个attrs属性
addAttr(el, name, value, list[i], isDynamic);
}
} else if (onRE.test(name)) { // v-on
//onRE等于/^@|^v-on:/,即该属性是v-on指令时
name = name.replace(onRE, '');
isDynamic = dynamicArgRE.test(name);
if (isDynamic) {
name = name.slice(1, -1);
}
addHandler(el, name, value, modifiers, false, warn$2, list[i], isDynamic);
} else { // 普通指令
name = name.replace(dirRE, '');
// parse arg
var argMatch = name.match(argRE);
var arg = argMatch && argMatch[1];
isDynamic = false;
if (arg) {
name = name.slice(0, -(arg.length + 1));
if (dynamicArgRE.test(arg)) {
arg = arg.slice(1, -1);
isDynamic = true;
}
}
addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]);
if (name === 'model') {
checkForAliasModel(el, value);
}
}
} else {
// literal attribute
{
var res = parseText(value, delimiters);
if (res) {
warn$2(
name + "=\"" + value + "\": " +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div id="{{ val }}">, use <div :id="val">.',
list[i]
);
}
}
addAttr(el, name, JSON.stringify(value), list[i]);
// #6887 firefox doesn't update muted state if set via attribute
// even immediately after element creation
if (!el.component &&
name === 'muted' &&
platformMustUseProp(el.tag, el.attrsMap.type, name)) {
addProp(el, name, 'true', list[i]);
}
}
}
}
// 检查当前元素el或其父元素是否有for指令
function checkInFor (el) {
var parent = el;
while (parent) {
if (parent.for !== undefined) {
return true
}
// 往父元素查询
parent = parent.parent;
}
return false
}
// 解析出所有的修饰符, 组成一个对象 .prevent
function parseModifiers (name) {
var match = name.match(modifierRE);
if (match) {
var ret = {};
// 循环处理所有匹配的字符串
match.forEach(function (m) { ret[m.slice(1)] = true; });
return ret
}
}
// 简历属性map对象, 同时验重
function makeAttrsMap (attrs) {
var map = {};
for (var i = 0, l = attrs.length; i < l; i++) {
if (
map[attrs[i].name] && !isIE && !isEdge
) {
// 非生产环境 && 有次属性名 && 不是IE && 不是Edge, 给出错误信息
warn$2('duplicate attribute: ' + attrs[i].name, attrs[i]);
}
// 建立属性map对象
map[attrs[i].name] = attrs[i].value;
}
return map
}
// script标签如果没有设置type, 默认是"text/javascript",浏览器会按照js去解析, 如果是其他type,忽略处理,等待页面加载完成后,
// 模板引擎获取到标签内容,然后使用数据对其进行渲染再输出到页面上
function isTextTag (el) {
return el.tag === 'script' || el.tag === 'style'
}
// <style>标签 type 为空或者为 text/javascript 的 <script>标签
function isForbiddenTag (el) {
return (
el.tag === 'style' ||
(el.tag === 'script' && (
!el.attrsMap.type ||
el.attrsMap.type === 'text/javascript'
))
)
}
var ieNSBug = /^xmlns:NS\d+/;
var ieNSPrefix = /^NS\d+:/;
// 对一些属性做特殊处理 监视IE浏览器的SVG bug
function guardIESVGBug (attrs) {
var res = [];
// 遍历属性数组
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
// 属性名替换
if (!ieNSBug.test(attr.name)) {
attr.name = attr.name.replace(ieNSPrefix, '');
res.push(attr);
}
}
return res
}
// 检查是否针对v-for的别名变量绑定了 v-modal 这样不合法
function checkForAliasModel (el, value) {
var _el = el;
while (_el) {
if (_el.for && _el.alias === value) {
warn$2(
"<" + (el.tag) + " v-model=\"" + value + "\">: " +
"You are binding v-model directly to a v-for iteration alias. " +
"This will not be able to modify the v-for source array because " +
"writing to the alias is like modifying a function local variable. " +
"Consider using an array of objects and use v-model on an object property instead.",
el.rawAttrsMap['v-model']
);
}
// 从子节点往上逐层判断
_el = _el.parent;
}
}
/* // 处理checkbox、radio,input等需要预处理的标签 */
function preTransformNode (el, options) {
if (el.tag === 'input') {
var map = el.attrsMap;
if (!map['v-model']) {
return
}
var typeBinding;
if (map[':type'] || map['v-bind:type']) {
typeBinding = getBindingAttr(el, 'type');
}
if (!map.type && !typeBinding && map['v-bind']) {
typeBinding = "(" + (map['v-bind']) + ").type";
}
if (typeBinding) {
var ifCondition = getAndRemoveAttr(el, 'v-if', true);
var ifConditionExtra = ifCondition ? ("&&(" + ifCondition + ")") : "";
var hasElse = getAndRemoveAttr(el, 'v-else', true) != null;
var elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true);
// 1. checkbox
var branch0 = cloneASTElement(el);
// process for on the main node
processFor(branch0);
addRawAttr(branch0, 'type', 'checkbox');
processElement(branch0, options);
branch0.processed = true; // prevent it from double-processed
branch0.if = "(" + typeBinding + ")==='checkbox'" + ifConditionExtra;
addIfCondition(branch0, {
exp: branch0.if,
block: branch0
});
// 2. add radio else-if condition
var branch1 = cloneASTElement(el);
getAndRemoveAttr(branch1, 'v-for', true);
addRawAttr(branch1, 'type', 'radio');
processElement(branch1, options);
addIfCondition(branch0, {
exp: "(" + typeBinding + ")==='radio'" + ifConditionExtra,
block: branch1
});
// 3. other
var branch2 = cloneASTElement(el);
getAndRemoveAttr(branch2, 'v-for', true);
addRawAttr(branch2, ':type', typeBinding);
processElement(branch2, options);
addIfCondition(branch0, {
exp: ifCondition,
block: branch2
});
if (hasElse) {
branch0.else = true;
} else if (elseIfCondition) {
branch0.elseif = elseIfCondition;
}
return branch0
}
}
}
// 通过 createASTElement 函数再创建出一个元素描述对象
function cloneASTElement (el) {
return createASTElement(el.tag, el.attrsList.slice(), el.parent)
}
var model$1 = {
preTransformNode: preTransformNode
};
var modules$1 = [
klass$1,
style$1,
model$1
];
/* */
function text (el, dir) {
if (dir.value) {
addProp(el, 'textContent', ("_s(" + (dir.value) + ")"), dir);
}
}
/* */
function html (el, dir) {
if (dir.value) {
addProp(el, 'innerHTML', ("_s(" + (dir.value) + ")"), dir);
}
}
var directives$1 = {
model: model,
text: text,
html: html
};
/* */
var baseOptions = {
expectHTML: true,
modules: modules$1,
directives: directives$1,
isPreTag: isPreTag,
isUnaryTag: isUnaryTag,
mustUseProp: mustUseProp,
canBeLeftOpenTag: canBeLeftOpenTag,
isReservedTag: isReservedTag,
getTagNamespace: getTagNamespace,
staticKeys: genStaticKeys(modules$1)
};
/* */
var isStaticKey;
var isPlatformReservedTag;
var genStaticKeysCached = cached(genStaticKeys$1);
/**
* Goal of the optimizer: walk the generated template AST tree
* and detect sub-trees that are purely static, i.e. parts of
* the DOM that never needs to change.
*
* Once we detect these sub-trees, we can:
*
* 1. Hoist them into constants, so that we no longer need to
* create fresh nodes for them on each re-render;
* 2. Completely skip them in the patching process.
*/
function optimize (root, options) {
if (!root) { return }
isStaticKey = genStaticKeysCached(options.staticKeys || '');
isPlatformReservedTag = options.isReservedTag || no;
// first pass: mark all non-static nodes.
markStatic$1(root);
// second pass: mark static roots.
markStaticRoots(root, false);
}
function genStaticKeys$1 (keys) {
return makeMap(
'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap' +
(keys ? ',' + keys : '')
)
}
function markStatic$1 (node) {
node.static = isStatic(node);
if (node.type === 1) {
// do not make component slot content static. this avoids
// 1. components not able to mutate slot nodes
// 2. static slot content fails for hot-reloading
if (
!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
return
}
for (var i = 0, l = node.children.length; i < l; i++) {
var child = node.children[i];
markStatic$1(child);
if (!child.static) {
node.static = false;
}
}
if (node.ifConditions) {
for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
var block = node.ifConditions[i$1].block;
markStatic$1(block);
if (!block.static) {
node.static = false;
}
}
}
}
}
function markStaticRoots (node, isInFor) {
if (node.type === 1) {
if (node.static || node.once) {
node.staticInFor = isInFor;
}
// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true;
return
} else {
node.staticRoot = false;
}
if (node.children) {
for (var i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for);
}
}
if (node.ifConditions) {
for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
markStaticRoots(node.ifConditions[i$1].block, isInFor);
}
}
}
}
function isStatic (node) {
if (node.type === 2) { // expression
return false
}
if (node.type === 3) { // text
return true
}
return !!(node.pre || (
!node.hasBindings && // no dynamic bindings
!node.if && !node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
function isDirectChildOfTemplateFor (node) {
while (node.parent) {
node = node.parent;
if (node.tag !== 'template') {
return false
}
if (node.for) {
return true
}
}
return false
}
/* */
var fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/;
var fnInvokeRE = /\([^)]*?\);*$/;
var simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/;
// KeyboardEvent.keyCode aliases
var keyCodes = {
esc: 27,
tab: 9,
enter: 13,
space: 32,
up: 38,
left: 37,
right: 39,
down: 40,
'delete': [8, 46]
};
// KeyboardEvent.key aliases
var keyNames = {
// #7880: IE11 and Edge use `Esc` for Escape key name.
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
// #9112: IE11 uses `Spacebar` for Space key name.
space: [' ', 'Spacebar'],
// #7806: IE11 uses key names without `Arrow` prefix for arrow keys.
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
// #9112: IE11 uses `Del` for Delete key name.
'delete': ['Backspace', 'Delete', 'Del']
};
// #4868: modifiers that prevent the execution of the listener
// need to explicitly return null so that we can determine whether to remove
// the listener for .once
var genGuard = function (condition) { return ("if(" + condition + ")return null;"); };
var modifierCode = {
stop: '$event.stopPropagation();',
prevent: '$event.preventDefault();',
self: genGuard("$event.target !== $event.currentTarget"),
ctrl: genGuard("!$event.ctrlKey"),
shift: genGuard("!$event.shiftKey"),
alt: genGuard("!$event.altKey"),
meta: genGuard("!$event.metaKey"),
left: genGuard("'button' in $event && $event.button !== 0"),
middle: genGuard("'button' in $event && $event.button !== 1"),
right: genGuard("'button' in $event && $event.button !== 2")
};
function genHandlers (
events,
isNative
) {
var prefix = isNative ? 'nativeOn:' : 'on:';
var staticHandlers = "";
var dynamicHandlers = "";
for (var name in events) {
var handlerCode = genHandler(events[name]);
if (events[name] && events[name].dynamic) {
dynamicHandlers += name + "," + handlerCode + ",";
} else {
staticHandlers += "\"" + name + "\":" + handlerCode + ",";
}
}
staticHandlers = "{" + (staticHandlers.slice(0, -1)) + "}";
if (dynamicHandlers) {
return prefix + "_d(" + staticHandlers + ",[" + (dynamicHandlers.slice(0, -1)) + "])"
} else {
return prefix + staticHandlers
}
}
function genHandler (handler) {
if (!handler) {
return 'function(){}'
}
if (Array.isArray(handler)) {
return ("[" + (handler.map(function (handler) { return genHandler(handler); }).join(',')) + "]")
}
var isMethodPath = simplePathRE.test(handler.value);
var isFunctionExpression = fnExpRE.test(handler.value);
var isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, ''));
if (!handler.modifiers) {
if (isMethodPath || isFunctionExpression) {
return handler.value
}
return ("function($event){" + (isFunctionInvocation ? ("return " + (handler.value)) : handler.value) + "}") // inline statement
} else {
var code = '';
var genModifierCode = '';
var keys = [];
for (var key in handler.modifiers) {
if (modifierCode[key]) {
genModifierCode += modifierCode[key];
// left/right
if (keyCodes[key]) {
keys.push(key);
}
} else if (key === 'exact') {
var modifiers = (handler.modifiers);
genModifierCode += genGuard(
['ctrl', 'shift', 'alt', 'meta']
.filter(function (keyModifier) { return !modifiers[keyModifier]; })
.map(function (keyModifier) { return ("$event." + keyModifier + "Key"); })
.join('||')
);
} else {
keys.push(key);
}
}
if (keys.length) {
code += genKeyFilter(keys);
}
// Make sure modifiers like prevent and stop get executed after key filtering
if (genModifierCode) {
code += genModifierCode;
}
var handlerCode = isMethodPath
? ("return " + (handler.value) + ".apply(null, arguments)")
: isFunctionExpression
? ("return (" + (handler.value) + ").apply(null, arguments)")
: isFunctionInvocation
? ("return " + (handler.value))
: handler.value;
return ("function($event){" + code + handlerCode + "}")
}
}
// genKeyFilter用于生成一段过滤的字符串
function genKeyFilter (keys) {
return (
// make sure the key filters only apply to KeyboardEvents
// #9441: can't use 'keyCode' in $event because Chrome autofill fires fake
// key events that do not have keyCode property...
"if(!$event.type.indexOf('key')&&" +
(keys.map(genFilterCode).join('&&')) + ")return null;"
)
}
function genFilterCode (key) {
var keyVal = parseInt(key, 10);
if (keyVal) {
return ("$event.keyCode!==" + keyVal)
}
var keyCode = keyCodes[key];
var keyName = keyNames[key];
return (
"_k($event.keyCode," +
(JSON.stringify(key)) + "," +
(JSON.stringify(keyCode)) + "," +
"$event.key," +
"" + (JSON.stringify(keyName)) +
")"
)
}
/* */
function on (el, dir) {
if (dir.modifiers) {
warn("v-on without argument does not support modifiers.");
}
el.wrapListeners = function (code) { return ("_g(" + code + "," + (dir.value) + ")"); };
}
/* */
function bind$1 (el, dir) {
el.wrapData = function (code) {
return ("_b(" + code + ",'" + (el.tag) + "'," + (dir.value) + "," + (dir.modifiers && dir.modifiers.prop ? 'true' : 'false') + (dir.modifiers && dir.modifiers.sync ? ',true' : '') + ")")
};
}
/****************************************************************************************************************************************** */
// CodegenState 里的工具函数
var baseDirectives = {
on: on,
bind: bind$1,
cloak: noop
};
// CodegenState函数里面包含转化时需要的属性和方法
var CodegenState = function CodegenState (options) {
this.options = options;
this.warn = options.warn || baseWarn;
this.transforms = pluckModuleFunction(options.modules, 'transformCode');
//dataGenFns是对静态类和静态样式的处理
this.dataGenFns = pluckModuleFunction(options.modules, 'genData');
//directives是对指令的相关操作
this.directives = extend(extend({}, baseDirectives), options.directives);
//保留标签标志
var isReservedTag = options.isReservedTag || no;
//不是保留标签就可能是组件
this.maybeComponent = function (el) { return !!el.component || !isReservedTag(el.tag); };
//onceId使用v-once的递增id
this.onceId = 0;
//staticRenderFns是对静态根节点的处理
this.staticRenderFns = [];
this.pre = false;
};
function generate (
ast,
options
) {
// 实例化 CodegenState 对象,生成代码的时候需要用到其中的一些东西
var state = new CodegenState(options);
// 生成字符串格式的代码
var code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")';
return {
render: ("with(this){return " + code + "}"),
staticRenderFns: state.staticRenderFns
}
}
function genElement (el, state) {
if (el.parent) {
el.pre = el.pre || el.parent.pre;
}
if (el.staticRoot && !el.staticProcessed) {
// 处理静态根节点,生成节点的渲染函数
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
// 处理带有 v-once 指令的节点
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
// 处理节点上的 v-for 指令
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
// 处理带有 v-if 指令的节点
return genIf(el, state)
// 当前节点不是 template 标签也不是插槽和带有 v-pre 指令的节点时
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
// 生成所有子节点的渲染函数,返回一个数组
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
// 生成插槽的渲染函数
return genSlot(el, state)
} else {
// component or element
var code;
if (el.component) {
// 处理动态组件和普通元素(自定义组件、原生标签)
code = genComponent(el.component, el, state);
} else {
var data;
// 自定义组件和原生标签
if (!el.plain || (el.pre && state.maybeComponent(el))) {
// 非普通元素或者带有 v-pre 指令的组件,处理节点的所有属性,返回一个 JSON 字符串
data = genData$2(el, state);
}
// 处理子节点,得到所有子节点字符串格式的代码组成的数组
var children = el.inlineTemplate ? null : genChildren(el, state, true);
// 得到最终的字符串格式的代码
code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
}
// 如果提供了 transformCode 方法,
// 则最终的 code 会经过各个模块(module)的该方法处理
for (var i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code);
}
return code
}
}
// 处理静态根节点,生成节点的渲染函数
function genStatic (el, state) {
el.staticProcessed = true;
var originalPreState = state.pre;
if (el.pre) {
state.pre = el.pre;
}
// 将当前静态节点的渲染函数放到 staticRenderFns 数组中
state.staticRenderFns.push(("with(this){return " + (genElement(el, state)) + "}"));
state.pre = originalPreState;
// 返回一个可执行函数 _m(idx, true or '')
return ("_m(" + (state.staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")")
}
// 处理带有 v-once 指令的节点
function genOnce (el, state) {
el.onceProcessed = true;
if (el.if && !el.ifProcessed) {
// 当前节点存在 v-if 指令,得到一个三元表达式,condition ? render1 : render2
return genIf(el, state)
} else if (el.staticInFor) {
var key = '';
var parent = el.parent;
while (parent) {
// 当前节点是一个包含在 v-for 指令内部的静态节点,得到 `_o(_c(tag, data, children), number, key)`
if (parent.for) {
key = parent.key;
break
}
parent = parent.parent;
}
if (!key) {
// 当前节点就是一个单纯的 v-once 节点,得到 `_m(idx, true of '')`
state.warn(
"v-once can only be used inside v-for that is keyed. ",
el.rawAttrsMap['v-once']
);
return genElement(el, state)
}
return ("_o(" + (genElement(el, state)) + "," + (state.onceId++) + "," + key + ")")
} else {
return genStatic(el, state)
}
}
// 处理带有 v-if 指令的节点,最终得到一个三元表达式:condition ? render1 : render2
function genIf (
el,
state,
altGen,
altEmpty
) {
// 标记当前节点的 v-if 指令已经被处理过了,避免无效的递归
el.ifProcessed = true; // avoid recursion
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions (
conditions,
state,
altGen,
altEmpty
) {
// 长度若为空,则直接返回一个空节点渲染函数
if (!conditions.length) {
return altEmpty || '_e()'
}
// 从 conditions 数组中拿出第一个条件对象 { exp, block }
var condition = conditions.shift();
// 返回结果是一个三元表达式字符串,condition ? 渲染函数1 : 渲染函数2
if (condition.exp) {
// 如果 condition.exp 条件成立,则得到一个三元表达式,
// 如果条件不成立,则通过递归的方式找 conditions 数组中下一个元素,
// 直到找到条件成立的元素,然后返回一个三元表达式
return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
} else {
return ("" + (genTernaryExp(condition.block)))
}
// v-if with v-once should generate code like (a)?_m(0):_m(1)
function genTernaryExp (el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
// 处理节点上的 v-for 指令
function genFor (
el,
state,
altGen,
altHelper
) {
// 处理节点上的 v-for 指令
var exp = el.for;
var alias = el.alias;
// iterator 为 v-for = "(item ,idx) in obj" 时
var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : '';
// 提示,v-for 指令在组件上时必须使用 key
if (state.maybeComponent(el) &&
el.tag !== 'slot' &&
el.tag !== 'template' &&
!el.key
) {
state.warn(
"<" + (el.tag) + " v-for=\"" + alias + " in " + exp + "\">: component lists rendered with " +
"v-for should have explicit keys. " +
"See https://vuejs.org/guide/list.html#key for more info.",
el.rawAttrsMap['v-for'],
true /* tip */
);
}
// 标记当前节点上的 v-for 指令已经被处理过了
el.forProcessed = true; // avoid recursion
return (altHelper || '_l') + "((" + exp + ")," +
"function(" + alias + iterator1 + iterator2 + "){" +
"return " + ((altGen || genElement)(el, state)) +
'})'
}
function genData$2 (el, state) {
var data = '{';
// directives first.
// directives may mutate the el's other properties before they are generated.
var dirs = genDirectives(el, state);
if (dirs) { data += dirs + ','; }
// key
if (el.key) {
data += "key:" + (el.key) + ",";
}
// ref
if (el.ref) {
data += "ref:" + (el.ref) + ",";
}
if (el.refInFor) {
data += "refInFor:true,";
}
// pre
if (el.pre) {
data += "pre:true,";
}
// record original tag name for components using "is" attribute
if (el.component) {
data += "tag:\"" + (el.tag) + "\",";
}
// module data generation functions
for (var i = 0; i < state.dataGenFns.length; i++) {
data += state.dataGenFns[i](el);
}
// attributes
if (el.attrs) {
data += "attrs:" + (genProps(el.attrs)) + ",";
}
// DOM props
if (el.props) {
data += "domProps:" + (genProps(el.props)) + ",";
}
// event handlers
if (el.events) {
data += (genHandlers(el.events, false)) + ",";
}
if (el.nativeEvents) {
data += (genHandlers(el.nativeEvents, true)) + ",";
}
// slot target
// only for non-scoped slots
if (el.slotTarget && !el.slotScope) {
data += "slot:" + (el.slotTarget) + ",";
}
// scoped slots
if (el.scopedSlots) {
data += (genScopedSlots(el, el.scopedSlots, state)) + ",";
}
// component v-model
if (el.model) {
data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
}
// inline-template
if (el.inlineTemplate) {
var inlineTemplate = genInlineTemplate(el, state);
if (inlineTemplate) {
data += inlineTemplate + ",";
}
}
data = data.replace(/,$/, '') + '}';
// v-bind dynamic argument wrap
// v-bind with dynamic arguments must be applied using the same v-bind object
// merge helper so that class/style/mustUseProp attrs are handled correctly.
if (el.dynamicAttrs) {
data = "_b(" + data + ",\"" + (el.tag) + "\"," + (genProps(el.dynamicAttrs)) + ")";
}
// v-bind data wrap
if (el.wrapData) {
data = el.wrapData(data);
}
// v-on data wrap
if (el.wrapListeners) {
data = el.wrapListeners(data);
}
return data
}
// 运行指令的编译方法,如果指令存在运行时任务,则返回 directives: [{ name, rawName, value, arg, modifiers }, ...}]
function genDirectives (el, state) {
// 获取指令数组
var dirs = el.directives;
// 没有指令则直接结束
if (!dirs) { return }
// 指令的处理结果
var res = 'directives:[';
// 标记,用于标记指令是否需要在运行时完成的任务,比如 v-model 的 input 事件
var hasRuntime = false;
var i, l, dir, needRuntime;
// 遍历指令数组
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i];
needRuntime = true;
// 获取节点当前指令的处理方法,比如 web 平台的 v-html、v-text、v-model
var gen = state.directives[dir.name];
if (gen) {
// 执行指令的编译方法,如果指令还需要运行时完成一部分任务,则返回 true,比如 v-model
needRuntime = !!gen(el, dir, state.warn);
}
if (needRuntime) {
// 表示该指令在运行时还有任务
hasRuntime = true;
res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:" + (dir.isDynamicArg ? dir.arg : ("\"" + (dir.arg) + "\""))) : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
}
}
if (hasRuntime) {
// 也就是说,只有指令存在运行时任务时,才会返回 res
return res.slice(0, -1) + ']'
}
}
// 处理内联模版
function genInlineTemplate (el, state) {
var ast = el.children[0];
if (el.children.length !== 1 || ast.type !== 1) {
state.warn(
'Inline-template components must have exactly one child element.',
{ start: el.start }
);
}
if (ast && ast.type === 1) {
var inlineRenderFns = generate(ast, state.options);
return ("inlineTemplate:{render:function(){" + (inlineRenderFns.render) + "},staticRenderFns:[" + (inlineRenderFns.staticRenderFns.map(function (code) { return ("function(){" + code + "}"); }).join(',')) + "]}")
}
}
// 处理作用域插槽模版
function genScopedSlots (
el,
slots,
state
) {
//默认情况下,作用域槽被认为是“稳定的”,这允许子槽
//只有作用域槽的组件跳过来自父组件的强制更新。
//但在某些情况下,我们不得不放弃这种优化
//例如,如果槽包含动态名称,有v-if或v-for…
var needsForceUpdate = el.for || Object.keys(slots).some(function (key) {
var slot = slots[key];
return (
slot.slotTargetDynamic ||
slot.if ||
slot.for ||
containsSlotChild(slot) // is passing down slot from parent which may be dynamic
)
});
// 如果一个有作用域槽的组件位于条件分支中,
//可以重用相同的组件,但不同的组件
//编译的槽位内容。为了避免这种情况,我们生成一个基于
//所有槽位内容的生成代码。
var needsKey = !!el.if;
// 或者当它在另一个作用域槽或v-for中(反应性可能是由于中间作用域变量而断开连接)
// #9438, #9506
// 通过正确分析作用域内绑定,可以进一步优化这一点并跳过不使用范围变量的强制更新。
if (!needsForceUpdate) {
var parent = el.parent;
while (parent) {
if (
(parent.slotScope && parent.slotScope !== emptySlotScopeToken) ||
parent.for
) {
needsForceUpdate = true;
break
}
if (parent.if) {
needsKey = true;
}
parent = parent.parent;
}
}
var generatedSlots = Object.keys(slots)
.map(function (key) { return genScopedSlot(slots[key], state); })
.join(',');
return ("scopedSlots:_u([" + generatedSlots + "]" + (needsForceUpdate ? ",null,true" : "") + (!needsForceUpdate && needsKey ? (",null,false," + (hash(generatedSlots))) : "") + ")")
}
function hash(str) {
var hash = 5381;
var i = str.length;
while(i) {
hash = (hash * 33) ^ str.charCodeAt(--i);
}
return hash >>> 0
}
function containsSlotChild (el) {
if (el.type === 1) {
if (el.tag === 'slot') {
return true
}
return el.children.some(containsSlotChild)
}
return false
}
function genScopedSlot (
el,
state
) {
var isLegacySyntax = el.attrsMap['slot-scope'];
if (el.if && !el.ifProcessed && !isLegacySyntax) {
return genIf(el, state, genScopedSlot, "null")
}
if (el.for && !el.forProcessed) {
return genFor(el, state, genScopedSlot)
}
var slotScope = el.slotScope === emptySlotScopeToken
? ""
: String(el.slotScope);
// 函数参数为标签上slot-scope属性对应的值
var fn = "function(" + slotScope + "){" +
"return " + (el.tag === 'template'
? el.if && isLegacySyntax
? ("(" + (el.if) + ")?" + (genChildren(el, state) || 'undefined') + ":undefined")
: genChildren(el, state) || 'undefined'
: genElement(el, state)) + "}";
// 反向代理v-slot没有作用域
var reverseProxy = slotScope ? "" : ",proxy:true";
// key为插槽的名称,fn为生成的函数代码
return ("{key:" + (el.slotTarget || "\"default\"") + ",fn:" + fn + reverseProxy + "}")
}
// 生成所有子节点的渲染函数,返回一个数组
function genChildren (
el,
state,
checkSkip,
altGenElement,
altGenNode
) {
// 所有子节点
var children = el.children;
if (children.length) {
// 第一个子节点
var el$1 = children[0];
// optimize single v-for
if (children.length === 1 &&
el$1.for &&
el$1.tag !== 'template' &&
el$1.tag !== 'slot'
) {
// 优化,只有一个子节点 && 子节点的上有 v-for 指令 && 子节点的标签不为 template 或者 slot
// 优化的方式是直接调用 genElement 生成该节点的渲染函数,不需要走下面的循环然后调用 genCode 最后得到渲染函数
var normalizationType = checkSkip
? state.maybeComponent(el$1) ? ",1" : ",0"
: "";
return ("" + ((altGenElement || genElement)(el$1, state)) + normalizationType)
}
// 获取节点规范化类型,返回一个 number
var normalizationType$1 = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0;
// 函数,生成代码的一个函数
var gen = altGenNode || genNode;
// 返回一个数组,数组的每个元素都是一个子节点的渲染函数
return ("[" + (children.map(function (c) { return gen(c, state); }).join(',')) + "]" + (normalizationType$1 ? ("," + normalizationType$1) : ''))
}
}
// 确定子数组所需的规范化。
// 0:不需要标准化
// 1:需要简单的规范化(可能是1级深嵌套数组)
// 2:需要完全标准化
function getNormalizationType (
children,
maybeComponent
) {
var res = 0;
for (var i = 0; i < children.length; i++) {
var el = children[i];
if (el.type !== 1) {
continue
}
if (needsNormalization(el) ||
(el.ifConditions && el.ifConditions.some(function (c) { return needsNormalization(c.block); }))) {
res = 2;
break
}
if (maybeComponent(el) ||
(el.ifConditions && el.ifConditions.some(function (c) { return maybeComponent(c.block); }))) {
res = 1;
}
}
return res
}
function needsNormalization (el) {
return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
}
// 生成节点的渲染函数
function genNode (node, state) {
if (node.type === 1) {
return genElement(node, state)
} else if (node.type === 3 && node.isComment) {
return genComment(node)
} else {
return genText(node)
}
}
// 生成文本的渲染函数
function genText (text) {
return ("_v(" + (text.type === 2
? text.expression // no need for () because already wrapped in _s()
: transformSpecialNewlines(JSON.stringify(text.text))) + ")")
}
// 生成Comment文本的渲染函数
function genComment (comment) {
return ("_e(" + (JSON.stringify(comment.text)) + ")")
}
// 生成插槽的渲染函数,得到 _t(slotName, children, attrs, bind)
function genSlot (el, state) {
// 插槽名称
var slotName = el.slotName || '"default"';
// 生成所有的子节点
var children = genChildren(el, state);
// 结果字符串,_t(slotName, children, attrs, bind)
var res = "_t(" + slotName + (children ? (",function(){return " + children + "}") : '');
var attrs = el.attrs || el.dynamicAttrs
? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(function (attr) { return ({
// 槽道具是驼峰的
name: camelize(attr.name),
value: attr.value,
dynamic: attr.dynamic
}); }))
: null;
var bind$$1 = el.attrsMap['v-bind'];
if ((attrs || bind$$1) && !children) {
res += ",null";
}
if (attrs) {
res += "," + attrs;
}
if (bind$$1) {
res += (attrs ? '' : ',null') + "," + bind$$1;
}
return res + ')'
}
// 生成动态组件的渲染函数
function genComponent (
componentName,
el,
state
) {
// 所有的子节点
var children = el.inlineTemplate ? null : genChildren(el, state, true);
// componentName 是 is 属性的值
return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")")
}
// 拼凑AST对象的属性或DOM属性
function genProps (props) {
var staticProps = "";
var dynamicProps = "";
//遍历props
for (var i = 0; i < props.length; i++) {
//对应的值
var prop = props[i];
var value = transformSpecialNewlines(prop.value);
if (prop.dynamic) {
dynamicProps += (prop.name) + "," + value + ",";
} else {
staticProps += "\"" + (prop.name) + "\":" + value + ",";
}
}
staticProps = "{" + (staticProps.slice(0, -1)) + "}";
if (dynamicProps) {
return ("_d(" + staticProps + ",[" + (dynamicProps.slice(0, -1)) + "])")
} else {
return staticProps
}
}
// 它们被浏览器理解为换行,而在Javascript的字符串表达式中是不允许换行的,从而导致错误
function transformSpecialNewlines (text) {
return text
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029')
}
/* */
// 这些关键字不应该出现在表达式中,而应该出现在操作符中
//允许使用typeof, instanceof和in
var prohibitedKeywordRE = new RegExp('\\b' + (
'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
'super,throw,while,yield,delete,export,import,return,switch,default,' +
'extends,finally,continue,debugger,function,arguments'
).split(',').join('\\b|\\b') + '\\b');
// 这些一元操作符不应该用作属性/方法名
var unaryOperatorsRE = new RegExp('\\b' + (
'delete,typeof,void'
).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)');
// 在表达式中剥离字符串
var stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g;
//检测模板中有问题的表达式
function detectErrors (ast, warn) {
if (ast) {
checkNode(ast, warn);
}
}
// 检查Node
function checkNode (node, warn) {
if (node.type === 1) {
for (var name in node.attrsMap) {
if (dirRE.test(name)) {
var value = node.attrsMap[name];
if (value) {
var range = node.rawAttrsMap[name];
if (name === 'v-for') {
checkFor(node, ("v-for=\"" + value + "\""), warn, range);
} else if (name === 'v-slot' || name[0] === '#') {
checkFunctionParameterExpression(value, (name + "=\"" + value + "\""), warn, range);
} else if (onRE.test(name)) {
checkEvent(value, (name + "=\"" + value + "\""), warn, range);
} else {
checkExpression(value, (name + "=\"" + value + "\""), warn, range);
}
}
}
}
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
checkNode(node.children[i], warn);
}
}
} else if (node.type === 2) {
checkExpression(node.expression, node.text, warn, node);
}
}
// 检查 Event
function checkEvent (exp, text, warn, range) {
var stripped = exp.replace(stripStringRE, '');
var keywordMatch = stripped.match(unaryOperatorsRE);
if (keywordMatch && stripped.charAt(keywordMatch.index - 1) !== '$') {
warn(
"avoid using JavaScript unary operator as property name: " +
"\"" + (keywordMatch[0]) + "\" in expression " + (text.trim()),
range
);
}
checkExpression(exp, text, warn, range);
}
// 检查 For 循环
function checkFor (node, text, warn, range) {
checkExpression(node.for || '', text, warn, range);
checkIdentifier(node.alias, 'v-for alias', text, warn, range);
checkIdentifier(node.iterator1, 'v-for iterator', text, warn, range);
checkIdentifier(node.iterator2, 'v-for iterator', text, warn, range);
}
// 检查 Identifier
function checkIdentifier (
ident,
type,
text,
warn,
range
) {
if (typeof ident === 'string') {
try {
new Function(("var " + ident + "=_"));
} catch (e) {
warn(("invalid " + type + " \"" + ident + "\" in expression: " + (text.trim())), range);
}
}
}
// 检查表达式
function checkExpression (exp, text, warn, range) {
try {
new Function(("return " + exp));
} catch (e) {
// prohibitedKeywordRE:正则校验:禁止关键字
// stripStringRE:正则校验:脚本字符串
var keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE);
if (keywordMatch) {
warn(
"avoid using JavaScript keyword as property name: " +
"\"" + (keywordMatch[0]) + "\"\n Raw expression: " + (text.trim()),
range
);
} else {
warn(
"invalid expression: " + (e.message) + " in\n\n" +
" " + exp + "\n\n" +
" Raw expression: " + (text.trim()) + "\n",
range
);
}
}
}
// 检查函数表达式
function checkFunctionParameterExpression (exp, text, warn, range) {
try {
new Function(exp, '');
} catch (e) {
warn(
"invalid function parameter expression: " + (e.message) + " in\n\n" +
" " + exp + "\n\n" +
" Raw expression: " + (text.trim()) + "\n",
range
);
}
}
/* */
var range = 2;
function generateCodeFrame (
source,
start,
end
) {
if ( start === void 0 ) start = 0;
if ( end === void 0 ) end = source.length;
var lines = source.split(/\r?\n/);
var count = 0;
var res = [];
for (var i = 0; i < lines.length; i++) {
count += lines[i].length + 1;
if (count >= start) {
for (var j = i - range; j <= i + range || end > count; j++) {
if (j < 0 || j >= lines.length) { continue }
res.push(("" + (j + 1) + (repeat$1(" ", 3 - String(j + 1).length)) + "| " + (lines[j])));
var lineLength = lines[j].length;
if (j === i) {
// push underline
var pad = start - (count - lineLength) + 1;
var length = end > count ? lineLength - pad : end - start;
res.push(" | " + repeat$1(" ", pad) + repeat$1("^", length));
} else if (j > i) {
if (end > count) {
var length$1 = Math.min(end - count, lineLength);
res.push(" | " + repeat$1("^", length$1));
}
count += lineLength + 1;
}
}
break
}
}
return res.join('\n')
}
function repeat$1 (str, n) {
var result = '';
if (n > 0) {
while (true) { // eslint-disable-line
if (n & 1) { result += str; }
n >>>= 1;
if (n <= 0) { break }
str += str;
}
}
return result
}
// 构建函数
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err: err, code: code });
return noop
}
}
function createCompileToFunctionFn (compile) {
// 创建一个空对象
var cache = Object.create(null);
return function compileToFunctions (
template, // 字符串模板 template 也就是经过那些操作获取之后的template
options, // 参数
vm // 虚拟dom
) {
// 把options和空对象混合,也就是获取options,相当于浅拷贝一份options
options = extend({}, options);
// 获取警告
var warn$$1 = options.warn || warn;
// 删除options.warn属性
delete options.warn;
/* istanbul ignore if */
{
// 检测可能的CSP限制
try {
new Function('return 1');
} catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) {
warn$$1(
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
'templates into render functions.'
);
}
}
}
// options中是否有delimiters 一般的delimiters都是delimiters: ['${', '}']这种
var key = options.delimiters
// 如果有delimiters String(['${', '}'])返回 '${,}' + template
? String(options.delimiters) + template
// 如果没有delimiters
: template;
if (cache[key]) {
// 如果cache对象中有此key属性,直接返回
return cache[key]
}
// // 执行compiled 传入template和options参数
var compiled = compile(template, options);
// 检查编译错误/技巧
{
if (compiled.errors && compiled.errors.length) {
if (options.outputSourceRange) {
compiled.errors.forEach(function (e) {
warn$$1(
"Error compiling template:\n\n" + (e.msg) + "\n\n" +
generateCodeFrame(template, e.start, e.end),
vm
);
});
} else {
warn$$1(
"Error compiling template:\n\n" + template + "\n\n" +
compiled.errors.map(function (e) { return ("- " + e); }).join('\n') + '\n',
vm
);
}
}
if (compiled.tips && compiled.tips.length) {
if (options.outputSourceRange) {
compiled.tips.forEach(function (e) { return tip(e.msg, vm); });
} else {
compiled.tips.forEach(function (msg) { return tip(msg, vm); });
}
}
}
// 将代码转换为函数
var res = {};
var fnGenErrors = [];
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
return createFunction(code, fnGenErrors)
});
//检查函数生成错误。
//只有当编译器本身存在错误时才会发生这种情况。
//主要用于代码生成开发
/* istanbul ignore if */
{
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
warn$$1(
"Failed to generate render function:\n\n" +
fnGenErrors.map(function (ref) {
var err = ref.err;
var code = ref.code;
return ((err.toString()) + " in\n\n" + code + "\n");
}).join('\n'),
vm
);
}
}
return (cache[key] = res)
}
}
/* vue编译器 */
// createCompilerCreator是个高阶函数,接受一个函数baseCompile,返回了一个函数createCompiler,createCompiler函数里又有一个compile函数,
// 里面调用了baseCompile和最初传入的baseOptions,最后返回compile函数和compileToFunctions函数
function createCompilerCreator (baseCompile) {
return function createCompiler (baseOptions) {
function compile (
template,
options
) {
// 将参数baseOptions和传入的options进行合并得到finalOptions
var finalOptions = Object.create(baseOptions);
var errors = [];
var tips = [];
var warn = function (msg, range, tip) {
(tip ? tips : errors).push(msg);
};
if (options) {
if (options.outputSourceRange) {
// match方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配
var leadingSpaceLength = template.match(/^\s*/)[0].length;
warn = function (msg, range, tip) {
var data = { msg: msg };
if (range) {
if (range.start != null) {
data.start = range.start + leadingSpaceLength;
}
if (range.end != null) {
data.end = range.end + leadingSpaceLength;
}
}
(tip ? tips : errors).push(data);
};
}
// 合并baseOptions和options的模块
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules);
}
// 合并baseOptions和options的指令
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives || null),
options.directives
);
}
// 复制options选项
for (var key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key];
}
}
}
finalOptions.warn = warn;
var compiled = baseCompile(template.trim(), finalOptions);
{
detectErrors(compiled.ast, warn);
}
compiled.errors = errors;
compiled.tips = tips;
return compiled
}
return {
compile: compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
// createCompilerCreator 函数的返回值并且传递了 baseCompile 函数作为参数
// 通过 createCompilerCreator 函数创建编译器
var createCompiler = createCompilerCreator(function baseCompile (
template,
options
) {
var ast = parse(template.trim(), options);
// 优化,遍历 AST,为每个节点做静态标记
// 标记每个节点是否为静态节点,然后进一步标记出静态根节点
// 这样在后续更新中就可以跳过这些静态节点了
// 标记静态根,用于生成渲染函数阶段,生成静态根节点的渲染函数
if (options.optimize !== false) {
optimize(ast, options);
}
// 代码生成,将 ast 转换成可执行的 render 函数的字符串形式
var code = generate(ast, options);
return {
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
});
/* */
var ref$1 = createCompiler(baseOptions);
var compile = ref$1.compile;
// 把模板字符串template编译成渲染函数render
var compileToFunctions = ref$1.compileToFunctions;
/* */
// 检查当前浏览器是否在属性值中编码字符
var div;
function getShouldDecode (href) {
div = div || document.createElement('div');
div.innerHTML = href ? "<a href=\"\n\"/>" : "<div a=\"\n\"/>";
return div.innerHTML.indexOf(' ') > 0
}
// IE会在属性值中编码换行符,而其他浏览器不会
// 解码换行符
var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false;
// 解码href换行符
var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false;
// vue会进行缓存,有缓存则读缓存结果,没有缓存则运行回调函数
var idToTemplate = cached(function (id) {
var el = query(id);
return el && el.innerHTML
});
var mount = Vue.prototype.$mount;
// $mount 方法支持传入 2 个参数,第一个是 el,它表示挂载的元素,
// 第二个参数是和服务端渲染相关,在浏览器环境下不需要传第二个参数
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && query(el);
if (el === document.body || el === document.documentElement) {
warn(
"Do not mount Vue to <html> or <body> - mount to normal elements instead."
);
return this
}
var options = this.$options;
// 如果没有定义 render 方法,则会把 el 或者 template 字符串转换成 render 方法
if (!options.render) {
//取template值
var template = options.template;
if (template) {
if (typeof template === 'string') {
// charAt() 方法可返回指定位置的字符,判断第一个字符是否为"#",template为模板字符串或者 '#tmp'形式
if (template.charAt(0) === '#') {
// document.querySelector('#tmp').innerHTML形式
template = idToTemplate(template);
/* istanbul ignore if */
if (!template) {
warn(
("Template element not found or is empty: " + (options.template)),
this
);
}
}
//document.querySelector('#tmp')形式
} else if (template.nodeType) {
template = template.innerHTML;
} else {
{
warn('invalid template option:' + template, this);
}
// template选项无效
return this
}
} else if (el) {
//如果没定义template 则把el的outerHTML作为模板
template = getOuterHTML(el);
}
if (template) {
/* istanbul ignore if */
if (config.performance && mark) {
mark('compile');
}
//将template编译为render function
var ref = compileToFunctions(template, {
outputSourceRange: "development" !== 'production',
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns;
/* istanbul ignore if */
if (config.performance && mark) {
mark('compile end');
measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
}
}
}
return mount.call(this, el, hydrating)
};
/**
* 获取元素的outerHTML,同时处理IE中的SVG元素
*/
function getOuterHTML (el) {
// 如果元素的outerHTML存在
if (el.outerHTML) {
// 返回该元素及本身的所有HTML代码
return el.outerHTML
} else {
// 如果该元素的outerHTML属性不存在,创建一个DIV
var container = document.createElement('div');
// 向DIV中添加这个el
container.appendChild(el.cloneNode(true));
// 此时返回container的innerHTML就可以得到该元素即本身的HTML代码
return container.innerHTML
}
}
Vue.compile = compileToFunctions;
return Vue;
}));