虽然vue3已经出来很久了,但我觉得vue.js的源码还是非常值得去学习一下。vue.js里面封装的很多工具类在我们平时工作项目中也会经常用到。所以我近期会对vue.js的源码进行解读,分享值得去学习的代码片段,这篇文章将会持续更新。
1.children 的规范化:normalizeArrayChildren
2.组件实例化:initInjections
3.slot插槽函数:resolveSlots,normalizeScopedSlots,normalizeScopedSlot,proxyNormalSlot,renderSlot
1.vue的事件机制
①.监听事件:$on
②.监听事件,只监听1次:$once
③.移除自定义事件监听器:$off
④.触发事件: $emit
//children 的规范化,simpleNormalizeChildren和normalizeChildren都是用来把children由树状结构变成一维数组 // 模板编译器试图通过在编译时静态分析模板来最小化规范化的需要 // 对于纯HTML标记,可以完全跳过规范化,因为生成的呈现函数保证返回Array<VNode>。有两种情况需要额外的规范化: // 当子级包含组件时-因为功能组件可能返回数组而不是单个根。在这种情况下,只需要一个简单的规范化—如果任何子对象是数组, // 我们就用Array.prototype.concat将整个对象展平。它保证只有1级深度,因为功能组件已经规范化了它们自己的子级 function simpleNormalizeChildren (children) { for (var i = 0; i < children.length; i++) { if (Array.isArray(children[i])) { return Array.prototype.concat.apply([], children) } } return children } // 2.当子级包含总是生成嵌套数组的构造时,例如<template>、<slot>、v-for,或者当子级由用户提供手写的呈现函数/JSX时。 // 在这种情况下,需要完全正常化,以满足所有可能类型的儿童价值观。 function normalizeChildren (children) { return isPrimitive(children) ? [createTextVNode(children)] : Array.isArray(children) ? normalizeArrayChildren(children) : undefined } // node节点的判断条件 function isTextNode (node) { return isDef(node) && isDef(node.text) && isFalse(node.isComment) } //children 的规范化,normalizeArrayChildren接收 2 个参数,children 表示要规范的子节点,nestedIndex 表示嵌套的索引 function normalizeArrayChildren (children, nestedIndex) { var res = []; var i, c, lastIndex, last; //遍历children, for (i = 0; i < children.length; i++) { //将单个节点赋值给c c = children[i]; //判断c的类型,如果是一个数组类型,则递归调用 normalizeArrayChildren; //否则通过 createTextVNode 方法转换成 VNode 类型; if (isUndef(c) || typeof c === 'boolean') { continue } lastIndex = res.length - 1; last = res[lastIndex]; //如果是一个数组类型,则递归调用 normalizeArrayChildren if (Array.isArray(c)) { if (c.length > 0) { // 如果 children 是一个列表并且列表还存在嵌套的情况,则根据 nestedIndex 去更新它的 key c = normalizeArrayChildren(c, ((nestedIndex || '') + "_" + i)); if (isTextNode(c[0]) && isTextNode(last)) { res[lastIndex] = createTextVNode(last.text + (c[0]).text); c.shift(); } res.push.apply(res, c); } } else if (isPrimitive(c)) { if (isTextNode(last)) { //通过 createTextVNode 方法转换成 VNode 类型 res[lastIndex] = createTextVNode(last.text + c); } else if (c !== '') { res.push(createTextVNode(c)); } } else { if (isTextNode(c) && isTextNode(last)) { res[lastIndex] = createTextVNode(last.text + c.text); } else { if (isTrue(children._isVList) && isDef(c.tag) && isUndef(c.key) && isDef(nestedIndex)) { c.key = "__vlist" + nestedIndex + "_" + i + "__"; } res.push(c); } } } return res } /* initProvide的作用就是将$options里的provide赋值到当前实例上 */ function initProvide (vm) { //如果provide存在,当它是函数时执行该返回,否则直接将provide保存到Vue实例的_provided属性上 var provide = vm.$options.provide; if (provide) { vm._provided = typeof provide === 'function' ? provide.call(vm) : provide; } } //组件实例化,初始化Inject参数, initInjections在初始化data/props之前被调用,主要作用是初始化vue实例的inject function initInjections (vm) { //遍历祖先节点,获取对应的inject var result = resolveInject(vm.$options.inject, vm); if (result) { // toggleObserving是vue内部对逻辑的一个优化,就是禁止掉根组件 props的依赖收集 toggleObserving(false); Object.keys(result).forEach(function (key) { //将key编程响应式,这样就可以访问该元素 { defineReactive$$1(vm, key, result[key], function () { warn( "Avoid mutating an injected value directly since the changes will be " + "overwritten whenever the provided component re-renders. " + "injection being mutated: \"" + key + "\"", vm ); }); } }); toggleObserving(true); } } // 确定Inject,vm指当前组件的实例 function resolveInject (inject, vm) { if (inject) { // inject is :any because flow is not smart enough to figure out cached var result = Object.create(null); //如果有符号类型,调用Reflect.ownKeys()返回所有的key,再调用filter var keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject); //获取所有的key,此时keys就是个字符串数组 for (var i = 0; i < keys.length; i++) { var key = keys[i]; // #6574 in case the inject object is observed... if (key === '__ob__') { continue } var provideKey = inject[key].from; var source = vm; while (source) { //如果source存在_provided 且 含有provideKey这个属性,则将值保存到result[key]中 if (source._provided && hasOwn(source._provided, provideKey)) { result[key] = source._provided[provideKey]; break } // 否则将source赋值给父Vue实例,直到找到对应的providekey为止 source = source.$parent; } // 如果最后source不存在,即没有从当前实例或祖先实例的_provide找到privideKey这个key if (!source) { // 如果有定义defult,则使用默认值 if ('default' in inject[key]) { var provideDefault = inject[key].default; result[key] = typeof provideDefault === 'function' ? provideDefault.call(vm) : provideDefault; } else { warn(("Injection \"" + key + "\" not found"), vm); } } } return result } } /* */ /** * 主要作用是将children VNodes转化成一个slots对象,处理组件slot,返回slot插槽对象 * children指占位符Vnode里的内容 * context指占位符Vnode所在的vue实例 */ function resolveSlots ( children, context ) { // 判断是否有children,即是否有插槽VNode if (!children || !children.length) { return {} } var slots = {}; // 遍历每一个子节点 for (var i = 0, l = children.length; i < l; i++) { var child = children[i]; // data为VNodeData,保存父组件传递到子组件的props以及attrs等 var data = child.data; //移出slot,删除该节点attrs的slot if (data && data.attrs && data.attrs.slot) { delete data.attrs.slot; } // 判断是否为具名插槽,如果为具名插槽,还需要 子组件 / 函数子组件 渲染上下文一致 // 当需要向子组件的子组件传递具名插槽时,不会保持插槽的名字 if ((child.context === context || child.fnContext === context) && data && data.slot != null ) { var name = data.slot; var slot = (slots[name] || (slots[name] = [])); //处理父组件采用template形式的插槽 if (child.tag === 'template') { slot.push.apply(slot, child.children || []); } else { slot.push(child); } } else { //返回匿名default插槽VNode数组 (slots.default || (slots.default = [])).push(child); } } // 忽略仅仅包含whitespace的插槽 for (var name$1 in slots) { if (slots[name$1].every(isWhitespace)) { delete slots[name$1]; } } return slots } // 方法用于判断指定字符是否为空白字符,空白符包含:空格、tab键、换行符 function isWhitespace (node) { return (node.isComment && !node.asyncFactory) || node.text === ' ' } /* 是否为异步占位 */ function isAsyncPlaceholder (node) { return node.isComment && node.asyncFactory } /*normalizeScopedSlots函数的核心就是返回res对象,其key为slotTarget,value为fn */ //slots: 某节点 data 属性上 scopedSlots //normalSlots: 当前节点下的普通插槽 //prevSlots 当前节点下的特殊插槽 function normalizeScopedSlots ( slots, normalSlots, prevSlots ) { var res; //判断是否拥有普通插槽 var hasNormalSlots = Object.keys(normalSlots).length > 0; var isStable = slots ? !!slots.$stable : !hasNormalSlots; var key = slots && slots.$key; if (!slots) { res = {}; } else if (slots._normalized) { return slots._normalized } else if ( isStable && prevSlots && prevSlots !== emptyObject && // slots $key 值与 prevSlots $key 相等 key === prevSlots.$key && //slots中没有普通插槽 !hasNormalSlots && //prevSlots中没有普通插槽 !prevSlots.$hasNormal ) { return prevSlots } else { res = {}; //遍历作用域插槽 for (var key$1 in slots) { if (slots[key$1] && key$1[0] !== '$') { res[key$1] = normalizeScopedSlot(normalSlots, key$1, slots[key$1]); } } } // 对普通插槽进行遍历,将slot代理到scopeSlots上 for (var key$2 in normalSlots) { if (!(key$2 in res)) { res[key$2] = proxyNormalSlot(normalSlots, key$2); } } // avoriaz seems to mock a non-extensible $scopedSlots object // and when that is passed down this would cause an error if (slots && Object.isExtensible(slots)) { (slots)._normalized = res; } // $key , $hasNormal , $stable 是直接使用 vue 内部对 Object.defineProperty 封装好的 def() 方法进行赋值的 def(res, '$stable', isStable); def(res, '$key', key); def(res, '$hasNormal', hasNormalSlots); return res } //将scopeSlots对应属性和方法挂载到scopeSlots,生成闭包,返回一个名为normalized的函数,$scopedSlots对象中的值就是此函数 function normalizeScopedSlot(normalSlots, key, fn) { var normalized = function () { var res = arguments.length ? fn.apply(null, arguments) : fn({}); res = res && typeof res === 'object' && !Array.isArray(res) ? [res] // single vnode : normalizeChildren(res); var vnode = res && res[0]; return res && ( !vnode || (res.length === 1 && vnode.isComment && !isAsyncPlaceholder(vnode)) // #9658, #10391 ) ? undefined : res }; // this is a slot using the new v-slot syntax without scope. although it is // compiled as a scoped slot, render fn users would expect it to be present // on this.$slots because the usage is semantically a normal slot. if (fn.proxy) { Object.defineProperty(normalSlots, key, { get: normalized, enumerable: true, configurable: true }); } return normalized } // 将slot代理到scopeSlots上 function proxyNormalSlot(slots, key) { return function () { return slots[key]; } } /* */ /** * Runtime helper for rendering v-for lists. */ function renderList ( val, render ) { var ret, i, l, keys, key; //如果val为数组,则遍历val if (Array.isArray(val) || typeof val === 'string') { ret = new Array(val.length); // 调用传入的函数,把值传入,数组保存结果 for (i = 0, l = val.length; i < l; i++) { ret[i] = render(val[i], i); } //如果val为数字类型 } else if (typeof val === 'number') { ret = new Array(val); // 调用传入的函数,把值传入,数组保存结果 for (i = 0; i < val; i++) { ret[i] = render(i + 1, i); } //如果val为object类型,则遍历对象 } else if (isObject(val)) { if (hasSymbol && val[Symbol.iterator]) { ret = []; var iterator = val[Symbol.iterator](); var result = iterator.next(); // 调用传入的函数,把值传入,数组保存结果 while (!result.done) { ret.push(render(result.value, ret.length)); result = iterator.next(); } } else { keys = Object.keys(val); ret = new Array(keys.length); for (i = 0, l = keys.length; i < l; i++) { key = keys[i]; ret[i] = render(val[key], key, i); } } } if (!isDef(ret)) { ret = []; } (ret)._isVList = true; return ret } /* */ // 调用renderSlot用函数的返回值进行渲染 // renderSlot函数会根据插槽名字找到对应的作用域Slot包装成的函数, // 然后执行它,把子组件内的数据{ child:child }传进去 function renderSlot ( name,//插槽名 fallbackRender,//插槽默认内容生成的 vnode 数组 props,// props 对象 bindObject //v-bind 绑定对象 ) { var scopedSlotFn = this.$scopedSlots[name]; var nodes; if (scopedSlotFn) { props = props || {}; if (bindObject) { if (!isObject(bindObject)) { warn('slot v-bind without argument expects an Object', this); } props = extend(extend({}, bindObject), props); } nodes = scopedSlotFn(props) || (typeof fallbackRender === 'function' ? fallbackRender() : fallbackRender); } else { nodes = this.$slots[name] || (typeof fallbackRender === 'function' ? fallbackRender() : fallbackRender); } var target = props && props.slot; if (target) { return this.$createElement('template', { slot: target }, nodes) } else { return nodes } } /* */ /** *找到我们写的过滤器,并将参数传入进去 */ function resolveFilter (id) { // resolveAsset用于获取资源,也就是获取组件的构造函数 return resolveAsset(this.$options, 'filters', id, true) || identity } /* 检查按下的键,是否和配置的键值对匹配 */ function isKeyNotMatch (expect, actual) { if (Array.isArray(expect)) { return expect.indexOf(actual) === -1 } else { return expect !== actual } } /** * 用于检查config.prototype中的键代码的运行时帮助程序,以Vue.prototype的形式公开 */ function checkKeyCodes ( eventKeyCode, key, builtInKeyCode, eventKeyName, builtInKeyName ) { // 比如 key 传入的是自定义名字 aaaa // keyCode 从Vue 定义的 keyNames 获取 aaaa 的实际数字 // keyName 从 Vue 定义的 keyCode 获取 aaaa 的别名 // 并且以用户定义的为准,可以覆盖Vue 内部定义的 var mappedKeyCode = config.keyCodes[key] || builtInKeyCode; // 该键只在 Vue 内部定义的 keyCode 中 if (builtInKeyName && eventKeyName && !config.keyCodes[key]) { return isKeyNotMatch(builtInKeyName, eventKeyName) // 该键只在 用户自定义配置的 keyCode 中 } else if (mappedKeyCode) { return isKeyNotMatch(mappedKeyCode, eventKeyCode) //原始键名 } else if (eventKeyName) { return hyphenate(eventKeyName) !== key } return eventKeyCode === undefined } /** * 用于将v-bind=“object”合并到VNode数据中的运行时帮助程序 */ function bindObjectProps ( data, tag, value, asProp, isSync ) { if (value) { if (!isObject(value)) { warn( 'v-bind without argument expects an Object or Array value', this ); } else { if (Array.isArray(value)) { value = toObject(value); } var hash; var loop = function ( key ) { if ( key === 'class' || key === 'style' || isReservedAttribute(key) ) { hash = data; } else { var type = data.attrs && data.attrs.type; hash = asProp || config.mustUseProp(tag, type, key) ? data.domProps || (data.domProps = {}) : data.attrs || (data.attrs = {}); } var camelizedKey = camelize(key); var hyphenatedKey = hyphenate(key); if (!(camelizedKey in hash) && !(hyphenatedKey in hash)) { hash[key] = value[key]; if (isSync) { var on = data.on || (data.on = {}); on[("update:" + key)] = function ($event) { value[key] = $event; }; } } }; for (var key in value) loop( key ); } } return data } /* */ /** * 生成静态元素 */ function renderStatic ( index, isInFor ) { var cached = this._staticTrees || (this._staticTrees = []); var tree = cached[index]; // if has already-rendered static tree and not inside v-for, // we can reuse the same tree. if (tree && !isInFor) { return tree } // otherwise, render a fresh tree. tree = cached[index] = this.$options.staticRenderFns[index].call( this._renderProxy, null, this // for render fns generated for functional component templates ); markStatic(tree, ("__static__" + index), false); return tree } /** * 标记v-once */ function markOnce ( tree, index, key ) { markStatic(tree, ("__once__" + index + (key ? ("_" + key) : "")), true); return tree } // 标记静态元素 function markStatic ( tree, key, isOnce ) { if (Array.isArray(tree)) { for (var i = 0; i < tree.length; i++) { if (tree[i] && typeof tree[i] !== 'string') { markStaticNode(tree[i], (key + "_" + i), isOnce); } } } else { markStaticNode(tree, key, isOnce); } } //标记静态节点 function markStaticNode (node, key, isOnce) { node.isStatic = true; node.key = key; node.isOnce = isOnce; } /* //处理v-on=’{}'到vnode data上 */ function bindObjectListeners (data, value) { if (value) { if (!isPlainObject(value)) { warn( 'v-on without argument expects an Object value', this ); } else { var on = data.on = data.on ? extend({}, data.on) : {}; for (var key in value) { var existing = on[key]; var ours = value[key]; on[key] = existing ? [].concat(existing, ours) : ours; } } } return data } /* 获取作用域插槽 */ function resolveScopedSlots ( fns, // see flow/vnode res, // the following are added in 2.6 hasDynamicKeys, contentHashKey ) { res = res || { $stable: !hasDynamicKeys }; for (var i = 0; i < fns.length; i++) { var slot = fns[i]; if (Array.isArray(slot)) { resolveScopedSlots(slot, res, hasDynamicKeys); } else if (slot) { // marker for reverse proxying v-slot without scope on this.$slots if (slot.proxy) { slot.fn.proxy = true; } res[slot.key] = slot.fn; } } if (contentHashKey) { (res).$key = contentHashKey; } return res } /*//处理动态属性名 */ function bindDynamicKeys (baseObj, values) { for (var i = 0; i < values.length; i += 2) { var key = values[i]; if (typeof key === 'string' && key) { baseObj[values[i]] = values[i + 1]; } else if (key !== '' && key !== null) { // null is a special value for explicitly removing a binding warn( ("Invalid value for dynamic directive argument (expected string or null): " + key), this ); } } return baseObj } //处理修饰符 // 帮助程序将修改器运行时标记动态追加到事件名称。 // 请确保仅在值已为字符串时追加,否则将转换为字符串并导致类型检查丢失。 function prependModifier (value, symbol) { return typeof value === 'string' ? symbol + value : value } /* 安装渲染工具函数,大多数服务于编译器编译出来的代码 */ function installRenderHelpers (target) { target._o = markOnce;// 标记v-once target._n = toNumber;// 转换成Number类型 target._s = toString;//转换成字符串 target._l = renderList;//生成列表VNode target._t = renderSlot;//生成解析slot节点 target._q = looseEqual; target._i = looseIndexOf; target._m = renderStatic;//生成静态元素 target._f = resolveFilter;// 获取过滤器 target._k = checkKeyCodes;//检查键盘事件keycode target._b = bindObjectProps;//绑定对象属性 target._v = createTextVNode;//创建文本VNod target._e = createEmptyVNode;//创建空节点VNode target._u = resolveScopedSlots;//获取作用域插槽 target._g = bindObjectListeners;//处理v-on=’{}'到vnode data上 target._d = bindDynamicKeys;//处理动态属性名 target._p = prependModifier;//处理修饰符 } /* */ function FunctionalRenderContext ( data, props, children, parent, Ctor ) { var this$1 = this; var options = Ctor.options; // ensure the createElement function in functional components // gets a unique context - this is necessary for correct named slot check var contextVm; if (hasOwn(parent, '_uid')) { contextVm = Object.create(parent); // $flow-disable-line contextVm._original = parent; } else { // the context vm passed in is a functional context as well. // in this case we want to make sure we are able to get a hold to the // real context instance. contextVm = parent; // $flow-disable-line parent = parent._original; } var isCompiled = isTrue(options._compiled); var needNormalization = !isCompiled; this.data = data; this.props = props; this.children = children; this.parent = parent; this.listeners = data.on || emptyObject; this.injections = resolveInject(options.inject, parent); this.slots = function () { if (!this$1.$slots) { normalizeScopedSlots( data.scopedSlots, this$1.$slots = resolveSlots(children, parent) ); } return this$1.$slots }; Object.defineProperty(this, 'scopedSlots', ({ enumerable: true, get: function get () { return normalizeScopedSlots(data.scopedSlots, this.slots()) } })); // support for compiled functional template if (isCompiled) { // exposing $options for renderStatic() this.$options = options; // pre-resolve slots for renderSlot() this.$slots = this.slots(); this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots); } if (options._scopeId) { this._c = function (a, b, c, d) { var vnode = createElement(contextVm, a, b, c, d, needNormalization); if (vnode && !Array.isArray(vnode)) { vnode.fnScopeId = options._scopeId; vnode.fnContext = parent; } return vnode }; } else { this._c = function (a, b, c, d) { return createElement(contextVm, a, b, c, d, needNormalization); }; } } installRenderHelpers(FunctionalRenderContext.prototype); function createFunctionalComponent ( Ctor, propsData, data, contextVm, children ) { var options = Ctor.options; var props = {}; var propOptions = options.props; if (isDef(propOptions)) { for (var key in propOptions) { props[key] = validateProp(key, propOptions, propsData || emptyObject); } } else { if (isDef(data.attrs)) { mergeProps(props, data.attrs); } if (isDef(data.props)) { mergeProps(props, data.props); } } var renderContext = new FunctionalRenderContext( data, props, children, contextVm, Ctor ); var vnode = options.render.call(null, renderContext._c, renderContext); if (vnode instanceof VNode) { return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext) } else if (Array.isArray(vnode)) { var vnodes = normalizeChildren(vnode) || []; var res = new Array(vnodes.length); for (var i = 0; i < vnodes.length; i++) { res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext); } return res } } function cloneAndMarkFunctionalResult (vnode, data, contextVm, options, renderContext) { // #7817 clone node before setting fnContext, otherwise if the node is reused // (e.g. it was from a cached normal slot) the fnContext causes named slots // that should not be matched to match. var clone = cloneVNode(vnode); clone.fnContext = contextVm; clone.fnOptions = options; { (clone.devtoolsMeta = clone.devtoolsMeta || {}).renderContext = renderContext; } if (data.slot) { (clone.data || (clone.data = {})).slot = data.slot; } return clone } function mergeProps (to, from) { for (var key in from) { to[camelize(key)] = from[key]; } } /* */ /* */ /* */ /* */ // inline hooks to be invoked on component VNodes during patch var componentVNodeHooks = { init: function init (vnode, hydrating) { if ( vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { // kept-alive components, treat as a patch var mountedNode = vnode; // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode); } else { var child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ); child.$mount(hydrating ? vnode.elm : undefined, hydrating); } }, prepatch: function prepatch (oldVnode, vnode) { var options = vnode.componentOptions; var child = vnode.componentInstance = oldVnode.componentInstance; updateChildComponent( child, options.propsData, // updated props options.listeners, // updated listeners vnode, // new parent vnode options.children // new children ); }, insert: function insert (vnode) { var context = vnode.context; var componentInstance = vnode.componentInstance; if (!componentInstance._isMounted) { componentInstance._isMounted = true; callHook(componentInstance, 'mounted'); } if (vnode.data.keepAlive) { if (context._isMounted) { // vue-router#1212 // During updates, a kept-alive component's child components may // change, so directly walking the tree here may call activated hooks // on incorrect children. Instead we push them into a queue which will // be processed after the whole patch process ended. queueActivatedComponent(componentInstance); } else { activateChildComponent(componentInstance, true /* direct */); } } }, destroy: function destroy (vnode) { var componentInstance = vnode.componentInstance; if (!componentInstance._isDestroyed) { if (!vnode.data.keepAlive) { componentInstance.$destroy(); } else { deactivateChildComponent(componentInstance, true /* direct */); } } } }; var hooksToMerge = Object.keys(componentVNodeHooks); function createComponent ( Ctor, data, context, children, tag ) { if (isUndef(Ctor)) { return } var baseCtor = context.$options._base; // plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor); } // if at this stage it's not a constructor or an async component factory, // reject. if (typeof Ctor !== 'function') { { warn(("Invalid Component definition: " + (String(Ctor))), context); } return } // async component var asyncFactory; if (isUndef(Ctor.cid)) { asyncFactory = Ctor; Ctor = resolveAsyncComponent(asyncFactory, baseCtor); if (Ctor === undefined) { // return a placeholder node for async component, which is rendered // as a comment node but preserves all the raw information for the node. // the information will be used for async server-rendering and hydration. return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } data = data || {}; // resolve constructor options in case global mixins are applied after // component constructor creation resolveConstructorOptions(Ctor); // transform component v-model data into props & events if (isDef(data.model)) { transformModel(Ctor.options, data); } // extract props var propsData = extractPropsFromVNodeData(data, Ctor, tag); // functional component if (isTrue(Ctor.options.functional)) { return createFunctionalComponent(Ctor, propsData, data, context, children) } // extract listeners, since these needs to be treated as // child component listeners instead of DOM listeners var listeners = data.on; // replace with listeners with .native modifier // so it gets processed during parent component patch. data.on = data.nativeOn; if (isTrue(Ctor.options.abstract)) { // abstract components do not keep anything // other than props & listeners & slot // work around flow var slot = data.slot; data = {}; if (slot) { data.slot = slot; } } // install component management hooks onto the placeholder node installComponentHooks(data); // return a placeholder vnode var name = Ctor.options.name || tag; var vnode = new VNode( ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')), data, undefined, undefined, undefined, context, { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }, asyncFactory ); return vnode } function createComponentInstanceForVnode ( // we know it's MountedComponentVNode but flow doesn't vnode, // activeInstance in lifecycle state parent ) { var options = { _isComponent: true, _parentVnode: vnode, parent: parent }; // check inline-template render functions var inlineTemplate = vnode.data.inlineTemplate; if (isDef(inlineTemplate)) { options.render = inlineTemplate.render; options.staticRenderFns = inlineTemplate.staticRenderFns; } return new vnode.componentOptions.Ctor(options) } function installComponentHooks (data) { var hooks = data.hook || (data.hook = {}); for (var i = 0; i < hooksToMerge.length; i++) { var key = hooksToMerge[i]; var existing = hooks[key]; var toMerge = componentVNodeHooks[key]; if (existing !== toMerge && !(existing && existing._merged)) { hooks[key] = existing ? mergeHook$1(toMerge, existing) : toMerge; } } } function mergeHook$1 (f1, f2) { var merged = function (a, b) { // flow complains about extra args which is why we use any f1(a, b); f2(a, b); }; merged._merged = true; return merged } // transform component v-model info (value and callback) into // prop and event handler respectively. function transformModel (options, data) { var prop = (options.model && options.model.prop) || 'value'; var event = (options.model && options.model.event) || 'input' ;(data.attrs || (data.attrs = {}))[prop] = data.model.value; var on = data.on || (data.on = {}); var existing = on[event]; var callback = data.model.callback; if (isDef(existing)) { if ( Array.isArray(existing) ? existing.indexOf(callback) === -1 : existing !== callback ) { on[event] = [callback].concat(existing); } } else { on[event] = callback; } } /* */ var SIMPLE_NORMALIZE = 1; var ALWAYS_NORMALIZE = 2; // wrapper function for providing a more flexible interface // without getting yelled at by flow function createElement ( context, tag, data, children, normalizationType, alwaysNormalize ) { if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children; children = data; data = undefined; } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE; } return _createElement(context, tag, data, children, normalizationType) } function _createElement ( context, tag, data, children, normalizationType ) { if (isDef(data) && isDef((data).__ob__)) { warn( "Avoid using observed data object as vnode data: " + (JSON.stringify(data)) + "\n" + 'Always create fresh vnode data objects in each render!', context ); return createEmptyVNode() } // object syntax in v-bind if (isDef(data) && isDef(data.is)) { tag = data.is; } if (!tag) { // in case of component :is set to falsy value return createEmptyVNode() } // warn against non-primitive key if (isDef(data) && isDef(data.key) && !isPrimitive(data.key) ) { { warn( 'Avoid using non-primitive value as key, ' + 'use string/number value instead.', context ); } } // support single function children as default scoped slot if (Array.isArray(children) && typeof children[0] === 'function' ) { data = data || {}; data.scopedSlots = { default: children[0] }; children.length = 0; } if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children); } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children); } var vnode, ns; if (typeof tag === 'string') { var Ctor; ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag); if (config.isReservedTag(tag)) { // platform built-in elements if (isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') { warn( ("The .native modifier for v-on is only valid on components but it was used on <" + tag + ">."), context ); } vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ); } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // component vnode = createComponent(Ctor, data, context, children, tag); } else { // unknown or unlisted namespaced elements // check at runtime because it may get assigned a namespace when its // parent normalizes children vnode = new VNode( tag, data, children, undefined, undefined, context ); } } else { // direct component options / constructor vnode = createComponent(tag, data, context, children); } if (Array.isArray(vnode)) { return vnode } else if (isDef(vnode)) { if (isDef(ns)) { applyNS(vnode, ns); } if (isDef(data)) { registerDeepBindings(data); } return vnode } else { return createEmptyVNode() } } function applyNS (vnode, ns, force) { vnode.ns = ns; if (vnode.tag === 'foreignObject') { // use default namespace inside foreignObject ns = undefined; force = true; } if (isDef(vnode.children)) { for (var i = 0, l = vnode.children.length; i < l; i++) { var child = vnode.children[i]; if (isDef(child.tag) && ( isUndef(child.ns) || (isTrue(force) && child.tag !== 'svg'))) { applyNS(child, ns, force); } } } } // ref #5318 // necessary to ensure parent re-render when deep bindings like :style and // :class are used on slot nodes function registerDeepBindings (data) { if (isObject(data.style)) { traverse(data.style); } if (isObject(data.class)) { traverse(data.class); } } /* */ function initRender (vm) { vm._vnode = null; // the root of the child tree vm._staticTrees = null; // v-once cached trees var options = vm.$options; var parentVnode = vm.$vnode = options._parentVnode; // the placeholder node in parent tree var renderContext = parentVnode && parentVnode.context; vm.$slots = resolveSlots(options._renderChildren, renderContext); vm.$scopedSlots = emptyObject; // bind the createElement fn to this instance // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize // internal version is used by render functions compiled from templates vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); }; // normalization is always applied for the public version, used in // user-written render functions. vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); }; // $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated var parentData = parentVnode && parentVnode.data; /* istanbul ignore else */ { defineReactive$$1(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () { !isUpdatingChildComponent && warn("$attrs is readonly.", vm); }, true); defineReactive$$1(vm, '$listeners', options._parentListeners || emptyObject, function () { !isUpdatingChildComponent && warn("$listeners is readonly.", vm); }, true); } } var currentRenderingInstance = null; function renderMixin (Vue) { // install runtime convenience helpers installRenderHelpers(Vue.prototype); Vue.prototype.$nextTick = function (fn) { return nextTick(fn, this) }; Vue.prototype._render = function () { var vm = this; var ref = vm.$options; var render = ref.render; var _parentVnode = ref._parentVnode; if (_parentVnode) { vm.$scopedSlots = normalizeScopedSlots( _parentVnode.data.scopedSlots, vm.$slots, vm.$scopedSlots ); } // set parent vnode. this allows render functions to have access // to the data on the placeholder node. vm.$vnode = _parentVnode; // render self var vnode; try { // There's no need to maintain a stack because all render fns are called // separately from one another. Nested component's render fns are called // when parent component is patched. currentRenderingInstance = vm; vnode = render.call(vm._renderProxy, vm.$createElement); } catch (e) { handleError(e, vm, "render"); // return error render result, // or previous vnode to prevent render error causing blank component /* istanbul ignore else */ if (vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e); } catch (e) { handleError(e, vm, "renderError"); vnode = vm._vnode; } } else { vnode = vm._vnode; } } finally { currentRenderingInstance = null; } // if the returned array contains only a single node, allow it if (Array.isArray(vnode) && vnode.length === 1) { vnode = vnode[0]; } // return empty vnode in case the render function errored out if (!(vnode instanceof VNode)) { if (Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ); } vnode = createEmptyVNode(); } // set parent vnode.parent = _parentVnode; return vnode }; } /* */ function ensureCtor (comp, base) { if ( comp.__esModule || (hasSymbol && comp[Symbol.toStringTag] === 'Module') ) { comp = comp.default; } return isObject(comp) ? base.extend(comp) : comp } function createAsyncPlaceholder ( factory, data, context, children, tag ) { var node = createEmptyVNode(); node.asyncFactory = factory; node.asyncMeta = { data: data, context: context, children: children, tag: tag }; return node } function resolveAsyncComponent ( factory, baseCtor ) { if (isTrue(factory.error) && isDef(factory.errorComp)) { return factory.errorComp } if (isDef(factory.resolved)) { return factory.resolved } var owner = currentRenderingInstance; if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) { // already pending factory.owners.push(owner); } if (isTrue(factory.loading) && isDef(factory.loadingComp)) { return factory.loadingComp } if (owner && !isDef(factory.owners)) { var owners = factory.owners = [owner]; var sync = true; var timerLoading = null; var timerTimeout = null ;(owner).$on('hook:destroyed', function () { return remove(owners, owner); }); var forceRender = function (renderCompleted) { for (var i = 0, l = owners.length; i < l; i++) { (owners[i]).$forceUpdate(); } if (renderCompleted) { owners.length = 0; if (timerLoading !== null) { clearTimeout(timerLoading); timerLoading = null; } if (timerTimeout !== null) { clearTimeout(timerTimeout); timerTimeout = null; } } }; var resolve = once(function (res) { // cache resolved factory.resolved = ensureCtor(res, baseCtor); // invoke callbacks only if this is not a synchronous resolve // (async resolves are shimmed as synchronous during SSR) if (!sync) { forceRender(true); } else { owners.length = 0; } }); var reject = once(function (reason) { warn( "Failed to resolve async component: " + (String(factory)) + (reason ? ("\nReason: " + reason) : '') ); if (isDef(factory.errorComp)) { factory.error = true; forceRender(true); } }); var res = factory(resolve, reject); if (isObject(res)) { if (isPromise(res)) { // () => Promise if (isUndef(factory.resolved)) { res.then(resolve, reject); } } else if (isPromise(res.component)) { res.component.then(resolve, reject); if (isDef(res.error)) { factory.errorComp = ensureCtor(res.error, baseCtor); } if (isDef(res.loading)) { factory.loadingComp = ensureCtor(res.loading, baseCtor); if (res.delay === 0) { factory.loading = true; } else { timerLoading = setTimeout(function () { timerLoading = null; if (isUndef(factory.resolved) && isUndef(factory.error)) { factory.loading = true; forceRender(false); } }, res.delay || 200); } } if (isDef(res.timeout)) { timerTimeout = setTimeout(function () { timerTimeout = null; if (isUndef(factory.resolved)) { reject( "timeout (" + (res.timeout) + "ms)" ); } }, res.timeout); } } } sync = false; // return in case resolved synchronously return factory.loading ? factory.loadingComp : factory.resolved } } /* */ function getFirstComponentChild (children) { if (Array.isArray(children)) { for (var i = 0; i < children.length; i++) { var c = children[i]; if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) { return c } } } } /* */ /* */ function initEvents (vm) { vm._events = Object.create(null); vm._hasHookEvent = false; // init parent attached events var listeners = vm.$options._parentListeners; if (listeners) { updateComponentListeners(vm, listeners); } } var target; function add (event, fn) { target.$on(event, fn); } function remove$1 (event, fn) { target.$off(event, fn); } function createOnceHandler (event, fn) { var _target = target; return function onceHandler () { var res = fn.apply(null, arguments); if (res !== null) { _target.$off(event, onceHandler); } } } function updateComponentListeners ( vm, listeners, oldListeners ) { target = vm; updateListeners(listeners, oldListeners || {}, add, remove$1, createOnceHandler, vm); target = undefined; } function eventsMixin (Vue) { var hookRE = /^hook:/; // 监听事件 Vue.prototype.$on = function (event, fn) { var vm = this; //当传入的监听事件为数组,则循环遍历调用$on,否则将监听事件和回调函数添加到事件处理中心_events对象中 if (Array.isArray(event)) { for (var i = 0, l = event.length; i < l; i++) { vm.$on(event[i], fn); } } else { // 之前已经有监听event事件,则将此次监听的回调函数添加到其数组中,否则创建一个新数组并添加fn (vm._events[event] || (vm._events[event] = [])).push(fn); if (hookRE.test(event)) { vm._hasHookEvent = true; } } return vm }; //监听事件,只监听1次, Vue中的事件机制,Vue中的事件机制本身就是一个订阅-发布模式的实现 Vue.prototype.$once = function (event, fn) { var vm = this; // 定义监听事件的回调函数 function on () { // 从事件中心移除监听事件的回调函数 vm.$off(event, on); // 执行回调函数 fn.apply(vm, arguments); } // 这个赋值是在$off方法里会用到的 // 比如我们调用了vm.$off(fn)来移除fn回调函数,然而我们在调用$once的时候,实际执行的是vm.$on(event, on) // 所以在event的回调函数数组里添加的是on函数,这个时候要移除fn,我们无法在回调函数数组里面找到fn函数移除,只能找到on函数 // 我们可以通过on.fn === fn来判断这种情况,并在回调函数数组里移除on函数 on.fn = fn; // 通过$on方法注册事件,$once最终调用的是$on,并且回调函数是on vm.$on(event, on); return vm }; // 移除自定义事件监听器。 // 如果没有提供参数,则移除所有的事件监听器; // 如果只提供了事件,则移除该事件所有的监听器; // 如果同时提供了事件与回调,则只移除这个回调的监听器。 Vue.prototype.$off = function (event, fn) { var vm = this; //调用this.$off()没有传参数,则清空事件处理中心缓存的事件及其回调 if (!arguments.length) { vm._events = Object.create(null); return vm } //当event为数组,则循环遍历调用$off,否则将监听事件和回调函数添加到事件处理中心_events对象中 if (Array.isArray(event)) { for (var i$1 = 0, l = event.length; i$1 < l; i$1++) { vm.$off(event[i$1], fn); } return vm } // 获取当前event里所有的回调函数 var cbs = vm._events[event]; // 如果不存在回调函数,则直接返回,因为没有可以移除监听的内容 if (!cbs) { return vm } // 如果没有指定要移除的回调函数,则移除该事件下所有的回调函数 if (!fn) { vm._events[event] = null; return vm } // 指定了要移除的回调函数 var cb; var i = cbs.length; while (i--) { cb = cbs[i]; // 在事件对应的回调函数数组里面找出要移除的回调函数,并从数组里移除 if (cb === fn || cb.fn === fn) { cbs.splice(i, 1); break } } return vm }; // 触发事件 Vue.prototype.$emit = function (event) { var vm = this; { var lowerCaseEvent = event.toLowerCase(); if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) { tip( "Event \"" + lowerCaseEvent + "\" is emitted in component " + (formatComponentName(vm)) + " but the handler is registered for \"" + event + "\". " + "Note that HTML attributes are case-insensitive and you cannot use " + "v-on to listen to camelCase events when using in-DOM templates. " + "You should probably use \"" + (hyphenate(event)) + "\" instead of \"" + event + "\"." ); } } // 触发事件对应的回调函数列表 var cbs = vm._events[event]; if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs; // $emit方法可以传参,这些参数会在调用回调函数的时候传进去 var args = toArray(arguments, 1); var info = "event handler for \"" + event + "\""; // 遍历回调函数列表,调用每个回调函数 for (var i = 0, l = cbs.length; i < l; i++) { invokeWithErrorHandling(cbs[i], vm, args, vm, info); } } return vm }; }