From 5d0222ecda4735a57c32c844093d10e4bfb89702 Mon Sep 17 00:00:00 2001 From: p2x9nfpei <2196435763@qq.com> Date: Tue, 29 Apr 2025 02:06:37 +0800 Subject: [PATCH] ADD file via upload --- vuedw.js | 4216 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4216 insertions(+) create mode 100644 vuedw.js diff --git a/vuedw.js b/vuedw.js new file mode 100644 index 0000000..bef76e6 --- /dev/null +++ b/vuedw.js @@ -0,0 +1,4216 @@ +// 缓存样式文本解析器,将CSS字符串转换为样式对象 +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 // 返回解析后的样式对象 +}); + +// 合并静态样式和动态样式数据 +function normalizeStyleData (data) { + var style = normalizeStyleBinding(data.style); // 标准化动态样式 + return data.staticStyle // 合并静态样式(编译时处理) + ? extend(data.staticStyle, style) + : style +} + +// 标准化样式绑定值(支持数组/字符串/对象格式) +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 && (styleData = normalizeStyleData(childNode.data))) { + extend(res, styleData); // 合并子组件样式 + } + } + } + + if ((styleData = normalizeStyleData(vnode.data))) { // 当前节点样式 + extend(res, styleData); + } + + var parentNode = vnode; + while ((parentNode = parentNode.parent)) { // 遍历父节点链 + if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) { + extend(res, styleData); // 合并父组件样式 + } + } + return res // 返回最终合并结果 +} + +// 匹配CSS自定义属性(--开头) +var cssVarRE = /^--/; +// 匹配!important结尾 +var importantRE = /\s*!important$/; +// 设置元素样式属性(处理多种情况) +var setProp = function (el, name, val) { + if (cssVarRE.test(name)) { // CSS变量处理 + el.style.setProperty(name, val); + } else if (importantRE.test(val)) { // 含!important处理 + 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; // 实际DOM元素 + var oldStaticStyle = oldData.staticStyle; // 旧静态样式 + var oldStyleBinding = oldData.normalizedStyle || oldData.style || {}; // 旧动态样式 + + 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]) { // 值发生变化时更新 + setProp(el, name, cur == null ? '' : cur); // null转为空字符串 + } + } +} + +// 导出样式模块(创建和更新时调用) +var style = { + create: updateStyle, + update: updateStyle +}; + +// 匹配空白字符的正则 +var whitespaceRE = /\s+/; + +// 添加类名(兼容SVG元素) +function addClass (el, cls) { + if (!cls || !(cls = cls.trim())) { // 空值处理 + return + } + + if (el.classList) { // 支持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元素) +function removeClass (el, cls) { + if (!cls || !(cls = cls.trim())) { // 空值处理 + return + } + + if (el.classList) { // 支持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) { // 允许CSS过渡 + extend(res, autoCssTransition(def$$1.name || 'v')); // 合并自动生成的CSS类 + } + extend(res, def$$1); // 合并用户自定义配置 + return res + } else if (typeof def$$1 === 'string') { // 字符串类型配置 + return autoCssTransition(def$$1) // 生成对应CSS类配置 + } +} + +// 缓存自动生成的CSS过渡类配置 +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") // 离开激活类 + } +}); + +// 浏览器环境检测(排除IE9) +var hasTransition = inBrowser && !isIE9; +var TRANSITION = 'transition'; // CSS过渡类型 +var ANIMATION = 'animation'; // CSS动画类型 + +// 过渡属性/事件检测 +var transitionProp = 'transition'; // 过渡属性名 +var transitionEndEvent = 'transitionend'; // 过渡结束事件 +var animationProp = 'animation'; // 动画属性名 +var animationEndEvent = 'animationend'; // 动画结束事件 +if (hasTransition) { // 处理浏览器前缀 + /* istanbul ignore if */ + if (window.ontransitionend === undefined && // 检测Webkit前缀 + window.onwebkittransitionend !== undefined + ) { + transitionProp = 'WebkitTransition'; // Webkit过渡属性 + transitionEndEvent = 'webkitTransitionEnd'; // Webkit过渡结束事件 + } + if (window.onanimationend === undefined && // 检测Webkit动画 + window.onwebkitanimationend !== undefined + ) { + animationProp = 'WebkitAnimation'; // Webkit动画属性 + animationEndEvent = 'webkitAnimationEnd'; // Webkit动画结束事件 + } +} + +// 跨浏览器requestAnimationFrame实现 +var raf = inBrowser + ? window.requestAnimationFrame // 原生支持 + ? window.requestAnimationFrame.bind(window) + : setTimeout // 降级方案 + : /* istanbul ignore next */ function (fn) { return fn(); }; // 非浏览器环境直接执行 + +// 下一帧执行函数(双raf保证在样式变更后执行) +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); // 从元素移除类 +} + +// 等待过渡结束执行回调 +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); // 增加1ms防止边界情况 + el.addEventListener(event, onEnd); // 监听结束事件 +} + +// 匹配transform属性的正则 +var transformRE = /\b(transform|all)(,|$)/; + +// 获取元素过渡信息 +function getTransitionInfo (el, expectedType) { + var styles = window.getComputedStyle(el); // 获取计算样式 + 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 = // 检测是否包含transform属性 + 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]) + })) +} + +// 转换时间为毫秒(处理逗号小数格式) +function toMs (s) { + return Number(s.slice(0, -1).replace(',', '.')) * 1000 // 替换逗号为点 +} + +/* 进入过渡处理 */ + +function enter (vnode, toggleDisplay) { + var el = vnode.elm; // 获取DOM元素 + + // 立即调用旧的离开回调 + 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; + + // 获取上下文实例 + var context = activeInstance; + var transitionNode = activeInstance.$vnode; + while (transitionNode && transitionNode.parent) { // 查找最近的transition父组件 + context = transitionNode.context; + transitionNode = transitionNode.parent; + } + + var isAppear = !context._isMounted || !vnode.isRootInsert; // 判断是否初次渲染 + + if (isAppear && !appear && appear !== '') { // 初次渲染但未配置appear + return + } + + // 确定使用的类名 + var startClass = isAppear && appearClass + ? appearClass + : 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; + + // 处理显式指定的持续时间 + var explicitEnterDuration = toNumber( + isObject(duration) + ? duration.enter + : duration + ); + + if (explicitEnterDuration != null) { // 验证持续时间有效性 + checkDuration(explicitEnterDuration, 'enter', vnode); + } + + var expectsCSS = css !== false && !isIE9; // 是否期望CSS过渡 + var userWantsControl = getHookArgumentsLength(enterHook); // 用户是否控制过渡 + + var cb = el._enterCb = once(function () { // 单次执行回调 + if (expectsCSS) { // 清理CSS类 + removeTransitionClass(el, toClass); + removeTransitionClass(el, activeClass); + } + if (cb.cancelled) { // 过渡取消处理 + if (expectsCSS) { + removeTransitionClass(el, startClass); + } + enterCancelledHook && enterCancelledHook(el); + } else { // 正常结束处理 + afterEnterHook && afterEnterHook(el); + } + el._enterCb = null; // 清理回调引用 + }); + + if (!vnode.data.show) { // 非v-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 && beforeEnterHook(el); // 执行前置钩子 + if (expectsCSS) { // CSS过渡处理 + addTransitionClass(el, startClass); // 添加起始类 + addTransitionClass(el, activeClass); // 添加激活类 + nextFrame(function () { // 下一帧处理 + removeTransitionClass(el, startClass); // 移除起始类 + if (!cb.cancelled) { + addTransitionClass(el, toClass); // 添加目标类 + if (!userWantsControl) { // 用户未自定义控制 + if (isValidDuration(explicitEnterDuration)) { // 有效持续时间 + setTimeout(cb, explicitEnterDuration); // 定时器结束 + } else { + whenTransitionEnds(el, type, cb); // 监听过渡结束 + } + } + } + }); + } + + if (vnode.data.show) { // v-show控制的元素 + toggleDisplay && toggleDisplay(); // 切换显示状态 + enterHook && enterHook(el, cb); // 执行进入钩子 + } + + if (!expectsCSS && !userWantsControl) { // 无CSS过渡且用户未控制 + cb(); // 立即回调 + } +} +// 处理元素离开过渡 +function leave (vnode, rm) { + var el = vnode.elm; // 获取DOM元素 + + // 立即调用可能的进入回调 + if (isDef(el._enterCb)) { + el._enterCb.cancelled = true; // 标记进入回调已取消 + 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; // 是否使用CSS过渡 + var userWantsControl = getHookArgumentsLength(leave); // 用户是否控制离开过程 + + // 处理显式离开持续时间 + var explicitLeaveDuration = toNumber( + isObject(duration) + ? duration.leave + : duration + ); + + if (isDef(explicitLeaveDuration)) { // 验证持续时间有效性 + checkDuration(explicitLeaveDuration, 'leave', vnode); + } + + // 创建一次性离开回调 + var cb = el._leaveCb = once(function () { + if (el.parentNode && el.parentNode._pending) { // 清理父节点待处理记录 + el.parentNode._pending[vnode.key] = null; + } + if (expectsCSS) { // 清理CSS类 + removeTransitionClass(el, leaveToClass); + removeTransitionClass(el, leaveActiveClass); + } + if (cb.cancelled) { // 过渡取消处理 + if (expectsCSS) { + removeTransitionClass(el, leaveClass); + } + leaveCancelled && leaveCancelled(el); // 调用取消钩子 + } else { // 正常结束 + rm(); // 执行移除回调 + afterLeave && afterLeave(el); // 调用结束钩子 + } + el._leaveCb = null; // 清除回调引用 + }); + + if (delayLeave) { // 存在延迟离开函数 + delayLeave(performLeave); // 延迟执行离开 + } else { + performLeave(); // 立即执行离开 + } + + // 实际执行离开动画的函数 + function performLeave () { + if (cb.cancelled) { // 已取消则返回 + return + } + // 记录待移除元素 + if (!vnode.data.show && el.parentNode) { + (el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode; + } + beforeLeave && beforeLeave(el); // 执行前置钩子 + if (expectsCSS) { // CSS过渡处理 + 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) { // 无CSS且用户未控制 + cb(); // 直接执行回调 + } + } +} + +// 开发环境校验过渡持续时间有效性 +function checkDuration (val, name, vnode) { + if (typeof val !== 'number') { // 非数字类型警告 + warn( + " 显式 " + name + " 持续时间无效,应为数字 - 得到 " + (JSON.stringify(val)), + vnode.context + ); + } else if (isNaN(val)) { // NaN值警告 + warn( + " 显式 " + name + " 持续时间为 NaN,可能表达式错误", + vnode.context + ); + } +} + +// 验证是否为有效持续时间 +function isValidDuration (val) { + return typeof val === 'number' && !isNaN(val) // 数字且非NaN +} + +/** + * 标准化过渡钩子的参数长度(处理合并钩子/组件方法/普通函数) + */ +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 // 检测参数长度是否大于1 + } +} + +// 进入过渡的封装函数(用于create/activate生命周期) +function _enter (_, vnode) { + if (vnode.data.show !== true) { // 非v-show控制的元素 + enter(vnode); // 执行进入过渡 + } +} + +// 浏览器环境的过渡处理模块 +var transition = inBrowser ? { + create: _enter, // 元素创建时触发进入 + activate: _enter, // 组件激活时触发进入 + remove: function remove$$1 (vnode, rm) { // 元素移除时 + if (vnode.data.show !== true) { // 非v-show元素 + leave(vnode, rm); // 执行离开过渡 + } else { + rm(); // 直接移除 + } + } +} : {}; // 非浏览器环境空对象 + +// 平台相关模块列表(按顺序应用) +var platformModules = [ + attrs, // 属性处理模块 + klass, // class类名处理模块 + events, // 事件处理模块 + domProps, // DOM属性处理模块 + style, // 样式处理模块 + transition // 过渡处理模块 +]; + +// 合并基础模块和平台模块 +var modules = platformModules.concat(baseModules); + +// 创建虚拟DOM patch函数 +var patch = createPatchFunction({ + nodeOps: nodeOps, // DOM节点操作方法 + modules: modules // 使用的模块列表 +}); + +/* IE9兼容处理 */ +if (isIE9) { + // 监听selectionchange事件解决oninput不触发的问题 + document.addEventListener('selectionchange', function () { + var el = document.activeElement; // 当前焦点元素 + if (el && el.vmodel) { // 带有vmodel标记的元素 + trigger(el, 'input'); // 手动触发input事件 + } + }); +} + +// v-model指令实现 +var directive = { + inserted: function inserted (el, binding, vnode, oldVnode) { + if (vnode.tag === 'select') { // select元素处理 + if (oldVnode.elm && !oldVnode.elm._vOptions) { // 旧节点无选项缓存 + mergeVNodeHook(vnode, 'postpatch', function () { // 合并postpatch钩子 + 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) { // 非lazy模式 + el.addEventListener('compositionstart', onCompositionStart); // 输入法开始 + el.addEventListener('compositionend', onCompositionEnd); // 输入法结束 + el.addEventListener('change', onCompositionEnd); // 处理Safari兼容 + if (isIE9) el.vmodel = true; // IE9特殊标记 + } + } + }, + + componentUpdated: function componentUpdated (el, binding, vnode) { + if (vnode.tag === 'select') { // select元素更新后 + setSelected(el, binding, vnode.context); // 设置选中状态 + 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(hasNoMatchingOption) + : binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions); + if (needReset) trigger(el, 'change'); // 触发change事件 + } + } + } +}; + +// 设置select元素选中状态 +function setSelected (el, binding, vm) { + actuallySetSelected(el, binding, vm); // 实际设置 + /* IE/Edge兼容处理 */ + if (isIE || isEdge) setTimeout(() => actuallySetSelected(el, binding, vm), 0); +} + +// 实际设置选中逻辑 +function actuallySetSelected (el, binding, vm) { + var value = binding.value; // 当前绑定值 + var isMultiple = el.multiple; // 是否多选 + if (isMultiple && !Array.isArray(value)) { // 多选但值非数组 + warn( + `