diff --git a/vue1.js b/vue1.js index 77717be..d72a163 100644 --- a/vue1.js +++ b/vue1.js @@ -2671,4 +2671,491 @@ function defineReactive$$1 ( ? provide.call(vm) : provide; } - } \ No newline at end of file + } + // 运行时辅助函数,用于将 v-bind="object" 合并到 VNode 的数据中 +function bindObjectProps ( + // VNode 的数据对象 + data, + // 标签名 + tag, + // v-bind 绑定的对象值 + value, + // 是否作为 DOM 属性绑定 + asProp, + // 是否为同步绑定 + isSync + ) { + // 如果绑定的值存在 + if (value) { + // 如果绑定的值不是对象类型 + if (!isObject(value)) { + // 发出警告,提示 v-bind 无参数时需要对象或数组值 + 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 ) { + // 如果键是 'class'、'style' 或者是保留属性 + if ( + key === 'class' || + key === 'style' || + isReservedAttribute(key) + ) { + // 目标对象为 data + hash = data; + } else { + // 获取 data 中的 type 属性 + var type = data.attrs && data.attrs.type; + // 根据条件判断目标对象是 domProps 还是 attrs + 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) { + // 获取或创建 data 中的 on 对象 + var on = data.on || (data.on = {}); + // 为 update:key 事件添加处理函数 + on[("update:" + key)] = function ($event) { + value[key] = $event; + }; + } + } + }; + + // 遍历绑定对象的每个键 + for (var key in value) loop( key ); + } + } + // 返回处理后的 data 对象 + return data +} + +// 运行时辅助函数,用于渲染静态树 +function renderStatic ( + // 静态树的索引 + index, + // 是否在 v-for 指令内部 + isInFor +) { + // 获取或创建静态树缓存数组 + var cached = this._staticTrees || (this._staticTrees = []); + // 获取缓存中的静态树 + var tree = cached[index]; + // 如果已经渲染过静态树且不在 v-for 内部 + if (tree && !isInFor) { + // 直接返回缓存的静态树 + return tree + } + // 否则,重新渲染新的静态树 + tree = cached[index] = this.$options.staticRenderFns[index].call( + // 渲染代理对象 + this._renderProxy, + null, + // 用于函数式组件模板生成的渲染函数 + this + ); + // 标记静态树 + markStatic(tree, ("__static__" + index), false); + // 返回渲染好的静态树 + return tree +} + +// 运行时辅助函数,用于 v-once 指令 +// 实际上意味着用唯一的键将节点标记为静态 +function markOnce ( + // 要标记的 VNode 树 + tree, + // 索引 + index, + // 键 + key +) { + // 标记静态节点 + markStatic(tree, ("__once__" + index + (key ? ("_" + key) : "")), true); + // 返回标记后的树 + return tree +} + +// 标记静态节点或节点数组 +function markStatic ( + // 要标记的节点或节点数组 + tree, + // 键 + key, + // 是否为 v-once 节点 + 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, + // 是否为 v-once 节点 + isOnce +) { + // 标记节点为静态节点 + node.isStatic = true; + // 设置节点的键 + node.key = key; + // 标记节点是否为 v-once 节点 + node.isOnce = isOnce; +} + +// 将对象形式的监听器绑定到 VNode 的数据上 +function bindObjectListeners ( + // VNode 的数据对象 + data, + // 监听器对象 + value +) { + // 如果监听器对象存在 + if (value) { + // 如果监听器对象不是普通对象 + if (!isPlainObject(value)) { + // 发出警告,提示 v-on 无参数时需要对象值 + warn( + 'v-on without argument expects an Object value', + this + ); + } else { + // 获取或创建 data 中的 on 对象 + 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; + } + } + } + // 返回处理后的 data 对象 + return data +} + +// 解析作用域插槽 +function resolveScopedSlots ( + // 作用域插槽函数数组 + fns, + // 解析结果对象 + res, + // 是否有动态键 + 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) { + // 如果插槽有 proxy 标记 + if (slot.proxy) { + // 设置插槽函数的 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 是用于显式移除绑定的特殊值 + // 发出警告,提示动态指令参数值无效 + 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 +) { + // 安装 markOnce 函数 + target._o = markOnce; + // 安装 toNumber 函数 + target._n = toNumber; + // 安装 toString 函数 + target._s = toString; + // 安装 renderList 函数 + target._l = renderList; + // 安装 renderSlot 函数 + target._t = renderSlot; + // 安装 looseEqual 函数 + target._q = looseEqual; + // 安装 looseIndexOf 函数 + target._i = looseIndexOf; + // 安装 renderStatic 函数 + target._m = renderStatic; + // 安装 resolveFilter 函数 + target._f = resolveFilter; + // 安装 checkKeyCodes 函数 + target._k = checkKeyCodes; + // 安装 bindObjectProps 函数 + target._b = bindObjectProps; + // 安装 createTextVNode 函数 + target._v = createTextVNode; + // 安装 createEmptyVNode 函数 + target._e = createEmptyVNode; + // 安装 resolveScopedSlots 函数 + target._u = resolveScopedSlots; + // 安装 bindObjectListeners 函数 + target._g = bindObjectListeners; + // 安装 bindDynamicKeys 函数 + target._d = bindDynamicKeys; + // 安装 prependModifier 函数 + target._p = prependModifier; +} + +// 函数式渲染上下文类 +function FunctionalRenderContext ( + // VNode 的数据对象 + data, + // 组件的 props + props, + // 子节点 + children, + // 父组件实例 + parent, + // 组件构造函数 + Ctor +) { + var this$1 = this; + + // 获取组件的选项 + var options = Ctor.options; + // 确保函数式组件中的 createElement 函数有唯一的上下文 + // 这对于正确的命名插槽检查是必要的 + var contextVm; + // 如果父组件有 _uid 属性 + if (hasOwn(parent, '_uid')) { + // 创建一个继承自父组件的对象 + contextVm = Object.create(parent); + // 记录原始父组件 + contextVm._original = parent; + } else { + // 传入的上下文 vm 也是一个函数式上下文 + // 在这种情况下,我们要确保能够获取到真实的上下文实例 + contextVm = parent; + // 获取原始父组件 + parent = parent._original; + } + // 判断组件是否是编译后的 + var isCompiled = isTrue(options._compiled); + // 判断是否需要规范化 + var needNormalization = !isCompiled; + + // 保存数据对象 + this.data = data; + // 保存 props + this.props = props; + // 保存子节点 + this.children = children; + // 保存父组件实例 + this.parent = parent; + // 保存监听器 + this.listeners = data.on || emptyObject; + // 解析注入 + this.injections = resolveInject(options.inject, parent); + // 定义 slots 方法 + this.slots = function () { + // 如果 $slots 不存在 + if (!this$1.$slots) { + // 规范化作用域插槽 + normalizeScopedSlots( + data.scopedSlots, + this$1.$slots = resolveSlots(children, parent) + ); + } + // 返回 $slots + return this$1.$slots + }; + + // 定义 scopedSlots 属性 + Object.defineProperty(this, 'scopedSlots', ({ + // 可枚举 + enumerable: true, + // 获取作用域插槽 + get: function get () { + return normalizeScopedSlots(data.scopedSlots, this.slots()) + } + })); + + // 支持编译后的函数式模板 + if (isCompiled) { + // 暴露 $options 用于 renderStatic() + this.$options = options; + // 预解析插槽用于 renderSlot() + this.$slots = this.slots(); + // 规范化作用域插槽 + this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots); + } + + // 如果有作用域 ID + if (options._scopeId) { + // 创建带有作用域 ID 的 VNode + 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 { + // 创建普通的 VNode + this._c = function (a, b, c, d) { return createElement(contextVm, a, b, c, d, needNormalization); }; + } +} + +// 安装渲染辅助函数到函数式渲染上下文的原型上 +installRenderHelpers(FunctionalRenderContext.prototype); + +// 创建函数式组件 +function createFunctionalComponent ( + // 组件构造函数 + Ctor, + // props 数据 + propsData, + // VNode 的数据对象 + data, + // 上下文 vm + contextVm, + // 子节点 + children +) { + // 获取组件的选项 + var options = Ctor.options; + // 初始化 props 对象 + var props = {}; + // 获取组件的 props 选项 + var propOptions = options.props; + // 如果 props 选项存在 + if (isDef(propOptions)) { + // 遍历 props 选项的每个键 + for (var key in propOptions) { + // 验证并设置 props 的值 + props[key] = validateProp(key, propOptions, propsData || emptyObject); + } + } else { + // 如果 data 中有 attrs 属性,合并到 props 中 + if (isDef(data.attrs)) { mergeProps(props, data.attrs); } + // 如果 data 中有 props 属性,合并到 props 中 + 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); + + // 如果渲染结果是单个 VNode + 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 + } +} \ No newline at end of file