diff --git a/vue1.js b/vue1.js index 34dacbf..77717be 100644 --- a/vue1.js +++ b/vue1.js @@ -1,2464 +1,2674 @@ -// Vue.js v2.6.12 版本声明 -// 版权所有 (c) 2014 - 2020 Evan You -// 基于 MIT 许可证发布 -// 立即执行函数表达式,用于将 Vue 库暴露给不同的模块系统 -// 参数 global 代表全局对象,factory 是一个函数,用于创建 Vue 实例 -(function (global, factory) { - // 判断是否为 CommonJS 模块环境 - // 如果 exports 是对象且 module 不是 undefined,则使用 module.exports 导出 factory 函数的返回值 - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - // 判断是否为 AMD 模块环境 - // 如果 define 是函数且支持 AMD 规范,则使用 define 函数定义模块 - typeof define === 'function' && define.amd ? define(factory) : - // 既不是 CommonJS 也不是 AMD 环境,则将 factory 函数的返回值赋值给全局对象的 Vue 属性 - (global = global || self, global.Vue = factory()); - // 传入当前全局对象 this 和 factory 函数 -}(this, function () { 'use strict'; - - // 创建一个冻结的空对象,用于避免不必要的对象创建和修改 - var emptyObject = Object.freeze({}); - - // 这些辅助函数由于其明确性和函数内联,在 JS 引擎中能产生更好的虚拟机代码 - // 检查一个值是否为 undefined 或 null - function isUndef (v) { - return v === undefined || v === null - } - - // 检查一个值是否既不是 undefined 也不是 null - function isDef (v) { - return v !== undefined && v !== null - } - - // 检查一个值是否严格等于 true - function isTrue (v) { - return v === true - } - - // 检查一个值是否严格等于 false - function isFalse (v) { - return v === false - } - - // 检查值是否为原始类型 - function isPrimitive (value) { - // 检查值是否为字符串类型 - return typeof value === 'string' || - // 检查值是否为数字类型 - typeof value === 'number' || - // 忽略 Flow 类型检查,检查值是否为符号类型 - typeof value === 'symbol' || - // 检查值是否为布尔类型 - typeof value === 'boolean' - } - - // 快速检查对象 - 当我们知道值是符合 JSON 类型时,主要用于区分对象和原始值 - function isObject (obj) { - // 检查对象是否不为 null 且类型为 object - return obj !== null && typeof obj === 'object' - } - - // 获取值的原始类型字符串,例如 [object Object] - var _toString = Object.prototype.toString; - - function toRawType (value) { - // 调用 Object.prototype.toString 方法获取值的类型字符串,并截取关键部分 - return _toString.call(value).slice(8, -1) - } - - // 严格的对象类型检查。仅当为纯 JavaScript 对象时返回 true - function isPlainObject (obj) { - // 检查对象的类型字符串是否为 [object Object] - return _toString.call(obj) === '[object Object]' - } - - // 检查一个值是否为正则表达式对象 - function isRegExp (v) { - return _toString.call(v) === '[object RegExp]' - } - - // 检查 val 是否为有效的数组索引 - function isValidArrayIndex (val) { - // 将值转换为浮点数 - var n = parseFloat(String(val)); - // 检查是否为非负整数且为有限值 - return n >= 0 && Math.floor(n) === n && isFinite(val) - } - - // 检查一个值是否为 Promise 对象 - function isPromise (val) { - // 检查值是否已定义 - return isDef(val) && - // 检查值是否有 then 方法 - typeof val.then === 'function' && - // 检查值是否有 catch 方法 - typeof val.catch === 'function' - } - - // 将值转换为实际渲染的字符串 - function toString (val) { - // 如果值为 null 或 undefined,返回空字符串 - return val == null ? '' : - // 检查值是否为数组或纯对象且 toString 方法为默认方法 - Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) ? - // 如果是,使用 JSON.stringify 转换为格式化的字符串 - JSON.stringify(val, null, 2) : - // 否则,直接转换为字符串 - String(val) - } - - // 将输入值转换为数字以便持久化。如果转换失败,返回原始字符串 - function toNumber (val) { - // 将值转换为浮点数 - var n = parseFloat(val); - // 检查是否为 NaN,如果是则返回原始值,否则返回转换后的数字 - return isNaN(n) ? val : n - } - - // 创建一个映射并返回一个函数,用于检查键是否在该映射中 - function makeMap ( - str, - expectsLowerCase - ) { - // 创建一个空对象作为映射 - var map = Object.create(null); - // 将字符串按逗号分割成数组 - var list = str.split(','); - // 遍历数组,将每个元素作为键添加到映射中 - for (var i = 0; i < list.length; i++) { - map[list[i]] = true; - } - // 如果期望小写,则返回一个函数,将输入转换为小写后检查是否在映射中 - return expectsLowerCase ? function (val) { return map[val.toLowerCase()]; } : - // 否则,直接检查输入是否在映射中 - function (val) { return map[val]; } - } - - // 检查标签是否为内置标签 - var isBuiltInTag = makeMap('slot,component', true); - - // 检查属性是否为保留属性 - var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is'); - - // 从数组中移除一个元素 - function remove (arr, item) { - // 检查数组长度是否大于 0 - if (arr.length) { - // 查找元素在数组中的索引 - var index = arr.indexOf(item); - // 如果索引大于 -1,表示元素存在于数组中 - if (index > -1) { - // 使用 splice 方法移除元素并返回被移除的元素数组 - return arr.splice(index, 1) - } - } - } - - // 检查对象是否具有某个属性 - var hasOwnProperty = Object.prototype.hasOwnProperty; - function hasOwn (obj, key) { - // 使用 hasOwnProperty 方法检查对象是否具有指定的键 - return hasOwnProperty.call(obj, key) - } - - // 创建一个纯函数的缓存版本 - function cached (fn) { - // 创建一个空对象作为缓存 - var cache = Object.create(null); - return function cachedFn (str) { - // 检查缓存中是否已有该键的结果 - var hit = cache[str]; - // 如果有,返回缓存结果;否则,调用原函数计算结果并缓存 - return hit || (cache[str] = fn(str)) - } - } - - // 将连字符分隔的字符串转换为驼峰式字符串 - var camelizeRE = /-(\w)/g; - var camelize = cached(function (str) { - // 使用正则表达式替换连字符后的字符为大写 - return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) - }); - - // 首字母大写字符串 - var capitalize = cached(function (str) { - // 将字符串的首字母转换为大写并拼接剩余部分 - return str.charAt(0).toUpperCase() + str.slice(1) - }); - - // 将驼峰式字符串转换为连字符分隔的字符串 - var hyphenateRE = /\B([A-Z])/g; - var hyphenate = cached(function (str) { - // 使用正则表达式替换大写字母前插入连字符并转换为小写 - return str.replace(hyphenateRE, '-$1').toLowerCase() - }); - - // 为不支持 bind 方法的环境提供简单的 bind 方法填充 - // 例如 PhantomJS 1.x。从技术上讲,由于现在大多数浏览器中本地 bind 方法性能已经足够好,我们不再需要这个方法 - // 但移除它会导致在 PhantomJS 1.x 中运行的代码中断,因此为了向后兼容必须保留 - function polyfillBind (fn, ctx) { - function boundFn (a) { - // 获取参数长度 - var l = arguments.length; - return l ? - // 如果有参数 - l > 1 ? - // 如果参数数量大于 1,使用 apply 方法调用原函数 - fn.apply(ctx, arguments) : - // 如果参数数量为 1,使用 call 方法调用原函数 - fn.call(ctx, a) : - // 如果没有参数,使用 call 方法调用原函数 - fn.call(ctx) - } - - // 绑定函数的长度设置为原函数的长度 - boundFn._length = fn.length; - return boundFn - } - - // 使用原生的 bind 方法 - function nativeBind (fn, ctx) { - return fn.bind(ctx) - } - - // 如果 Function.prototype.bind 存在,则使用原生 bind 方法,否则使用填充方法 - var bind = Function.prototype.bind ? nativeBind : polyfillBind; - - // 将类似数组的对象转换为真正的数组 - function toArray (list, start) { - // 如果 start 未提供,默认为 0 - start = start || 0; - // 计算新数组的长度 - var i = list.length - start; - // 创建一个指定长度的新数组 - var ret = new Array(i); - // 从后往前遍历类似数组的对象,将元素复制到新数组中 - while (i--) { - ret[i] = list[i + start]; - } - return ret - } - - // 将属性混合到目标对象中 - function extend (to, _from) { - // 遍历源对象的所有属性 - for (var key in _from) { - // 将源对象的属性复制到目标对象中 - to[key] = _from[key]; - } - return to - } - - // 将对象数组合并为一个对象 - function toObject (arr) { - // 创建一个空对象作为结果 - var res = {}; - // 遍历数组中的每个对象 - for (var i = 0; i < arr.length; i++) { - // 如果对象存在 - if (arr[i]) { - // 将对象的属性合并到结果对象中 - extend(res, arr[i]); - } - } - return res - } - - // 不执行任何操作。 - // 为了让 Flow 满意而保留参数,同时避免留下无用的转译代码 - // (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/) - function noop (a, b, c) {} - - // 始终返回 false - var no = function (a, b, c) { return false; }; - - // 返回相同的值 - var identity = function (_) { return _; }; - - // 从编译器模块生成包含静态键的字符串 - function genStaticKeys (modules) { - // 使用 reduce 方法将所有模块的静态键合并为一个数组 - return modules.reduce(function (keys, m) { - return keys.concat(m.staticKeys || []) - }, []).join(',') - } - - // 检查两个值是否宽松相等 - 即,如果它们是纯对象,它们的结构是否相同? - function looseEqual (a, b) { - // 如果两个值严格相等,直接返回 true - if (a === b) { return true } - // 检查 a 是否为对象 - var isObjectA = isObject(a); - // 检查 b 是否为对象 - var isObjectB = isObject(b); - if (isObjectA && isObjectB) { - try { - // 检查 a 是否为数组 - var isArrayA = Array.isArray(a); - // 检查 b 是否为数组 - var isArrayB = Array.isArray(b); - if (isArrayA && isArrayB) { - // 如果都是数组,检查长度是否相等且每个元素是否宽松相等 - return a.length === b.length && a.every(function (e, i) { - return looseEqual(e, b[i]) - }) - } else if (a instanceof Date && b instanceof Date) { - // 如果都是日期对象,检查时间戳是否相等 - return a.getTime() === b.getTime() - } else if (!isArrayA && !isArrayB) { - // 如果都不是数组,检查键的数量是否相等且每个键对应的值是否宽松相等 - var keysA = Object.keys(a); - var keysB = Object.keys(b); - return keysA.length === keysB.length && keysA.every(function (key) { - return looseEqual(a[key], b[key]) - }) - } else { - return false - } - } catch (e) { - return false - } - } else if (!isObjectA && !isObjectB) { - // 如果都不是对象,将它们转换为字符串并比较 - return String(a) === String(b) - } else { - return false - } - } - - // 返回在数组中可以找到宽松相等值的第一个索引(如果值是纯对象,数组必须包含相同结构的对象),如果不存在则返回 -1 - function looseIndexOf (arr, val) { - // 遍历数组 - for (var i = 0; i < arr.length; i++) { - // 检查当前元素是否与目标值宽松相等 - if (looseEqual(arr[i], val)) { return i } - } - return -1 - } - - // 确保函数只被调用一次 - function once (fn) { - // 标记函数是否已被调用 - var called = false; - return function () { - // 如果函数未被调用 - if (!called) { - // 标记为已调用 - called = true; - // 调用原函数并传递参数 - fn.apply(this, arguments); - } - } - } - - // 服务器渲染标记属性 - var SSR_ATTR = 'data-server-rendered'; - - // 资产类型数组,包括组件、指令和过滤器 - var ASSET_TYPES = [ - 'component', - 'directive', - 'filter' - ]; - - // 生命周期钩子数组 - var LIFECYCLE_HOOKS = [ - 'beforeCreate', - 'created', - 'beforeMount', - 'mounted', - 'beforeUpdate', - 'updated', - 'beforeDestroy', - 'destroyed', - 'activated', - 'deactivated', - 'errorCaptured', - 'serverPrefetch' - ]; - - // Vue 配置对象 - var config = ({ - // 选项合并策略(在 core/util/options 中使用) - // 忽略 Flow 类型检查 - optionMergeStrategies: Object.create(null), - // 是否抑制警告 - silent: false, - // 在启动时显示生产模式提示消息? - productionTip: "development" !== 'production', - // 是否启用开发者工具 - devtools: "development" !== 'production', - // 是否记录性能 - performance: false, - // 监听器错误的错误处理函数 - errorHandler: null, - // 监听器警告的警告处理函数 - warnHandler: null, - // 忽略某些自定义元素 - ignoredElements: [], - // v-on 的自定义用户键别名 - // 忽略 Flow 类型检查 - keyCodes: Object.create(null), - // 检查标签是否为保留标签,以便不能将其注册为组件。这依赖于平台,可能会被覆盖 - isReservedTag: no, - // 检查属性是否为保留属性,以便不能将其用作组件的 prop。这依赖于平台,可能会被覆盖 - isReservedAttr: no, - // 检查标签是否为未知元素。依赖于平台 - isUnknownElement: no, - // 获取元素的命名空间 - getTagNamespace: noop, - // 解析特定平台的真实标签名 - parsePlatformTagName: identity, - // 检查属性是否必须使用属性绑定,例如 value。依赖于平台 - mustUseProp: no, - // 异步执行更新。供 Vue Test Utils 使用。如果设置为 false,性能将显著降低 - async: true, - // 出于遗留原因暴露 - _lifecycleHooks: LIFECYCLE_HOOKS - }); - - // 用于解析 HTML 标签、组件名称和属性路径的 Unicode 字母。 - // 使用 https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname - // 跳过 \u10000 - \uEFFFF 以避免 PhantomJS 冻结 - var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; - - // 检查字符串是否以 $ 或 _ 开头 - function isReserved (str) { - // 获取字符串的第一个字符的 Unicode 编码 - var c = (str + '').charCodeAt(0); - // 检查编码是否为 $ 或 _ 的编码 - return c === 0x24 || c === 0x5F - } - - // 定义一个属性 - function def (obj, key, val, enumerable) { - // 使用 Object.defineProperty 方法定义属性 - Object.defineProperty(obj, key, { - // 属性的值 - value: val, - // 是否可枚举 - enumerable: !!enumerable, - // 是否可写 - writable: true, - // 是否可配置 - configurable: true - }); - } - - // 解析简单路径 - var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]")); - function parsePath (path) { - // 检查路径是否包含非法字符 - if (bailRE.test(path)) { - return - } - // 将路径按点分割成段 - var segments = path.split('.'); - return function (obj) { - // 遍历路径段 - for (var i = 0; i < segments.length; i++) { - // 如果对象为空,返回 - if (!obj) { return } - // 获取对象的下一级属性 - obj = obj[segments[i]]; - } - return obj - } - } - // 能否使用 __proto__ 属性? - var hasProto = '__proto__' in {}; - -// 浏览器环境嗅探 -// 判断是否处于浏览器环境 - var inBrowser = typeof window !== 'undefined'; -// 判断是否处于 Weex 环境 - var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; -// 获取 Weex 平台名称并转换为小写 - var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase(); -// 获取浏览器的用户代理字符串并转换为小写 - var UA = inBrowser && window.navigator.userAgent.toLowerCase(); -// 判断是否为 IE 浏览器 - var isIE = UA && /msie|trident/.test(UA); -// 判断是否为 IE 9 浏览器 - var isIE9 = UA && UA.indexOf('msie 9.0') > 0; -// 判断是否为 Edge 浏览器 - var isEdge = UA && UA.indexOf('edge/') > 0; -// 判断是否为安卓设备 - var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); -// 判断是否为 iOS 设备 - var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); -// 判断是否为 Chrome 浏览器 - var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; -// 判断是否为 PhantomJS 环境 - var isPhantomJS = UA && /phantomjs/.test(UA); -// 判断是否为 Firefox 浏览器,并获取版本号 - var isFF = UA && UA.match(/firefox\/(\d+)/); - -// Firefox 在 Object.prototype 上有 "watch" 函数 - var nativeWatch = ({}).watch; - -// 检测浏览器是否支持 passive 选项 - var supportsPassive = false; - if (inBrowser) { - try { - // 创建一个空对象用于测试 - var opts = {}; - // 定义一个属性 "passive",当访问该属性时将 supportsPassive 设为 true - Object.defineProperty(opts, 'passive', ({ - get: function get () { - supportsPassive = true; - } - })); - // 尝试添加一个测试事件监听器,触发对 "passive" 属性的访问 - window.addEventListener('test-passive', null, opts); - } catch (e) {} - } - -// 这个变量需要延迟求值,因为 vue 可能在 vue-server-renderer 设置 VUE_ENV 之前被引入 - var _isServer; -// 检查是否处于服务器渲染环境 - var isServerRendering = function () { - if (_isServer === undefined) { - if (!inBrowser && !inWeex && typeof global !== 'undefined') { - // 检测是否存在 vue-server-renderer 并避免 Webpack 对 process 进行填充 - _isServer = global['process'] && global['process'].env.VUE_ENV === 'server'; - } else { - _isServer = false; - } - } - return _isServer - }; - -// 检测开发者工具是否可用 - var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; - -// 判断一个构造函数是否为原生函数 - function isNative (Ctor) { - return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) - } - -// 检测是否支持 Symbol 和 Reflect.ownKeys - var hasSymbol = - typeof Symbol !== 'undefined' && isNative(Symbol) && - typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); - -// 定义一个 Set 类型变量 - var _Set; -// 如果原生支持 Set,则使用原生 Set - if (typeof Set !== 'undefined' && isNative(Set)) { - _Set = Set; - } else { - // 否则使用自定义的 Set 填充 - _Set = (function () { - function Set () { - // 创建一个空对象用于存储键 - this.set = Object.create(null); - } - // 检查集合中是否存在某个键 - Set.prototype.has = function has (key) { - return this.set[key] === true - }; - // 向集合中添加一个键 - Set.prototype.add = function add (key) { - this.set[key] = true; - }; - // 清空集合 - Set.prototype.clear = function clear () { - this.set = Object.create(null); - }; - - return Set; - }()); - } - -// 定义警告函数,初始为空函数 - var warn = noop; -// 定义提示函数,初始为空函数 - var tip = noop; -// 定义生成组件调用栈的函数,初始为空函数 - var generateComponentTrace = (noop); -// 定义格式化组件名称的函数,初始为空函数 - var formatComponentName = (noop); - - { - // 检查是否存在 console 对象 - var hasConsole = typeof console !== 'undefined'; - // 用于匹配连字符或下划线后的字符 - var classifyRE = /(?:^|[-_])(\w)/g; - // 将字符串转换为驼峰式命名 - var classify = function (str) { return str - .replace(classifyRE, function (c) { return c.toUpperCase(); }) - .replace(/[-_]/g, ''); }; - - // 定义警告函数 - warn = function (msg, vm) { - // 获取组件调用栈 - var trace = vm ? generateComponentTrace(vm) : ''; - - if (config.warnHandler) { - // 如果配置了警告处理函数,则调用该函数 - config.warnHandler.call(null, msg, vm, trace); - } else if (hasConsole && (!config.silent)) { - // 否则在控制台输出警告信息 - console.error(("[Vue warn]: " + msg + trace)); - } - }; - - // 定义提示函数 - tip = function (msg, vm) { - if (hasConsole && (!config.silent)) { - // 在控制台输出提示信息 - console.warn("[Vue tip]: " + msg + ( - vm ? generateComponentTrace(vm) : '' - )); - } - }; - - // 格式化组件名称 - formatComponentName = function (vm, includeFile) { - // 检查当前组件实例是否为根组件实例 - if (vm.$root === vm) { - // 如果是根组件实例,返回 '' 字符串 - return '' - } - // 根据传入的 vm 类型和属性获取组件的选项对象 - var options = typeof vm === 'function' && vm.cid != null - // 如果 vm 是函数且有 cid 属性,说明是组件构造函数,取其 options 属性 - ? vm.options - // 如果 vm 是 Vue 实例,取其 $options 属性,如果没有则取构造函数的 options 属性 - : vm._isVue - ? vm.$options || vm.constructor.options - // 否则直接将 vm 作为选项对象 - : vm; - // 从选项对象中获取组件的名称,如果没有则取 _componentTag 属性 - var name = options.name || options._componentTag; - // 从选项对象中获取组件对应的文件路径 - var file = options.__file; - // 如果组件名称为空且存在文件路径 - if (!name && file) { - // 使用正则表达式匹配文件路径中的文件名部分 - var match = file.match(/([^/\\]+)\.vue$/); - // 如果匹配成功,将匹配到的文件名赋值给 name - name = match && match[1]; - } - - // 生成格式化后的组件名称字符串 - return ( - // 如果有组件名称,将其转换为驼峰命名并包裹在 < > 中,否则返回 '' - (name ? ("<" + (classify(name)) + ">") : "") + - // 如果存在文件路径且 includeFile 不为 false,添加文件路径信息 - (file && includeFile !== false ? (" at " + file) : '') - ) - }; - -// 重复字符串指定次数 - var repeat = function (str, n) { - // 初始化结果字符串为空 - var res = ''; - // 开始循环,直到 n 变为 0 - while (n) { - // 如果 n 是奇数,将当前字符串 str 追加到结果字符串 res 中 - if (n % 2 === 1) { res += str; } - // 如果 n 大于 1,将字符串 str 自身拼接,相当于将字符串长度翻倍 - if (n > 1) { str += str; } - // 将 n 右移一位,相当于将 n 除以 2 并向下取整 - n >>= 1; - } - // 返回重复后的字符串 - return res - }; - // 生成组件调用栈信息 - // 重新定义 generateComponentTrace 函数,用于生成组件调用链的追踪信息 - generateComponentTrace = function (vm) { - // 检查传入的 vm 是否为 Vue 实例,并且是否存在父组件 - if (vm._isVue && vm.$parent) { - // 初始化一个数组 tree,用于存储组件调用链中的每个组件实例 - var tree = []; - // 初始化一个变量 currentRecursiveSequence,用于记录当前组件的递归调用次数 - var currentRecursiveSequence = 0; - // 开始循环遍历组件的父组件链,直到没有父组件为止 - while (vm) { - // 检查 tree 数组中是否已经有组件实例 - if (tree.length > 0) { - // 获取 tree 数组中的最后一个组件实例 - var last = tree[tree.length - 1]; - // 检查最后一个组件实例的构造函数是否和当前组件实例的构造函数相同 - if (last.constructor === vm.constructor) { - // 如果相同,说明是递归调用,递归次数加 1 - currentRecursiveSequence++; - // 将 vm 指向其父组件,继续循环 - vm = vm.$parent; - // 跳过本次循环的后续代码,继续下一次循环 - continue - } - // 如果递归次数大于 0,说明之前有递归调用 - else if (currentRecursiveSequence > 0) { - // 将 tree 数组的最后一个元素替换为一个数组,包含最后一个组件实例和递归次数 - tree[tree.length - 1] = [last, currentRecursiveSequence]; - // 重置递归次数为 0 - currentRecursiveSequence = 0; - } - } - // 将当前组件实例添加到 tree 数组中 - tree.push(vm); - // 将 vm 指向其父组件,继续循环 - vm = vm.$parent; - } - // 生成组件调用链的追踪信息字符串 - return '\n\nfound in\n\n' + tree - // 对 tree 数组中的每个元素进行映射处理 - .map(function (vm, i) { - // 根据索引 i 判断是否为第一个元素 - return ("" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + - // 检查当前元素是否为数组 - (Array.isArray(vm) - // 如果是数组,说明有递归调用,格式化输出组件名和递归次数 - ? ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)") - // 如果不是数组,直接格式化输出组件名 - : formatComponentName(vm))); - }) - // 将映射后的数组元素用换行符连接成一个字符串 - .join('\n') - } - // 如果传入的 vm 不是 Vue 实例或者没有父组件 - else { - // 直接生成包含组件名的追踪信息字符串 - return ("\n\n(found in " + (formatComponentName(vm)) + ")") - } - }; - -// 定义唯一标识符 - var uid = 0; - -// Dep 类,用于实现依赖收集和发布更新 - var Dep = function Dep () { - // 为每个 Dep 实例分配一个唯一的 ID - this.id = uid++; - // 存储订阅者的数组 - this.subs = []; - }; - -// 向订阅者列表中添加一个订阅者 - Dep.prototype.addSub = function addSub (sub) { - this.subs.push(sub); - }; - -// 从订阅者列表中移除一个订阅者 - Dep.prototype.removeSub = function removeSub (sub) { - remove(this.subs, sub); - }; - -// 触发依赖收集 - Dep.prototype.depend = function depend () { - if (Dep.target) { - Dep.target.addDep(this); - } - }; - -// 通知所有订阅者更新 - Dep.prototype.notify = function notify () { - // 先复制一份订阅者列表,避免在更新过程中修改原列表 - var subs = this.subs.slice(); - if (!config.async) { - // 如果不是异步模式,对订阅者列表进行排序 - subs.sort(function (a, b) { return a.id - b.id; }); - } - for (var i = 0, l = subs.length; i < l; i++) { - // 调用每个订阅者的 update 方法 - subs[i].update(); - } - }; - -// 当前正在计算的目标观察者 - Dep.target = null; -// 存储目标观察者的栈 - var targetStack = []; - -// 将目标观察者压入栈中,并设置为当前目标 - function pushTarget (target) { - targetStack.push(target); - Dep.target = target; - } - -// 从栈中弹出目标观察者,并更新当前目标 - function popTarget () { - targetStack.pop(); - Dep.target = targetStack[targetStack.length - 1]; - } - -// VNode 类,用于表示虚拟 DOM 节点 - // 定义 VNode 构造函数,用于创建虚拟 DOM 节点对象 - var VNode = function VNode ( - // 节点的标签名,例如 'div'、'span' 等 - tag, - // 节点的数据对象,包含节点的属性、事件等信息 - data, - // 节点的子节点数组 - children, - // 节点的文本内容 - text, - // 与虚拟节点对应的真实 DOM 元素 - elm, - // 节点所在的上下文,通常是 Vue 实例 - context, - // 组件的选项对象,包含组件的配置信息 - componentOptions, - // 异步组件的工厂函数 - asyncFactory - ) { - // 将传入的标签名赋值给实例的 tag 属性 - this.tag = tag; - // 将传入的数据对象赋值给实例的 data 属性 - this.data = data; - // 将传入的子节点数组赋值给实例的 children 属性 - this.children = children; - // 将传入的文本内容赋值给实例的 text 属性 - this.text = text; - // 将传入的真实 DOM 元素赋值给实例的 elm 属性 - this.elm = elm; - // 命名空间,初始化为 undefined - this.ns = undefined; - // 将传入的上下文赋值给实例的 context 属性 - this.context = context; - // 函数式组件的上下文,初始化为 undefined - this.fnContext = undefined; - // 函数式组件的选项,初始化为 undefined - this.fnOptions = undefined; - // 函数式组件的作用域 ID,初始化为 undefined - this.fnScopeId = undefined; - // 如果 data 存在且包含 key 属性,则将其赋值给实例的 key 属性,否则为 undefined - this.key = data && data.key; - // 将传入的组件选项对象赋值给实例的 componentOptions 属性 - this.componentOptions = componentOptions; - // 组件实例,初始化为 undefined - this.componentInstance = undefined; - // 父节点,初始化为 undefined - this.parent = undefined; - // 标记节点是否为原始的、未处理的节点,初始化为 false - this.raw = false; - // 标记节点是否为静态节点,初始化为 false - this.isStatic = false; - // 标记节点是否是根插入节点,初始化为 true - this.isRootInsert = true; - // 标记节点是否为注释节点,初始化为 false - this.isComment = false; - // 标记节点是否为克隆节点,初始化为 false - this.isCloned = false; - // 标记节点是否只渲染一次,初始化为 false - this.isOnce = false; - // 将传入的异步组件工厂函数赋值给实例的 asyncFactory 属性 - this.asyncFactory = asyncFactory; - // 异步组件的元数据,初始化为 undefined - this.asyncMeta = undefined; - // 标记节点是否为异步占位符节点,初始化为 false - this.isAsyncPlaceholder = false; - }; - -// 定义 VNode 原型上的访问器属性 - var prototypeAccessors = { child: { configurable: true } }; - -// 废弃:为了向后兼容,将 child 作为 componentInstance 的别名 - prototypeAccessors.child.get = function () { - return this.componentInstance - }; - -// 将访问器属性定义到 VNode 原型上 - Object.defineProperties( VNode.prototype, prototypeAccessors ); - -// 创建一个空的虚拟节点 - var createEmptyVNode = function (text) { - if ( text === void 0 ) text = ''; - - var node = new VNode(); - node.text = text; - node.isComment = true; - return node - }; - -// 创建一个文本虚拟节点 - function createTextVNode (val) { - return new VNode(undefined, undefined, undefined, String(val)) - } - -// 浅克隆一个虚拟节点 - // 定义一个函数 cloneVNode,用于克隆一个虚拟节点(VNode) - function cloneVNode (vnode) { - // 创建一个新的 VNode 实例,将原 VNode 的部分属性传递给新实例 - var cloned = new VNode( - // 克隆原 VNode 的标签名 - vnode.tag, - // 克隆原 VNode 的数据对象 - vnode.data, - // 克隆原 VNode 的子节点数组,如果存在子节点,则创建一个新的数组副本 - vnode.children && vnode.children.slice(), - // 克隆原 VNode 的文本内容 - vnode.text, - // 克隆原 VNode 对应的真实 DOM 元素 - vnode.elm, - // 克隆原 VNode 的上下文 - vnode.context, - // 克隆原 VNode 的组件选项对象 - vnode.componentOptions, - // 克隆原 VNode 的异步组件工厂函数 - vnode.asyncFactory - ); - // 将原 VNode 的命名空间赋值给克隆节点 - cloned.ns = vnode.ns; - // 将原 VNode 是否为静态节点的标记赋值给克隆节点 - cloned.isStatic = vnode.isStatic; - // 将原 VNode 的 key 值赋值给克隆节点 - cloned.key = vnode.key; - // 将原 VNode 是否为注释节点的标记赋值给克隆节点 - cloned.isComment = vnode.isComment; - // 将原 VNode 的函数式组件上下文赋值给克隆节点 - cloned.fnContext = vnode.fnContext; - // 将原 VNode 的函数式组件选项赋值给克隆节点 - cloned.fnOptions = vnode.fnOptions; - // 将原 VNode 的函数式组件作用域 ID 赋值给克隆节点 - cloned.fnScopeId = vnode.fnScopeId; - // 将原 VNode 的异步组件元数据赋值给克隆节点 - cloned.asyncMeta = vnode.asyncMeta; - // 标记克隆节点为已克隆状态 - cloned.isCloned = true; - // 返回克隆后的 VNode 节点 - return cloned - } - -// 获取数组的原型 - var arrayProto = Array.prototype; -// 创建一个继承自数组原型的对象 - var arrayMethods = Object.create(arrayProto); - -// 定义需要拦截的数组方法 - var methodsToPatch = [ - 'push', - 'pop', - 'shift', - 'unshift', - 'splice', - 'sort', - 'reverse' - ]; - -// 拦截数组的变异方法并触发更新 - methodsToPatch.forEach(function (method) { - // 缓存原始方法 - var original = arrayProto[method]; - // 定义变异方法 - // 调用 def 函数,为 arrayMethods 对象定义一个新的方法,该方法名由 method 变量指定 -// 第三个参数是一个名为 mutator 的函数,它会覆盖原数组方法的行为 - def(arrayMethods, method, function mutator () { - // 初始化一个空数组 args,用于存储传递给当前函数的参数 - var args = [], len = arguments.length; - // 使用 while 循环将传递给函数的参数逆序存储到 args 数组中 - while ( len-- ) args[ len ] = arguments[ len ]; - - // 调用原始的数组方法(original 是之前缓存的数组原型上的原始方法) - // 使用 apply 方法将当前数组实例作为 this 上下文,并传递参数 args - var result = original.apply(this, args); - // 获取当前数组实例的 Observer 实例 - // __ob__ 是在对象被观测时添加的一个属性,指向该对象的 Observer 实例 - var ob = this.__ob__; - // 定义一个变量 inserted,用于存储新插入到数组中的元素 - var inserted; - // 根据不同的数组方法进行不同的处理 - switch (method) { - // 如果是 push 或 unshift 方法 - case 'push': - case 'unshift': - // 将传递给 push 或 unshift 方法的所有参数赋值给 inserted 变量 - // 因为 push 和 unshift 方法会将参数添加到数组中,这些参数就是新插入的元素 - inserted = args; - // 跳出 switch 语句 - break - // 如果是 splice 方法 - case 'splice': - // splice 方法的参数格式为 (start, deleteCount, item1, item2, ...) - // 从索引 2 开始截取参数,这些参数就是 splice 方法插入到数组中的新元素 - inserted = args.slice(2); - // 跳出 switch 语句 - break - } - if (inserted) { ob.observeArray(inserted); } - // 通知依赖更新 - ob.dep.notify(); - return result - }); - }); - -// 获取拦截后的数组方法的属性名 - var arrayKeys = Object.getOwnPropertyNames(arrayMethods); - -// 是否应该进行观测 - var shouldObserve = true; - -// 切换观测状态 - function toggleObserving (value) { - shouldObserve = value; - } - -// Observer 类,用于将对象转换为可观测对象 - var booleanIndex = getTypeIndex(Boolean, prop.type); - // 如果属性类型包含布尔类型 - if (booleanIndex > -1) { - // 如果属性不存在且没有默认值,则将值设为false - if (absent &&!hasOwn(prop, 'default')) { - value = false; - } else if (value === '' || value === hyphenate(key)) { - // 只有当布尔类型的优先级高于字符串类型时,才将空字符串或同名的值转换为布尔类型 - var Observer = function Observer (value) { - this.value = value; - this.dep = new Dep(); - this.vmCount = 0; - // 在对象上定义一个不可枚举的属性 __ob__,指向当前 Observer 实例 - def(value, '__ob__', this); - if (Array.isArray(value)) { - if (hasProto) { - // 使用 __proto__ 进行原型增强 - protoAugment(value, arrayMethods); - } else { - // 复制属性进行增强 - copyAugment(value, arrayMethods, arrayKeys); - } - // 对数组元素进行观测 - this.observeArray(value); - } else { - // 遍历对象属性并转换为响应式 - this.walk(value); - } - }; - -// 遍历对象的所有属性,将其转换为 getter/setter - Observer.prototype.walk = function walk (obj) { - var keys = Object.keys(obj); - for (var i = 0; i < keys.length; i++) { - defineReactive$$1(obj, keys[i]); - } - }; - -// 对数组中的每个元素进行观测 - Observer.prototype.observeArray = function observeArray (items) { - for (var i = 0, l = items.length; i < l; i++) { - observe(items[i]); - } - }; - -// 使用 __proto__ 增强目标对象或数组 - function protoAugment (target, src) { - target.__proto__ = src; - } - -// 通过定义隐藏属性增强目标对象或数组 - function copyAugment (target, src, keys) { - for (var i = 0, l = keys.length; i < l; i++) { - var key = keys[i]; - def(target, key, src[key]); - } - } - -// 尝试为一个值创建观察者实例 - function observe (value, asRootData) { - // 检查传入的值是否不是对象,或者是否为 VNode 实例 - // 如果满足条件,则直接返回,不进行观察 - if (!isObject(value) || value instanceof VNode) { - return - } - // 声明一个变量 ob,用于存储观察者实例 - var ob; - // 检查传入的值是否已经有 __ob__ 属性,并且该属性对应的是 Observer 实例 - if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { - // 如果已经有观察者实例,则直接将其赋值给 ob 变量 - ob = value.__ob__; - } - // 如果值还没有被观察,并且满足以下条件 - else if ( - // 全局变量 shouldObserve 为 true,表示应该进行观察 - shouldObserve && - // 当前不是服务器渲染环境 - !isServerRendering() && - // 传入的值是数组或者是普通对象 - (Array.isArray(value) || isPlainObject(value)) && - // 传入的值是可扩展的,即可以添加新的属性 - Object.isExtensible(value) && - // 传入的值不是 Vue 实例 - !value._isVue - ) { - // 创建一个新的 Observer 实例,并将其赋值给 ob 变量 - ob = new Observer(value); - } - // 如果 asRootData 为 true,并且已经成功创建了观察者实例 - if (asRootData && ob) { - // 增加观察者实例的 vmCount 属性,该属性用于记录该对象作为根数据的次数 - ob.vmCount++; - } - // 返回观察者实例,如果没有创建则返回 undefined - return ob - } - // Define a reactive property on an Object. -// 在一个对象上定义一个响应式属性 - function defineReactive$$1 ( - // 要定义响应式属性的对象 - obj, - // 属性名 - key, - // 属性的初始值 - val, - // 自定义的 setter 函数 - customSetter, - // 是否进行浅观察 - shallow - ) { - // 创建一个 Dep 实例,用于依赖收集和派发更新 - var dep = new Dep(); - - // 获取对象上该属性的描述符 - var property = Object.getOwnPropertyDescriptor(obj, key); - // 如果属性存在且不可配置,则直接返回 - if (property && property.configurable === false) { - return - } - - // cater for pre-defined getter/setters - // 处理预定义的 getter/setter - // 获取属性的 getter 函数 - var getter = property && property.get; - // 获取属性的 setter 函数 - var setter = property && property.set; - // 如果没有 getter 或者有 setter,并且只传入了两个参数,则获取对象上该属性的值 - if ((!getter || setter) && arguments.length === 2) { - val = obj[key]; - } - - // 如果不是浅观察,则尝试为属性值创建观察者实例 - var childOb = !shallow && observe(val); - // 使用 Object.defineProperty 定义属性的 getter 和 setter - Object.defineProperty(obj, key, { - // 属性可枚举 - enumerable: true, - // 属性可配置 - configurable: true, - // 定义 getter 函数 - get: function reactiveGetter () { - // 如果有预定义的 getter,则调用该 getter 获取值,否则使用传入的初始值 - var value = getter ? getter.call(obj) : val; - // 如果存在 Dep.target(即有正在计算的依赖) - if (Dep.target) { - // 收集依赖 - dep.depend(); - // 如果存在子观察者 - if (childOb) { - // 子观察者收集依赖 - childOb.dep.depend(); - // 如果值是数组 - if (Array.isArray(value)) { - // 对数组中的每个元素收集依赖 - dependArray(value); - } - } - } - // 返回属性值 - return value - }, - // 定义 setter 函数 - set: function reactiveSetter (newVal) { - // 如果有预定义的 getter,则调用该 getter 获取旧值,否则使用传入的初始值 - var value = getter ? getter.call(obj) : val; - // 检查新值和旧值是否相等,如果相等则直接返回 - /* eslint-disable no-self-compare */ - if (newVal === value || (newVal !== newVal && value !== value)) { - return - } - /* eslint-enable no-self-compare */ - // 如果有自定义的 setter 函数,则调用该函数 - if (customSetter) { - customSetter(); - } - // 如果有 getter 但没有 setter,则直接返回 - // #7981: for accessor properties without setter - if (getter && !setter) { return } - // 如果有预定义的 setter,则调用该 setter 设置新值 - if (setter) { - setter.call(obj, newVal); - } else { - // 否则直接更新值 - val = newVal; - } - // 如果不是浅观察,则尝试为新值创建观察者实例 - childOb = !shallow && observe(newVal); - // 通知所有依赖更新 - dep.notify(); - } - }); - } - -// Set a property on an object. Adds the new property and -// triggers change notification if the property doesn't -// already exist. -// 在对象上设置一个属性。如果属性不存在,则添加该属性并触发变更通知 - function set ( - // 目标对象 - target, - // 属性名 - key, - // 属性值 - val - ) { - // 如果目标对象为 undefined、null 或者是原始类型,则发出警告 - if (isUndef(target) || isPrimitive(target) - ) { - warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); - } - // 如果目标对象是数组且键是有效的数组索引 - if (Array.isArray(target) && isValidArrayIndex(key)) { - // 确保数组长度足够 - target.length = Math.max(target.length, key); - // 使用 splice 方法替换数组中的元素 - target.splice(key, 1, val); - // 返回设置的值 - return val - } - // 如果键已经存在于目标对象中且不是原型链上的属性 - if (key in target && !(key in Object.prototype)) { - // 直接设置属性值 - target[key] = val; - // 返回设置的值 - return val - } - // 获取目标对象的观察者实例 - var ob = (target).__ob__; - // 如果目标对象是 Vue 实例或者是根数据对象,则发出警告 - if (target._isVue || (ob && ob.vmCount)) { - warn( - 'Avoid adding reactive properties to a Vue instance or its root $data ' + - 'at runtime - declare it upfront in the data option.' - ); - // 返回设置的值 - return val - } - // 如果没有观察者实例 - if (!ob) { - // 直接设置属性值 - target[key] = val; - // 返回设置的值 - return val - } - // 为目标对象的属性定义响应式 - defineReactive$$1(ob.value, key, val); - // 通知依赖更新 - ob.dep.notify(); - // 返回设置的值 - return val - } - -// Delete a property and trigger change if necessary. -// 删除一个属性,并在必要时触发变更通知 - function del ( - // 目标对象 - target, - // 属性名 - key - ) { - // 如果目标对象为 undefined、null 或者是原始类型,则发出警告 - if (isUndef(target) || isPrimitive(target) - ) { - warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); - } - // 如果目标对象是数组且键是有效的数组索引 - if (Array.isArray(target) && isValidArrayIndex(key)) { - // 使用 splice 方法删除数组中的元素 - target.splice(key, 1); - return - } - // 获取目标对象的观察者实例 - var ob = (target).__ob__; - // 如果目标对象是 Vue 实例或者是根数据对象,则发出警告 - if (target._isVue || (ob && ob.vmCount)) { - warn( - 'Avoid deleting properties on a Vue instance or its root $data ' + - '- just set it to null.' - ); - return - } - // 如果目标对象没有该属性,则直接返回 - if (!hasOwn(target, key)) { - return - } - // 删除目标对象的属性 - delete target[key]; - // 如果没有观察者实例,则直接返回 - if (!ob) { - return - } - // 通知依赖更新 - ob.dep.notify(); - } - -// Collect dependencies on array elements when the array is touched, since -// we cannot intercept array element access like property getters. -// 当数组被访问时,收集数组元素的依赖,因为我们无法像拦截属性 getter 那样拦截数组元素的访问 - function dependArray ( - // 数组 - value - ) { - // 遍历数组 - for (var e = (void 0), i = 0, l = value.length; i < l; i++) { - // 获取数组元素 - e = value[i]; - // 如果元素存在且有观察者实例,则收集该元素的依赖 - e && e.__ob__ && e.__ob__.dep.depend(); - // 如果元素是数组 - if (Array.isArray(e)) { - // 递归收集数组元素的依赖 - dependArray(e); - } - } - } - -// Option overwriting strategies are functions that handle -// how to merge a parent option value and a child option -// value into the final value. -// 选项覆盖策略是处理如何将父选项值和子选项值合并为最终值的函数 - var strats = config.optionMergeStrategies; - -// Options with restrictions -// 有约束的选项 - { - // 定义 el 和 propsData 选项的合并策略 - strats.el = strats.propsData = function ( - // 父选项值 - parent, - // 子选项值 - child, - // Vue 实例 - vm, - // 选项名 - key - ) { - // 如果没有 Vue 实例,则发出警告 - if (!vm) { - warn( - "option \"" + key + "\" can only be used during instance " + - 'creation with the `new` keyword.' - ); - } - // 使用默认的合并策略 - return defaultStrat(parent, child) - }; - } - -// Helper that recursively merges two data objects together. -// 递归合并两个数据对象的辅助函数 - function mergeData ( - // 目标对象 - to, - // 源对象 - from - ) { - // 如果源对象不存在,则返回目标对象 - if (!from) { return to } - // 定义变量,用于存储键、目标对象的值和源对象的值 - var key, toVal, fromVal; - - // 获取源对象的所有键 - var keys = hasSymbol - ? Reflect.ownKeys(from) - : Object.keys(from); - - // 遍历源对象的所有键 - for (var i = 0; i < keys.length; i++) { - // 获取当前键 - key = keys[i]; - // 如果键是 __ob__,则跳过 - // in case the object is already observed... - if (key === '__ob__') { continue } - // 获取目标对象上该键的值 - toVal = to[key]; - // 获取源对象上该键的值 - fromVal = from[key]; - // 如果目标对象没有该键 - if (!hasOwn(to, key)) { - // 在目标对象上设置该键的值 - set(to, key, fromVal); - } else if ( - // 如果目标对象和源对象上该键的值不相等 - toVal !== fromVal && - // 目标对象上该键的值是纯对象 - isPlainObject(toVal) && - // 源对象上该键的值是纯对象 - isPlainObject(fromVal) - ) { - // 递归合并两个对象 - mergeData(toVal, fromVal); - } - } - // 返回合并后的目标对象 - return to - } - -// Data -// 数据合并函数 - function mergeDataOrFn ( - // 父数据值 - parentVal, - // 子数据值 - childVal, - // Vue 实例 - vm - ) { - // 如果没有 Vue 实例 - if (!vm) { - // 在 Vue.extend 合并时,两者都应该是函数 - // 如果子数据值不存在,则返回父数据值 - if (!childVal) { - return parentVal - } - // 如果父数据值不存在,则返回子数据值 - if (!parentVal) { - return childVal - } - // 当父数据值和子数据值都存在时,我们需要返回一个函数,该函数返回两个函数合并后的结果 - // 这里不需要检查父数据值是否是函数,因为它必须是函数才能通过之前的合并 - return function mergedDataFn () { - return mergeData( - // 如果子数据值是函数,则调用该函数并传入 this 上下文,否则直接使用子数据值 - typeof childVal === 'function' ? childVal.call(this, this) : childVal, - // 如果父数据值是函数,则调用该函数并传入 this 上下文,否则直接使用父数据值 - typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal - ) - } - } else { - return function mergedInstanceDataFn () { - // 实例合并 - // 如果子数据值是函数,则调用该函数并传入 Vue 实例,否则直接使用子数据值 - var instanceData = typeof childVal === 'function' - ? childVal.call(vm, vm) - : childVal; - // 如果父数据值是函数,则调用该函数并传入 Vue 实例,否则直接使用父数据值 - var defaultData = typeof parentVal === 'function' - ? parentVal.call(vm, vm) - : parentVal; - // 如果实例数据存在 - if (instanceData) { - // 合并实例数据和默认数据 - return mergeData(instanceData, defaultData) - } else { - // 否则返回默认数据 - return defaultData - } - } - } - } - -// 定义 data 选项的合并策略 - strats.data = function ( - // 父选项值 - parentVal, - // 子选项值 - childVal, - // Vue 实例 - vm - ) { - // 如果没有 Vue 实例 - if (!vm) { - // 如果子选项值存在且不是函数,则发出警告 - if (childVal && typeof childVal !== 'function') { - warn( - 'The "data" option should be a function ' + - 'that returns a per-instance value in component ' + - 'definitions.', - vm - ); - - return parentVal - } - // 使用 mergeDataOrFn 函数进行合并 - return mergeDataOrFn(parentVal, childVal) - } - - // 使用 mergeDataOrFn 函数进行合并,并传入 Vue 实例 - return mergeDataOrFn(parentVal, childVal, vm) - }; - -// Hooks and props are merged as arrays. -// 钩子和 props 以数组形式合并 - function mergeHook ( - // 父选项值 - parentVal, - // 子选项值 - childVal - ) { - // 初始化合并结果 - var res = childVal - ? parentVal - // 如果父选项值存在,则将子选项值合并到父选项值中 - ? parentVal.concat(childVal) - // 如果父选项值不存在,且子选项值是数组,则直接使用子选项值 - : Array.isArray(childVal) - ? childVal - // 如果父选项值不存在,且子选项值不是数组,则将子选项值包装成数组 - : [childVal] - // 如果子选项值不存在,则使用父选项值 - : parentVal; - // 对合并结果进行去重处理 - return res - ? dedupeHooks(res) - : res - } - -// 对钩子数组进行去重处理 - function dedupeHooks ( - // 钩子数组 - hooks - ) { - // 初始化去重后的数组 - var res = []; - // 遍历钩子数组 - for (var i = 0; i < hooks.length; i++) { - // 如果去重后的数组中不包含当前钩子,则将其添加到去重后的数组中 - if (res.indexOf(hooks[i]) === -1) { - res.push(hooks[i]); - } - } - // 返回去重后的数组 - return res - } - -// 遍历生命周期钩子数组 - LIFECYCLE_HOOKS.forEach(function (hook) { - // 为每个生命周期钩子定义合并策略 - strats[hook] = mergeHook; - }); - -// Assets -// 当存在 Vue 实例(实例创建时),我们需要对构造函数选项、实例选项和父选项进行三方合并 -// 资产(组件、指令、过滤器)的合并函数 - function mergeAssets ( - // 父选项值 - parentVal, - // 子选项值 - childVal, - // Vue 实例 - vm, - // 选项名 - key - ) { - // 创建一个继承自父选项值的对象 - var res = Object.create(parentVal || null); - // 如果子选项值存在 - if (childVal) { - // 检查子选项值是否为对象类型 - assertObjectType(key, childVal, vm); - // 将子选项值合并到结果对象中 - return extend(res, childVal) - } else { - // 如果子选项值不存在,则返回结果对象 - return res - } - } - -// 遍历资产类型数组 - ASSET_TYPES.forEach(function (type) { - // 为每个资产类型定义合并策略 - strats[type + 's'] = mergeAssets; - }); - -// Watchers. -// 监听器的合并策略 -// Watchers hashes should not overwrite one -// another, so we merge them as arrays. -// 监听器哈希不应该相互覆盖,所以我们将它们合并为数组 - strats.watch = function ( - // 父选项值 - parentVal, - // 子选项值 - childVal, - // Vue 实例 - vm, - // 选项名 - key - ) { - // work around Firefox's Object.prototype.watch... - // 处理 Firefox 的 Object.prototype.watch - if (parentVal === nativeWatch) { parentVal = undefined; } - if (childVal === nativeWatch) { childVal = undefined; } - /* istanbul ignore if */ - // 如果子选项值不存在,则返回一个继承自父选项值的对象 - if (!childVal) { return Object.create(parentVal || null) } - { - // 检查子选项值是否为对象类型 - assertObjectType(key, childVal, vm); - } - // 如果父选项值不存在,则返回子选项值 - if (!parentVal) { return childVal } - // 初始化合并结果 - var ret = {}; - // 将父选项值合并到结果对象中 - extend(ret, parentVal); - // 遍历子选项值的所有键 - for (var key$1 in childVal) { - // 获取父选项值中该键的值 - var parent = ret[key$1]; - // 获取子选项值中该键的值 - var child = childVal[key$1]; - // 如果父选项值中该键的值存在且不是数组,则将其包装成数组 - if (parent && !Array.isArray(parent)) { - parent = [parent]; - } - // 将子选项值中该键的值合并到父选项值中 - ret[key$1] = parent - ? parent.concat(child) - : Array.isArray(child) ? child : [child]; - } - // 返回合并结果 - return ret - }; - -// Other object hashes. -// 其他对象哈希的合并策略 - strats.props = - strats.methods = - strats.inject = - strats.computed = function ( - // 父选项值 - parentVal, - // 子选项值 - childVal, - // Vue 实例 - vm, - // 选项名 - key - ) { - // 如果子选项值存在且不是生产环境,则检查子选项值是否为对象类型 - if (childVal && "development" !== 'production') { - assertObjectType(key, childVal, vm); - } - // 如果父选项值不存在,则返回子选项值 - if (!parentVal) { return childVal } - // 初始化合并结果 - var ret = Object.create(null); - // 将父选项值合并到结果对象中 - extend(ret, parentVal); - // 如果子选项值存在,则将其合并到结果对象中 - if (childVal) { extend(ret, childVal); } - // 返回合并结果 - return ret - }; -// provide 选项的合并策略使用 mergeDataOrFn 函数 - strats.provide = mergeDataOrFn; - -// Default strategy. -// 默认合并策略 - var defaultStrat = function ( - // 父选项值 - parentVal, - // 子选项值 - childVal - ) { - // 如果子选项值为 undefined,则返回父选项值,否则返回子选项值 - return childVal === undefined - ? parentVal - : childVal - }; - -// Validate component names -// 验证组件名称 - function checkComponents ( - // 选项对象 - options - ) { - // 遍历选项对象中的 components 属性 - for (var key in options.components) { - // 验证组件名称 - validateComponentName(key); - } - } - -// 验证组件名称是否合法 - function validateComponentName ( - // 组件名称 - name - ) { - // 如果组件名称不符合正则表达式的规则,则发出警告 - if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) { - warn( - 'Invalid component name: "' + name + '". Component names ' + - 'should conform to valid custom element name in html5 specification.' - ); - } - // 如果组件名称是内置标签或者是保留标签,则发出警告 - if (isBuiltInTag(name) || config.isReservedTag(name)) { - warn( - 'Do not use built-in or reserved HTML elements as component ' + - 'id: ' + name - ); - } - } - -// Ensure all props option syntax are normalized into the -// Object-based format. -// 确保所有 props 选项的语法都被规范化为基于对象的格式 - function normalizeProps ( - // 选项对象 - options, - // Vue 实例 - vm - ) { - // 获取选项对象中的 props 属性 - var props = options.props; - // 如果 props 属性不存在,则直接返回 - if (!props) { return } - // 初始化规范化后的 props 对象 - var res = {}; - // 定义变量,用于存储索引、值和名称 - var i, val, name; - // 如果 props 属性是数组 - if (Array.isArray(props)) { - // 获取数组的长度 - i = props.length; - // 从后往前遍历数组 - while (i--) { - // 获取当前元素 - val = props[i]; - // 如果当前元素是字符串 - if (typeof val === 'string') { - // 将字符串转换为驼峰命名 - name = camelize(val); - // 在规范化后的 props 对象中添加该属性,并设置类型为 null - res[name] = { type: null }; - } else { - // 如果当前元素不是字符串,则发出警告 - warn('props must be strings when using array syntax.'); - } - } - } else if (isPlainObject(props)) { - // 如果 props 属性是纯对象 - // 遍历对象的所有键 - for (var key in props) { - // 获取当前键对应的值 - val = props[key]; - // 将键转换为驼峰命名 - name = camelize(key); - // 如果值是纯对象,则直接使用该值,否则将其包装成一个包含类型的对象 - res[name] = isPlainObject(val) - ? val - : { type: val }; - } - } else { - // 如果 props 属性既不是数组也不是纯对象,则发出警告 - warn( - "Invalid value for option \"props\": expected an Array or an Object, " + - "but got " + (toRawType(props)) + ".", - vm - ); - } - // 将规范化后的 props 对象赋值给选项对象的 props 属性 - options.props = res; - } - -// Normalize all injections into Object-based format -// 将所有注入项规范化为基于对象的格式 - function normalizeInject ( - // 选项对象 - options, - // Vue 实例 - vm - ) { - // 获取选项对象中的 inject 属性 - var inject = options.inject; - // 如果 inject 属性不存在,则直接返回 - if (!inject) { return } - // 初始化规范化后的 inject 对象 - var normalized = options.inject = {}; - // 如果 inject 属性是数组 - if (Array.isArray(inject)) { - // 遍历数组 - for (var i = 0; i < inject.length; i++) { - // 在规范化后的 inject 对象中添加该属性,并设置 from 属性为当前元素 - normalized[inject[i]] = { from: inject[i] }; - } - } else if (isPlainObject(inject)) { - // 如果 inject 属性是纯对象 - // 遍历对象的所有键 - for (var key in inject) { - // 获取当前键对应的值 - var val = inject[key]; - // 如果值是纯对象,则将 from 属性合并到该值中,否则将其包装成一个包含 from 属性的对象 - normalized[key] = isPlainObject(val) - ? extend({ from: key }, val) - : { from: val }; - } - } else { - // 如果 inject 属性既不是数组也不是纯对象,则发出警告 - warn( - "Invalid value for option \"inject\": expected an Array or an Object, " + - "but got " + (toRawType(inject)) + ".", - vm - ); - } - } - -// Normalize raw function directives into object format. -// 将原始的函数指令规范化为对象格式 - function normalizeDirectives ( - // 选项对象 - options - ) { - // 获取选项对象中的 directives 属性 - var dirs = options.directives; - // 如果 directives 属性存在 - if (dirs) { - // 遍历 directives 对象的所有键 - for (var key in dirs) { - // 获取当前键对应的值 - var def$$1 = dirs[key]; - // 如果值是函数 - if (typeof def$$1 === 'function') { - // 将其包装成一个包含 bind 和 update 方法的对象 - dirs[key] = { bind: def$$1, update: def$$1 }; - } - } - } - } - -// 检查值是否为纯对象,如果不是则发出警告 - function assertObjectType ( - // 选项名 - name, - // 值 - value, - // Vue 实例 - vm - ) { - // 如果值不是纯对象,则发出警告 - if (!isPlainObject(value)) { - warn( - "Invalid value for option \"" + name + "\": expected an Object, " + - "but got " + (toRawType(value)) + ".", - vm - ); - } - } - -// Merge two option objects into a new one. -// Core utility used in both instantiation and inheritance. -// 将两个选项对象合并为一个新的对象 -// 实例化和继承中使用的核心工具 - function mergeOptions ( - // 父选项对象 - parent, - // 子选项对象 - child, - // Vue 实例 - vm - ) { - { - // 检查子选项对象中的组件名称是否合法 - checkComponents(child); - } - - // 如果子选项对象是一个函数,则获取其 options 属性 - if (typeof child === 'function') { - child = child.options; - } - - // 规范化子选项对象中的 props 属性 - normalizeProps(child, vm); - // 规范化子选项对象中的 inject 属性 - normalizeInject(child, vm); - // 规范化子选项对象中的 directives 属性 - normalizeDirectives(child); - - // Apply extends and mixins on the child options, - // but only if it is a raw options object that isn't - // the result of another mergeOptions call. - // Only merged options has the _base property. - // 如果子选项对象不是另一次 mergeOptions 调用的结果(即没有 _base 属性) - if (!child._base) { - // 如果子选项对象有 extends 属性 - if (child.extends) { - // 递归合并父选项对象和子选项对象的 extends 属性 - parent = mergeOptions(parent, child.extends, vm); - } - // 如果子选项对象有 mixins 属性 - if (child.mixins) { - // 遍历 mixins 数组 - for (var i = 0, l = child.mixins.length; i < l; i++) { - // 递归合并父选项对象和 mixins 数组中的每个选项对象 - parent = mergeOptions(parent, child.mixins[i], vm); - } - } - } - // 定义一个空对象options - var options = {}; -// 定义变量key - var key; -// 遍历parent对象的属性 - for (key in parent) { - // 调用mergeField函数处理属性 - mergeField(key); - } -// 遍历child对象的属性 - for (key in child) { - // 如果parent对象不包含当前属性 - if (!hasOwn(parent, key)) { - // 调用mergeField函数处理属性 - mergeField(key); - } - } -// 定义mergeField函数,用于合并属性 - function mergeField (key) { - // 获取属性的合并策略,如果没有则使用默认策略 - var strat = strats[key] || defaultStrat; - // 根据合并策略合并parent和child中对应属性的值,并将结果存储在options中 - options[key] = strat(parent[key], child[key], vm, key); - } -// 返回合并后的options对象 - return options - } - //解析一个资源。 - // 这个函数被使用是因为子实例需要访问在其祖先链中定义的资源。 -// 定义resolveAsset函数,用于解析资源 - function resolveAsset ( - options, - type, - id, - warnMissing - ) { - // 如果id不是字符串类型,则返回 - /* istanbul ignore if */ - if (typeof id!== 'string') { - return - } - // 获取指定类型的资源 - var assets = options[type]; - // 首先检查本地注册的变体 - if (hasOwn(assets, id)) { return assets[id] } - // 将id转换为驼峰命名 - var camelizedId = camelize(id); - // 如果驼峰命名的id存在于资源中,则返回对应的值 - if (hasOwn(assets, camelizedId)) { return assets[camelizedId] } - // 将驼峰命名的id转换为首字母大写的形式 - var PascalCaseId = capitalize(camelizedId); - // 如果首字母大写的id存在于资源中,则返回对应的值 - if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] } - // 回退到原型链查找 - var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; - // 如果需要警告且没有找到资源,则发出警告 - if (warnMissing &&!res) { - warn( - 'Failed to resolve'+ type.slice(0, -1) + ':'+ id, - options - ); - } - // 返回找到的资源 - return res - } - - /* */ - -// 定义validateProp函数,用于验证属性 - function validateProp ( - key, - propOptions, - propsData, - vm - ) { - // 获取属性的选项 - var prop = propOptions[key]; - // 判断属性是否不存在于propsData中 - var absent =!hasOwn(propsData, key); - // 获取属性的值 - var value = propsData[key]; - // 布尔类型转换 - var stringIndex = getTypeIndex(String, prop.type); - if (stringIndex < 0 || booleanIndex < stringIndex) { - value = true; - } - } - } - // 检查默认值 - if (value === undefined) { - // 获取属性的默认值 - value = getPropDefaultValue(vm, prop, key); - // 因为默认值是一个新的副本,所以确保对其进行观察 - var prevShouldObserve = shouldObserve; - toggleObserving(true); - observe(value); - toggleObserving(prevShouldObserve); - } - { - // 断言属性是否有效 - assertProp(prop, key, value, vm, absent); - } - // 返回验证后的属性值 - return value - } - - // 获取属性的默认值。 -// 定义getPropDefaultValue函数,用于获取属性的默认值 - function getPropDefaultValue (vm, prop, key) { - // 如果属性没有默认值,则返回undefined - if (!hasOwn(prop, 'default')) { - return undefined - } - // 获取属性的默认值 - var def = prop.default; - // 对对象和数组类型的默认值进行警告检查,非工厂函数的默认值是无效的 - if (isObject(def)) { - warn( - 'Invalid default value for prop "' + key + '":'+ - 'Props with type Object/Array must use a factory function'+ - 'to return the default value.', - vm - ); - } - // 如果vm存在,且propsData中属性值为undefined,且vm._props中属性值不为undefined,则返回vm._props中的值 - if (vm && vm.$options.propsData && - vm.$options.propsData[key] === undefined && - vm._props[key]!== undefined - ) { - return vm._props[key] - } - // 如果默认值是函数且属性类型不是函数,则调用函数获取默认值,否则直接返回默认值 - return typeof def === 'function' && getType(prop.type)!== 'Function' - ? def.call(vm) - : def - } - - // 断言一个属性是否有效。 -// 定义assertProp函数,用于断言属性是否有效 - function assertProp ( - prop, - name, - value, - vm, - absent - ) { - // 如果属性是必需的且不存在,则发出警告 - if (prop.required && absent) { - warn( - 'Missing required prop: "' + name + '"', - vm - ); - return - } - // 如果属性值为null且属性不是必需的,则返回 - if (value == null &&!prop.required) { - return - } - // 获取属性的类型 - var type = prop.type; - // 初始化有效性为true,如果没有指定类型或者类型为true - var valid =!type || type === true; - // 定义预期类型数组 - var expectedTypes = []; - // 如果有指定属性类型 - if (type) { - // 如果类型不是数组,则将其转换为数组 - if (!Array.isArray(type)) { - type = [type]; - } - // 遍历类型数组,检查属性值是否符合预期类型 - for (var i = 0; i < type.length &&!valid; i++) { - var assertedType = assertType(value, type[i]); - expectedTypes.push(assertedType.expectedType || ''); - valid = assertedType.valid; - } - } - - // 如果属性值不符合预期类型,则发出警告 - if (!valid) { - warn( - getInvalidTypeMessage(name, value, expectedTypes), - vm - ); - return - } - // 获取属性的验证函数 - var validator = prop.validator; - // 如果有验证函数且属性值不通过验证,则发出警告 - if (validator) { - if (!validator(value)) { - warn( - 'Invalid prop: custom validator check failed for prop "' + name + '".', - vm - ); - } - } - } - -// 定义简单类型检查正则表达式 - var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/; - -// 定义assertType函数,用于检查属性值的类型是否符合预期 - function assertType (value, type) { - // 初始化有效性为false - var valid; - // 获取预期类型 - var expectedType = getType(type); - // 如果预期类型是简单类型 - if (simpleCheckRE.test(expectedType)) { - // 获取属性值的类型 - var t = typeof value; - // 检查属性值类型是否与预期类型一致(对于基本包装对象,还需要检查是否是预期类型的实例) - valid = t === expectedType.toLowerCase(); - if (!valid && t === 'object') { - valid = value instanceof type; - } - } else if (expectedType === 'Object') { - // 如果预期类型是对象,则检查属性值是否是普通对象 - valid = isPlainObject(value); - } else if (expectedType === 'Array') { - // 如果预期类型是数组,则检查属性值是否是数组 - valid = Array.isArray(value); - } else { - // 否则检查属性值是否是预期类型的实例 - valid = value instanceof type; - } - // 返回检查结果 - return { - valid: valid, - expectedType: expectedType - } - } - //使用函数的字符串名称来检查内置类型, - // 因为简单的相等性检查在不同的vm/iframe中运行时会失败。 -// 定义getType函数,用于获取函数的类型名称 - function getType (fn) { - // 匹配函数的字符串表示中的函数名 - var match = fn && fn.toString().match(/^\s*function (\w+)/); - // 返回匹配到的函数名,否则返回空字符串 - return match? match[1] : '' - } - -// 定义isSameType函数,用于判断两个类型是否相同 - function isSameType (a, b) { - // 通过比较两个类型的名称是否相同来判断 - return getType(a) === getType(b) - } - -// 定义getTypeIndex函数,用于获取类型在预期类型数组中的索引 - function getTypeIndex (type, expectedTypes) { - // 如果预期类型不是数组,则直接比较类型是否相同并返回索引 - if (!Array.isArray(expectedTypes)) { - return isSameType(expectedTypes, type)? 0 : -1 - } - // 遍历预期类型数组,查找类型的索引 - for (var i = 0, len = expectedTypes.length; i < len; i++) { - if (isSameType(expectedTypes[i], type)) { - return i - } - } - // 如果没有找到,则返回-1 - return -1 - } - -// 定义getInvalidTypeMessage函数,用于获取无效类型的错误消息 - function getInvalidTypeMessage (name, value, expectedTypes) { - // 初始化错误消息 - var message = "Invalid prop: type check failed for prop \"" + name + "\"." + - " Expected " + (expectedTypes.map(capitalize).join(', ')); - // 获取第一个预期类型 - var expectedType = expectedTypes[0]; - // 获取属性值的实际类型 - var receivedType = toRawType(value); - // 格式化预期值 - var expectedValue = styleValue(value, expectedType); - // 格式化实际值 - var receivedValue = styleValue(value, receivedType); - // 如果只有一个预期类型且是可解释的类型,并且实际类型不是布尔类型,则在错误消息中添加预期值 - if (expectedTypes.length === 1 && - isExplicable(expectedType) && - !isBoolean(expectedType, receivedType)) { - message += " with value " + expectedValue; - } - // 在错误消息中添加实际类型 - message += ", got " + receivedType + " "; - // 如果实际类型是可解释的类型,则在错误消息中添加实际值 - if (isExplicable(receivedType)) { - message += "with value " + receivedValue + "."; - } - // 返回错误消息 - return message - } - -// 定义styleValue函数,用于格式化值的显示形式 - function styleValue (value, type) { - // 如果类型是字符串,则用双引号包裹值 - if (type === 'String') { - return ("\"" + value + "\"") - } else if (type === 'Number') { - // 如果类型是数字,则将值转换为字符串 - return ("" + (Number(value))) - } else { - // 否则直接将值转换为字符串 - return ("" + value) - } - } - -// 定义isExplicable函数,用于判断类型是否是可解释的类型(字符串、数字、布尔) - function isExplicable (value) { - // 定义可解释的类型数组 - var explicitTypes = ['string', 'number', 'boolean']; - // 检查值的类型是否在可解释的类型数组中 - return explicitTypes.some(function (elem) { return value.toLowerCase() === elem; }) - } - -// 定义isBoolean函数,用于判断参数中是否包含布尔类型 - function isBoolean () { - // 将参数转换为数组 - var args = [], len = arguments.length; - while ( len-- ) args[ len ] = arguments[ len ]; - - // 检查数组中是否有元素的类型为布尔类型 - return args.some(function (elem) { return elem.toLowerCase() === 'boolean'; }) - } - - -// 定义handleError函数,用于处理错误 - function handleError (err, vm, info) { - // 在处理错误处理程序时停用依赖跟踪,以避免可能的无限渲染。 - // 参考:https://github.com/vuejs/vuex/issues/1505 - pushTarget(); - try { - // 如果有vm实例 - if (vm) { - var cur = vm; - // 向上遍历vm的父级实例 - while ((cur = cur.$parent)) { - // 获取父级实例的errorCaptured钩子函数数组 - var hooks = cur.$options.errorCaptured; - // 如果有errorCaptured钩子函数 - if (hooks) { - // 遍历钩子函数数组 - for (var i = 0; i < hooks.length; i++) { - try { - // 调用钩子函数,并判断是否捕获了错误(返回false表示捕获) - var capture = hooks[i].call(cur, err, vm, info) === false; - if (capture) { return } - } catch (e) { - // 如果钩子函数调用时出错,则调用全局错误处理函数 - globalHandleError(e, cur, 'errorCaptured hook'); - } - } - } - } - } - // 调用全局错误处理函数 - globalHandleError(err, vm, info); - } finally { - // 恢复依赖跟踪 - popTarget(); - } - } - -// 定义invokeWithErrorHandling函数,用于在错误处理下调用处理程序 - function invokeWithErrorHandling ( - handler, - context, - args, - vm, - info - ) { - // 定义变量res用于存储处理程序的返回值 - var res; - try { - // 调用处理程序 - res = args? handler.apply(context, args) : handler.call(context); - // 如果返回值是Promise且未被处理,则添加错误处理 - if (res &&!res._isVue && isPromise(res) &&!res._handled) { - res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); }); - // 标记Promise已被处理,避免多次捕获 - res._handled = true; - } - } catch (e) { - // 如果处理程序调用时出错,则调用错误处理函数 - handleError(e, vm, info); - } - // 返回处理程序的返回值 - return res - } - -// 定义globalHandleError函数,用于全局处理错误 - function globalHandleError (err, vm, info) { - // 如果有配置的错误处理函数 - if (config.errorHandler) { - try { - // 调用配置的错误处理函数 - return config.errorHandler.call(null, err, vm, info) - } catch (e) { - // 如果用户在处理函数中故意抛出原始错误,不重复记录 - if (e!== err) { - logError(e, null, 'config.errorHandler'); - } - } - } - // 调用logError函数记录错误 - logError(err, vm, info); - } - -// 定义logError函数,用于记录错误 - function logError (err, vm, info) { - { - // 发出警告 - warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm); - } - /* istanbul ignore else */ - // 如果在浏览器或Weex环境中且console可用,则在控制台输出错误 - if ((inBrowser || inWeex) && typeof console!== 'undefined') { - console.error(err); - } else { - // 否则抛出错误 - throw err - } - } - - -// 定义变量isUsingMicroTask表示是否使用微任务,初始值为false - var isUsingMicroTask = false; - -// 定义callbacks数组用于存储回调函数 - var callbacks = []; -// 定义变量pending表示是否有挂起的回调,初始值为false - var pending = false; - -// 定义flushCallbacks函数,用于刷新回调函数队列 - function flushCallbacks () { - // 将pending设为false - pending = false; - // 获取callbacks数组的副本 - var copies = callbacks.slice(0); - // 清空callbacks数组 - callbacks.length = 0; - // 遍历副本数组,依次调用回调函数 - for (var i = 0; i < copies.length; i++) { - copies[i](); - } - } - // 这里我们使用微任务来实现异步延迟包装器 -// 在 2.5 版本中,我们使用(宏)任务(结合微任务) -// 然而,在重绘之前立即更改状态时会有一些细微问题 -// (例如问题 #6813,out - in 过渡效果) -// 此外,在事件处理程序中使用(宏)任务会导致一些无法避免的奇怪行为 -// (例如问题 #7109、#7153、#7546、#7834、#8109) -// 所以现在我们再次在各处使用微任务 -// 这种权衡的一个主要缺点是,在某些场景下 -// 微任务的优先级过高,会在本应按顺序执行的事件之间触发 -// (例如问题 #4521、#6690,这些问题有解决办法) -// 甚至会在同一事件的冒泡过程中触发(问题 #6566) - var timerFunc; - -// nextTick 行为利用了微任务队列,可以通过原生 Promise.then 或 MutationObserver 来访问该队列 -// MutationObserver 有更广泛的支持,然而在 iOS >= 9.3.3 的 UIWebView 中,当在触摸事件处理程序中触发时,它存在严重的 bug -// 触发几次后它会完全停止工作……所以,如果原生 Promise 可用,我们将使用它 - /* istanbul ignore next, $flow-disable-line */ - if (typeof Promise!== 'undefined' && isNative(Promise)) { - // 创建一个已解决的 Promise 实例 - var p = Promise.resolve(); - // 定义 timerFunc 函数,用于在 Promise 的 then 方法中调用 flushCallbacks 函数 - timerFunc = function () { - p.then(flushCallbacks); - // 在有问题的 UIWebView 中,Promise.then 不会完全失效,但 - // 它可能会陷入一种奇怪的状态,即回调被推入微任务队列,但队列没有被刷新,直到浏览器需要执行其他工作,例如处理定时器 - // 因此,我们可以通过添加一个空定时器来“强制”刷新微任务队列 - if (isIOS) { setTimeout(noop); } - }; - // 标记正在使用微任务 - isUsingMicroTask = true; -// 如果不是 IE 浏览器,且支持 MutationObserver - } else if (!isIE && typeof MutationObserver!== 'undefined' && ( - isNative(MutationObserver) || - // PhantomJS 和 iOS 7.x - MutationObserver.toString() === '[object MutationObserverConstructor]' - )) { - // 在原生 Promise 不可用时使用 MutationObserver - // 例如 PhantomJS、iOS 7、Android 4.4 - // (问题 #6466:MutationObserver 在 IE11 中不可靠) - var counter = 1; - // 创建一个 MutationObserver 实例,当观察到变化时调用 flushCallbacks 函数 - var observer = new MutationObserver(flushCallbacks); - // 创建一个文本节点 - var textNode = document.createTextNode(String(counter)); - // 开始观察文本节点的字符数据变化 - observer.observe(textNode, { - characterData: true - }); - // 定义 timerFunc 函数,通过改变文本节点的数据来触发 MutationObserver - timerFunc = function () { - counter = (counter + 1) % 2; - textNode.data = String(counter); - }; - // 标记正在使用微任务 - isUsingMicroTask = true; -// 如果支持 setImmediate 且是原生的 - } else if (typeof setImmediate!== 'undefined' && isNative(setImmediate)) { - // 回退到使用 setImmediate - // 从技术上讲,它利用了(宏)任务队列,但它仍然比 setTimeout 是更好的选择 - timerFunc = function () { - setImmediate(flushCallbacks); - }; - } else { - // 回退到使用 setTimeout - timerFunc = function () { - setTimeout(flushCallbacks, 0); - }; - } - -// 定义 nextTick 函数,用于将回调函数添加到队列中,并在合适的时机执行 - function nextTick (cb, ctx) { - var _resolve; - // 将回调函数包装后添加到 callbacks 数组中 - callbacks.push(function () { - if (cb) { - try { - // 调用回调函数 - cb.call(ctx); - } catch (e) { - // 处理回调函数执行过程中出现的错误 - handleError(e, ctx, 'nextTick'); - } - } else if (_resolve) { - // 如果没有回调函数但有 _resolve 函数,则调用 _resolve 函数 - _resolve(ctx); - } - }); - // 如果没有待处理的任务,则标记为有待处理任务,并调用 timerFunc 函数 - if (!pending) { - pending = true; - timerFunc(); - } - // $flow-disable-line - // 如果没有提供回调函数且支持 Promise,则返回一个 Promise 对象 - if (!cb && typeof Promise!== 'undefined') { - return new Promise(function (resolve) { - _resolve = resolve; - }) - } - } - - -// 定义 mark 和 measure 变量 - var mark; - var measure; - - { - // 检查是否在浏览器环境且支持 window.performance - var perf = inBrowser && window.performance; - /* istanbul ignore if */ - if ( - perf && - perf.mark && - perf.measure && - perf.clearMarks && - perf.clearMeasures - ) { - // 定义 mark 函数,用于在性能记录中标记一个时间点 - mark = function (tag) { return perf.mark(tag); }; - // 定义 measure 函数,用于测量两个标记时间点之间的性能 - measure = function (name, startTag, endTag) { - perf.measure(name, startTag, endTag); - perf.clearMarks(startTag); - perf.clearMarks(endTag); - // perf.clearMeasures(name) - }; - } - } \ No newline at end of file +// Vue.js v2.6.12 +// (c) 2014-2020 Evan You +// Released under the MIT License. +// 立即执行函数,用于创建 Vue 实例并挂载到全局对象 +(function (global, factory) { + // 检查是否为 CommonJS 模块环境 + if (typeof exports === 'object' && typeof module !== 'undefined') { + // 如果是 CommonJS 环境,通过 module.exports 导出 factory 函数的返回值 + module.exports = factory(); + } + // 检查是否为 AMD 模块环境 + else if (typeof define === 'function' && define.amd) { + // 如果是 AMD 环境,使用 define 方法定义模块 + define(factory); + } + // 若既不是 CommonJS 也不是 AMD 环境,则作为全局变量挂载到全局对象 + else { + global = global || self; + // 将 factory 函数的返回值挂载到全局对象的 Vue 属性上 + global.Vue = factory(); + } +// 传入全局对象 this 和工厂函数 +}(this, function () { + // 使用严格模式,增强代码的安全性和报错提示 + 'use strict'; + + // 创建一个冻结的空对象,防止其属性被修改,常用于默认值 + var emptyObject = Object.freeze({}); + + // 检查值是否为 undefined 或 null + function isUndef (v) { + // 若值为 undefined 或者 null 则返回 true + return v === undefined || v === null; + } + + // 检查值是否已定义且不为 null + function isDef (v) { + // 若值既不是 undefined 也不是 null 则返回 true + return v !== undefined && v !== null; + } + + // 检查值是否为 true + function isTrue (v) { + // 若值严格等于 true 则返回 true + return v === true; + } + + // 检查值是否为 false + function isFalse (v) { + // 若值严格等于 false 则返回 true + return v === false; + } + + // 检查值是否为原始类型(字符串、数字、符号、布尔值) + function isPrimitive (value) { + // 判断值的类型是否为字符串、数字、符号或布尔值 + return ( + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'symbol' || + typeof value === 'boolean' + ); + } + + // 快速检查值是否为对象(非 null 且类型为 'object') + function isObject (obj) { + // 若值不为 null 且类型为 'object' 则返回 true + return obj !== null && typeof obj === 'object'; + } + + // 获取对象原型上的 toString 方法引用,用于后续获取对象类型字符串 + var _toString = Object.prototype.toString; + + // 获取值的原始类型字符串,去除前后的 [object ] 部分 + function toRawType (value) { + // 调用 toString 方法并截取中间部分 + return _toString.call(value).slice(8, -1); + } + + // 严格检查对象是否为普通的 JavaScript 对象 + function isPlainObject (obj) { + // 若对象的 toString 结果为 [object Object] 则返回 true + return _toString.call(obj) === '[object Object]'; + } + + // 检查值是否为正则表达式对象 + function isRegExp (v) { + // 若对象的 toString 结果为 [object RegExp] 则返回 true + return _toString.call(v) === '[object RegExp]'; + } + + // 检查值是否为有效的数组索引(非负整数且为有限值) + function isValidArrayIndex (val) { + // 将值转换为浮点数 + var n = parseFloat(String(val)); + // 检查是否为非负整数且为有限值 + return n >= 0 && Math.floor(n) === n && isFinite(val); + } + + // 检查值是否为 Promise 对象(有 then 和 catch 方法) + function isPromise (val) { + // 检查值是否已定义且有 then 和 catch 方法 + return ( + isDef(val) && + typeof val.then === 'function' && + typeof val.catch === 'function' + ); + } + + // 将值转换为字符串,处理数组、对象等情况 + function toString (val) { + // 若值为 null 或 undefined 则返回空字符串 + if (val == null) { + return ''; + } + // 若值为数组或普通对象且其 toString 方法为默认的则进行 JSON 序列化 + if (Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)) { + return JSON.stringify(val, null, 2); + } + // 其他情况直接转换为字符串 + return String(val); + } + + // 将值转换为数字,若转换失败则返回原始值 + function toNumber (val) { + // 将值转换为浮点数 + var n = parseFloat(val); + // 若转换结果为 NaN 则返回原始值,否则返回转换后的数字 + return isNaN(n) ? val : n; + } + + // 创建一个映射对象,并返回一个用于检查键是否存在于映射中的函数 + function makeMap ( + str, + expectsLowerCase + ) { + // 创建一个空对象作为映射 + var map = Object.create(null); + // 将字符串按逗号分割成数组 + var list = str.split(','); + // 遍历数组,将每个元素作为键添加到映射中 + for (var i = 0; i < list.length; i++) { + map[list[i]] = true; + } + // 根据是否需要小写处理返回不同的检查函数 + if (expectsLowerCase) { + return function (val) { return map[val.toLowerCase()]; }; + } + return function (val) { return map[val]; }; + } + + // 创建一个检查是否为内置标签(slot、component)的函数 + var isBuiltInTag = makeMap('slot,component', true); + + // 创建一个检查是否为保留属性(key、ref 等)的函数 + var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is'); + + // 从数组中移除指定的项 + function remove (arr, item) { + // 检查数组是否有元素 + if (arr.length) { + // 查找项在数组中的索引 + var index = arr.indexOf(item); + // 若索引存在,则移除该项 + if (index > -1) { + return arr.splice(index, 1); + } + } + } + + // 获取对象原型上的 hasOwnProperty 方法引用 + var hasOwnProperty = Object.prototype.hasOwnProperty; + + // 检查对象是否自身拥有指定的属性 + function hasOwn (obj, key) { + // 使用 hasOwnProperty 方法检查对象是否拥有指定属性 + return hasOwnProperty.call(obj, key); + } + + // 创建一个函数的缓存版本,避免重复计算 + function cached (fn) { + // 创建一个空对象作为缓存 + var cache = Object.create(null); + return function cachedFn (str) { + // 检查缓存中是否已有结果 + var hit = cache[str]; + // 若有则返回缓存结果,否则计算并缓存结果 + return hit || (cache[str] = fn(str)); + }; + } + + // 用于将连字符分隔的字符串转换为驼峰式字符串 + var camelizeRE = /-(\w)/g; + + // 创建一个缓存版本的驼峰化函数 + var camelize = cached(function (str) { + // 使用正则表达式将连字符后的字母转换为大写 + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }); + }); + + // 创建一个缓存版本的首字母大写函数 + var capitalize = cached(function (str) { + // 将字符串的首字母转换为大写 + return str.charAt(0).toUpperCase() + str.slice(1); + }); + + // 用于将驼峰式字符串转换为连字符分隔的字符串 + var hyphenateRE = /\B([A-Z])/g; + + // 创建一个缓存版本的连字符化函数 + var hyphenate = cached(function (str) { + // 使用正则表达式将大写字母前添加连字符并转换为小写 + return str.replace(hyphenateRE, '-$1').toLowerCase(); + }); + + // 为不支持 bind 方法的环境提供的 polyfill + function polyfillBind (fn, ctx) { + function boundFn (a) { + var l = arguments.length; + // 根据参数数量调用原函数 + if (l) { + if (l > 1) { + return fn.apply(ctx, arguments); + } + return fn.call(ctx, a); + } + return fn.call(ctx); + } + // 记录原函数的参数长度 + boundFn._length = fn.length; + return boundFn; + } + + // 使用原生的 bind 方法 + function nativeBind (fn, ctx) { + return fn.bind(ctx); + } + + // 根据环境选择使用原生 bind 或 polyfill + var bind = Function.prototype.bind + ? nativeBind + : polyfillBind; + + // 将类数组对象转换为真正的数组 + function toArray (list, start) { + // 若未指定起始位置,则默认为 0 + start = start || 0; + // 计算数组长度 + var i = list.length - start; + // 创建一个指定长度的数组 + var ret = new Array(i); + // 遍历类数组对象,将元素复制到新数组中 + while (i--) { + ret[i] = list[i + start]; + } + return ret; + } + + // 将一个对象的属性复制到另一个对象上 + function extend (to, _from) { + // 遍历源对象的属性 + for (var key in _from) { + // 将属性复制到目标对象上 + to[key] = _from[key]; + } + return to; + } + + // 将对象数组合并为一个对象 + function toObject (arr) { + // 创建一个空对象作为结果 + var res = {}; + // 遍历数组 + for (var i = 0; i < arr.length; i++) { + // 若数组元素存在,则将其属性合并到结果对象上 + if (arr[i]) { + extend(res, arr[i]); + } + } + return res; + } + + // 空函数,不执行任何操作 + function noop (a, b, c) {} + + // 始终返回 false 的函数 + var no = function (a, b, c) { return false; }; + + // 返回传入参数本身的函数 + var identity = function (_) { return _; }; + + // 从编译器模块数组中生成静态键字符串 + function genStaticKeys (modules) { + // 使用 reduce 方法将模块的静态键合并成一个数组并转换为字符串 + return modules.reduce(function (keys, m) { + return keys.concat(m.staticKeys || []); + }, []).join(','); + } + + // 检查两个值是否宽松相等 + function looseEqual (a, b) { + // 若严格相等则直接返回 true + if (a === b) { + return true; + } + // 检查 a 是否为对象 + var isObjectA = isObject(a); + // 检查 b 是否为对象 + var isObjectB = isObject(b); + if (isObjectA && isObjectB) { + try { + // 检查 a 是否为数组 + var isArrayA = Array.isArray(a); + // 检查 b 是否为数组 + var isArrayB = Array.isArray(b); + if (isArrayA && isArrayB) { + // 若都是数组,检查长度和元素是否宽松相等 + return a.length === b.length && a.every(function (e, i) { + return looseEqual(e, b[i]); + }); + } else if (a instanceof Date && b instanceof Date) { + // 若都是日期对象,检查时间戳是否相等 + return a.getTime() === b.getTime(); + } else if (!isArrayA && !isArrayB) { + // 若都不是数组,检查键和值是否宽松相等 + var keysA = Object.keys(a); + var keysB = Object.keys(b); + return keysA.length === keysB.length && keysA.every(function (key) { + return looseEqual(a[key], b[key]); + }); + } + } catch (e) { + // 异常情况返回 false + return false; + } + } else if (!isObjectA && !isObjectB) { + // 若都不是对象,检查字符串形式是否相等 + return String(a) === String(b); + } + // 其他情况返回 false + return false; + } + + // 在数组中查找第一个与指定值宽松相等的元素的索引 + function looseIndexOf (arr, val) { + // 遍历数组 + for (var i = 0; i < arr.length; i++) { + // 若找到宽松相等的元素则返回其索引 + if (looseEqual(arr[i], val)) { + return i; + } + } + // 未找到则返回 -1 + return -1; + } + + // 确保函数只执行一次 + function once (fn) { + var called = false; + return function () { + // 若函数未执行过 + if (!called) { + // 标记为已执行 + called = true; + // 执行原函数 + fn.apply(this, arguments); + } + }; + } + + // 服务器渲染标记属性名 + var SSR_ATTR = 'data-server-rendered'; + + // 资产类型数组 + var ASSET_TYPES = [ + 'component', + 'directive', + 'filter' + ]; + + // 生命周期钩子数组 + var LIFECYCLE_HOOKS = [ + 'beforeCreate', + 'created', + 'beforeMount', + 'mounted', + 'beforeUpdate', + 'updated', + 'beforeDestroy', + 'destroyed', + 'activated', + 'deactivated', + 'errorCaptured', + 'serverPrefetch' + ]; + + // Vue 的配置对象 + var config = { + // 选项合并策略对象 + optionMergeStrategies: Object.create(null), + // 是否抑制警告信息 + silent: false, + // 是否在开发环境显示生产模式提示 + productionTip: "development" !== 'production', + // 是否启用开发者工具 + devtools: "development" !== 'production', + // 是否记录性能信息 + performance: false, + // 错误处理函数 + errorHandler: null, + // 警告处理函数 + warnHandler: null, + // 忽略的自定义元素列表 + ignoredElements: [], + // 自定义按键别名对象 + keyCodes: Object.create(null), + // 检查标签是否为保留标签的函数 + isReservedTag: no, + // 检查属性是否为保留属性的函数 + isReservedAttr: no, + // 检查标签是否为未知元素的函数 + isUnknownElement: no, + // 获取元素命名空间的函数 + getTagNamespace: noop, + // 解析平台标签名的函数 + parsePlatformTagName: identity, + // 检查属性是否必须使用属性绑定的函数 + mustUseProp: no, + // 是否异步执行更新 + async: true, + // 生命周期钩子数组 + _lifecycleHooks: LIFECYCLE_HOOKS + }; + + // 用于匹配 Unicode 字母的正则表达式 + var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; + + // 检查字符串是否以 $ 或 _ 开头 + function isReserved (str) { + // 获取字符串首字符的 Unicode 编码 + var c = (str + '').charCodeAt(0); + // 检查是否为 $ 或 _ 的编码 + return c === 0x24 || c === 0x5F; + } + + // 在对象上定义一个属性 + function def (obj, key, val, enumerable) { + // 使用 Object.defineProperty 方法定义属性 + Object.defineProperty(obj, key, { + // 属性的值 + value: val, + // 是否可枚举 + enumerable: !!enumerable, + // 是否可写 + writable: true, + // 是否可配置 + configurable: true + }); + } + + // 用于检查路径是否包含非法字符的正则表达式 + var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]")); + + // 解析简单路径,返回一个函数用于获取对象上的属性值 + function parsePath (path) { + // 检查路径是否包含非法字符 + if (bailRE.test(path)) { + return; + } + // 将路径按点分割成数组 + var segments = path.split('.'); + return function (obj) { + // 遍历路径数组,逐级获取对象属性值 + for (var i = 0; i < segments.length; i++) { + if (!obj) { + return; + } + obj = obj[segments[i]]; + } + return obj; + }; + } + + // 返回 factory 函数的返回值 + return {}; +})); +// 空注释,可能用于代码分隔 +// 检查当前环境是否支持使用 __proto__ 属性 +var hasProto = '__proto__' in {}; + +// 进行浏览器环境嗅探,判断代码运行的环境 +// 判断是否在浏览器环境中,通过检查 window 对象是否存在 +var inBrowser = typeof window !== 'undefined'; +// 判断是否在 Weex 环境中,通过检查 WXEnvironment 对象是否存在 +var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; +// 获取 Weex 环境的平台信息,并转换为小写 +var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase(); +// 获取浏览器环境下的用户代理信息,并转换为小写 +var UA = inBrowser && window.navigator.userAgent.toLowerCase(); +// 判断是否为 IE 浏览器 +var isIE = UA && /msie|trident/.test(UA); +// 判断是否为 IE9 浏览器 +var isIE9 = UA && UA.indexOf('msie 9.0') > 0; +// 判断是否为 Edge 浏览器 +var isEdge = UA && UA.indexOf('edge/') > 0; +// 判断是否为 Android 设备 +var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); +// 判断是否为 iOS 设备 +var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); +// 判断是否为 Chrome 浏览器 +var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; +// 判断是否为 PhantomJS 环境 +var isPhantomJS = UA && /phantomjs/.test(UA); +// 判断是否为 Firefox 浏览器,并获取其版本号 +var isFF = UA && UA.match(/firefox\/(\d+)/); + +// Firefox 在 Object.prototype 上有 "watch" 函数 +var nativeWatch = ({}).watch; + +// 检查浏览器是否支持 passive 选项 +var supportsPassive = false; +if (inBrowser) { + try { + // 创建一个空对象用于测试 passive 选项 + var opts = {}; + // 使用 Object.defineProperty 定义 passive 属性,当访问该属性时设置 supportsPassive 为 true + Object.defineProperty(opts, 'passive', { + get: function get () { + // 忽略此代码块的测试覆盖率检查 + /* istanbul ignore next */ + supportsPassive = true; + } + }); + // 尝试添加一个测试事件监听器,使用 passive 选项 + window.addEventListener('test-passive', null, opts); + } catch (e) {} +} + +// 用于存储是否为服务器渲染的标志,需要延迟求值 +var _isServer; +// 判断是否为服务器渲染的函数 +var isServerRendering = function () { + if (_isServer === undefined) { + // 忽略此代码块的测试覆盖率检查 + /* istanbul ignore if */ + if (!inBrowser && !inWeex && typeof global !== 'undefined') { + // 检测 vue-server-renderer 的存在,并避免 Webpack 对 process 进行填充 + _isServer = global['process'] && global['process'].env.VUE_ENV === 'server'; + } else { + _isServer = false; + } + } + return _isServer; +}; + +// 检测是否存在 Vue 开发者工具 +var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + +// 检查构造函数是否为原生构造函数 +/* istanbul ignore next */ +function isNative (Ctor) { + return typeof Ctor === 'function' && /native code/.test(Ctor.toString()); +} + +// 检查当前环境是否支持 Symbol 和 Reflect.ownKeys +var hasSymbol = + typeof Symbol !== 'undefined' && isNative(Symbol) && + typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); + +// 定义一个 Set 类型的变量 +var _Set; +// 忽略此代码块的测试覆盖率检查 +/* istanbul ignore if */ +if (typeof Set !== 'undefined' && isNative(Set)) { + // 如果原生支持 Set,则使用原生 Set + _Set = Set; +} else { + // 实现一个非标准的 Set 填充,仅适用于原始类型的键 + _Set = (function () { + // Set 构造函数 + function Set () { + // 使用一个空对象来模拟 Set 的存储 + this.set = Object.create(null); + } + // 检查 Set 中是否存在指定的键 + Set.prototype.has = function has (key) { + return this.set[key] === true; + }; + // 向 Set 中添加一个键 + Set.prototype.add = function add (key) { + this.set[key] = true; + }; + // 清空 Set + Set.prototype.clear = function clear () { + this.set = Object.create(null); + }; + + return Set; + }()); +} + +// 初始化警告、提示和组件跟踪相关的函数为空函数 +var warn = noop; +var tip = noop; +// 绕过 Flow 类型检查 +var generateComponentTrace = (noop); +var formatComponentName = (noop); + +// 在开发环境下进行处理 +{ + // 检查是否存在 console 对象 + var hasConsole = typeof console !== 'undefined'; + // 用于将连字符或下划线分隔的字符串转换为驼峰式的正则表达式 + var classifyRE = /(?:^|[-_])(\w)/g; + // 将连字符或下划线分隔的字符串转换为驼峰式的函数 + var classify = function (str) { + return str + .replace(classifyRE, function (c) { return c.toUpperCase(); }) + .replace(/[-_]/g, ''); + }; + + // 定义警告函数 + warn = function (msg, vm) { + // 获取组件的跟踪信息 + var trace = vm ? generateComponentTrace(vm) : ''; + + if (config.warnHandler) { + // 如果配置了警告处理函数,则调用该函数 + config.warnHandler.call(null, msg, vm, trace); + } else if (hasConsole && (!config.silent)) { + // 如果存在 console 且未禁止警告,则在控制台输出警告信息 + console.error(("[Vue warn]: " + msg + trace)); + } + }; + + // 定义提示函数 + tip = function (msg, vm) { + if (hasConsole && (!config.silent)) { + // 如果存在 console 且未禁止警告,则在控制台输出提示信息 + console.warn("[Vue tip]: " + msg + ( + vm ? generateComponentTrace(vm) : '' + )); + } + }; + + // 格式化组件名称的函数 + formatComponentName = function (vm, includeFile) { + if (vm.$root === vm) { + // 如果是根组件,则返回 + return ''; + } + // 获取组件的选项对象 + var options = typeof vm === 'function' && vm.cid != null + ? vm.options + : vm._isVue + ? vm.$options || vm.constructor.options + : vm; + // 获取组件的名称 + var name = options.name || options._componentTag; + // 获取组件的文件路径 + var file = options.__file; + if (!name && file) { + // 如果没有名称但有文件路径,则从文件路径中提取组件名称 + var match = file.match(/([^/\\]+)\.vue$/); + name = match && match[1]; + } + + return ( + // 格式化组件名称 + (name ? ("<" + (classify(name)) + ">") : "") + + (file && includeFile !== false ? (" at " + file) : '') + ); + }; + + // 重复字符串的函数 + var repeat = function (str, n) { + var res = ''; + while (n) { + if (n % 2 === 1) { + // 如果 n 为奇数,则将 str 添加到结果中 + res += str; + } + if (n > 1) { + // 如果 n 大于 1,则将 str 翻倍 + str += str; + } + // 将 n 右移一位 + n >>= 1; + } + return res; + }; + + // 生成组件跟踪信息的函数 + generateComponentTrace = function (vm) { + if (vm._isVue && vm.$parent) { + // 如果是 Vue 组件且有父组件 + var tree = []; + var currentRecursiveSequence = 0; + while (vm) { + if (tree.length > 0) { + var last = tree[tree.length - 1]; + if (last.constructor === vm.constructor) { + // 如果当前组件和上一个组件是同一个构造函数创建的,则增加递归次数 + currentRecursiveSequence++; + vm = vm.$parent; + continue; + } else if (currentRecursiveSequence > 0) { + // 如果递归次数大于 0,则将上一个组件和递归次数作为数组添加到 tree 中 + tree[tree.length - 1] = [last, currentRecursiveSequence]; + currentRecursiveSequence = 0; + } + } + // 将当前组件添加到 tree 中 + tree.push(vm); + // 切换到父组件 + vm = vm.$parent; + } + return '\n\nfound in\n\n' + tree + .map(function (vm, i) { + return ("" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + (Array.isArray(vm) + ? ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)") + : formatComponentName(vm))); + }) + .join('\n'); + } else { + // 如果不是 Vue 组件或没有父组件,则直接返回组件名称 + return ("\n\n(found in " + (formatComponentName(vm)) + ")"); + } + }; +} + +// 初始化一个唯一标识符 +var uid = 0; + +// Dep 类,用于实现依赖收集和发布更新 +// Dep 是一个可观察对象,可以有多个指令订阅它 +var Dep = function Dep () { + // 为每个 Dep 实例分配一个唯一的 ID + this.id = uid++; + // 存储订阅者的数组 + this.subs = []; +}; + +// 向 Dep 实例中添加订阅者 +Dep.prototype.addSub = function addSub (sub) { + this.subs.push(sub); +}; + +// 从 Dep 实例中移除订阅者 +Dep.prototype.removeSub = function removeSub (sub) { + remove(this.subs, sub); +}; + +// 触发依赖收集 +Dep.prototype.depend = function depend () { + if (Dep.target) { + // 如果存在当前目标观察者,则让其添加对当前 Dep 实例的依赖 + Dep.target.addDep(this); + } +}; + +// 通知所有订阅者进行更新 +Dep.prototype.notify = function notify () { + // 先复制一份订阅者列表,避免在更新过程中修改原列表导致问题 + var subs = this.subs.slice(); + if (!config.async) { + // 如果不是异步更新,则对订阅者列表进行排序,确保按正确顺序更新 + subs.sort(function (a, b) { return a.id - b.id; }); + } + for (var i = 0, l = subs.length; i < l; i++) { + // 调用每个订阅者的 update 方法进行更新 + subs[i].update(); + } +}; + +// 当前正在计算的目标观察者,全局唯一,因为同一时间只能有一个观察者在计算 +Dep.target = null; +// 存储目标观察者的栈 +var targetStack = []; + +// 将目标观察者压入栈中,并设置为当前目标观察者 +function pushTarget (target) { + targetStack.push(target); + Dep.target = target; +} + +// 从栈中弹出目标观察者,并更新当前目标观察者 +function popTarget () { + targetStack.pop(); + Dep.target = targetStack[targetStack.length - 1]; +} + +// VNode 类,用于表示虚拟 DOM 节点 +var VNode = function VNode ( + tag, // 节点的标签名 + data, // 节点的数据 + children, // 节点的子节点 + text, // 节点的文本内容 + elm, // 对应的真实 DOM 元素 + context, // 节点的上下文 + componentOptions, // 组件选项 + asyncFactory // 异步工厂函数 +) { + this.tag = tag; + this.data = data; + this.children = children; + this.text = text; + this.elm = elm; + this.ns = undefined; // 命名空间 + this.context = context; + this.fnContext = undefined; // 函数式组件的上下文 + this.fnOptions = undefined; // 函数式组件的选项 + this.fnScopeId = undefined; // 函数式组件的作用域 ID + this.key = data && data.key; // 节点的 key + this.componentOptions = componentOptions; + this.componentInstance = undefined; // 组件实例 + this.parent = undefined; // 父节点 + this.raw = false; // 是否为原始节点 + this.isStatic = false; // 是否为静态节点 + this.isRootInsert = true; // 是否为根插入节点 + this.isComment = false; // 是否为注释节点 + this.isCloned = false; // 是否为克隆节点 + this.isOnce = false; // 是否只渲染一次 + this.asyncFactory = asyncFactory; + this.asyncMeta = undefined; // 异步元数据 + this.isAsyncPlaceholder = false; // 是否为异步占位符 +}; + +// 定义 VNode 原型的访问器属性 +var prototypeAccessors = { child: { configurable: true } }; + +// 为了向后兼容,定义 child 访问器,返回 componentInstance +/* istanbul ignore next */ +prototypeAccessors.child.get = function () { + return this.componentInstance; +}; + +// 将访问器属性定义到 VNode 原型上 +Object.defineProperties( VNode.prototype, prototypeAccessors ); + +// 创建一个空的 VNode 节点 +var createEmptyVNode = function (text) { + if ( text === void 0 ) text = ''; + + var node = new VNode(); + node.text = text; + node.isComment = true; + return node; +}; + +// 创建一个文本 VNode 节点 +function createTextVNode (val) { + return new VNode(undefined, undefined, undefined, String(val)); +} + +// 优化后的浅克隆函数,用于静态节点和插槽节点,避免在 DOM 操作时因引用问题出错 +function cloneVNode (vnode) { + var cloned = new VNode( + vnode.tag, + vnode.data, + // 克隆子节点数组,避免在克隆子节点时修改原始数组 + vnode.children && vnode.children.slice(), + vnode.text, + vnode.elm, + vnode.context, + vnode.componentOptions, + vnode.asyncFactory + ); + cloned.ns = vnode.ns; + cloned.isStatic = vnode.isStatic; + cloned.key = vnode.key; + cloned.isComment = vnode.isComment; + cloned.fnContext = vnode.fnContext; + cloned.fnOptions = vnode.fnOptions; + cloned.fnScopeId = vnode.fnScopeId; + cloned.asyncMeta = vnode.asyncMeta; + cloned.isCloned = true; + return cloned; +} + +// 由于 Flow 对动态访问数组原型方法支持不佳,不对此文件进行类型检查 +var arrayProto = Array.prototype; +// 创建一个继承自数组原型的对象,用于重写数组方法 +var arrayMethods = Object.create(arrayProto); + +// 需要重写的数组方法 +var methodsToPatch = [ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' +]; + +// 拦截数组的变异方法并触发更新事件 +methodsToPatch.forEach(function (method) { + // 缓存原始的数组方法 + var original = arrayProto[method]; + // 在 arrayMethods 对象上定义重写后的方法 + def(arrayMethods, method, function mutator () { + // 将参数转换为数组 + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + // 调用原始方法并获取结果 + var result = original.apply(this, args); + // 获取数组的观察者实例 + var ob = this.__ob__; + var inserted; + switch (method) { + case 'push': + case 'unshift': + // 如果是 push 或 unshift 方法,插入的元素就是参数 + inserted = args; + break; + case 'splice': + // 如果是 splice 方法,插入的元素是参数从索引 2 开始的部分 + inserted = args.slice(2); + break; + } + if (inserted) { + // 对插入的元素进行观察 + ob.observeArray(inserted); + } + // 通知依赖进行更新 + ob.dep.notify(); + return result; + }); +}); + +// 获取重写后的数组方法的属性名 +var arrayKeys = Object.getOwnPropertyNames(arrayMethods); + +// 用于控制是否进行观察的标志 +var shouldObserve = true; + +// 切换观察状态的函数 +function toggleObserving (value) { + shouldObserve = value; +} + +// Observer 类,用于将对象的属性转换为响应式的 +// 该类会将目标对象的属性键转换为 getter/setter,用于收集依赖和分发更新 +var Observer = function Observer (value) { + // 保存被观察的对象 + this.value = value; + // 创建一个 Dep 实例,用于依赖收集和发布更新 + this.dep = new Dep(); + // 记录关联的 Vue 实例数量 + this.vmCount = 0; + // 在被观察的对象上定义一个不可枚举的属性 __ob__,指向当前 Observer 实例 + def(value, '__ob__', this); + if (Array.isArray(value)) { + if (hasProto) { + // 如果支持 __proto__,则通过原型链增强数组 + protoAugment(value, arrayMethods); + } else { + // 否则通过定义隐藏属性增强数组 + copyAugment(value, arrayMethods, arrayKeys); + } + // 对数组的每个元素进行观察 + this.observeArray(value); + } else { + // 如果是对象,则遍历其属性并转换为响应式 + this.walk(value); + } +}; + +// 遍历对象的所有属性并将其转换为 getter/setter +// 此方法仅在值类型为 Object 时调用 +Observer.prototype.walk = function walk (obj) { + // 获取对象的所有属性名 + var keys = Object.keys(obj); + for (var i = 0; i < keys.length; i++) { + // 将每个属性转换为响应式 + defineReactive$$1(obj, keys[i]); + } +}; + +// 观察数组中的每个元素 +Observer.prototype.observeArray = function observeArray (items) { + for (var i = 0, l = items.length; i < l; i++) { + // 对数组中的每个元素进行观察 + observe(items[i]); + } +}; + +// 通过 __proto__ 拦截原型链来增强目标对象或数组 +function protoAugment (target, src) { + // 禁用 no-proto 规则 + /* eslint-disable no-proto */ + // 将目标对象的原型指向源对象 + target.__proto__ = src; + // 启用 no-proto 规则 + /* eslint-enable no-proto */ +} + +// 通过定义隐藏属性来增强目标对象或数组 +/* istanbul ignore next */ +function copyAugment (target, src, keys) { + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + // 在目标对象上定义属性,值为源对象的对应属性 + def(target, key, src[key]); + } +} + +// 尝试为一个值创建一个观察者实例 +// 如果成功观察,则返回新的观察者;如果该值已经有观察者,则返回现有的观察者 +function observe (value, asRootData) { + if (!isObject(value) || value instanceof VNode) { + // 如果值不是对象或者是 VNode 实例,则不进行观察 + return; + } + var ob; + if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { + // 如果值已经有观察者,则直接使用现有的观察者 + ob = value.__ob__; + } else if ( + shouldObserve && + !isServerRendering() && + (Array.isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value._isVue + ) { + // 如果满足观察条件,则创建一个新的观察者 + ob = new Observer(value); + } + if (asRootData && ob) { + // 如果作为根数据且有观察者,则增加关联的 Vue 实例数量 + ob.vmCount++; + } + return ob; +} + +// 在对象上定义一个响应式属性 +function defineReactive$$1 ( + obj, // 目标对象 + key, // 属性名 + val, // 属性值 + customSetter, // 自定义的 setter 函数 + shallow // 是否进行浅观察 +) { + // 创建一个 Dep 实例,用于依赖收集和发布更新 + var dep = new Dep(); + + // 获取对象上该属性的描述符 + var property = Object.getOwnPropertyDescriptor(obj, key); + if (property && property.configurable === false) { + // 如果属性不可配置,则直接返回 + return; + } + + // 获取原有的 getter 和 setter 函数 + var getter = property && property.get; + var setter = property && property.set; + if ((!getter || setter) && arguments.length === 2) { + // 如果没有 getter 或者有 setter 且只传入了两个参数,则获取对象上该属性的当前值 + val = obj[key]; + } + + // 如果不是浅观察,则对属性值进行观察 + var childOb = !shallow && observe(val); + // 使用 Object.defineProperty 定义属性的 getter 和 setter + Object.defineProperty(obj, key, { + enumerable: true, // 属性可枚举 + configurable: true, // 属性可配置 + get: function reactiveGetter () { + // 如果有原有的 getter 函数,则调用它获取值,否则使用传入的 val + var value = getter ? getter.call(obj) : val; + if (Dep.target) { + // 如果存在当前目标观察者,则让其添加对当前 Dep 实例的依赖 + dep.depend(); + if (childOb) { + // 如果有子观察者,则让当前目标观察者添加对子观察者的依赖 + childOb.dep.depend(); + if (Array.isArray(value)) { + // 如果值是数组,则对数组元素进行依赖收集 + dependArray(value); + } + } + } + return value; + }, + // 定义响应式属性的 setter 函数 + set: function reactiveSetter (newVal) { + // 获取当前属性的值 + var value = getter ? getter.call(obj) : val; + // 禁用 no-self-compare 规则,用于处理 NaN 的情况 + /* eslint-disable no-self-compare */ + // 检查新值和旧值是否相等,考虑 NaN 的情况 + if (newVal === value || (newVal !== newVal && value !== value)) { + return; + } + // 启用 no-self-compare 规则 + /* eslint-enable no-self-compare */ + // 如果存在自定义的 setter 函数,则调用它 + if (customSetter) { + customSetter(); + } + // 对于只有 getter 没有 setter 的访问器属性,直接返回 + if (getter && !setter) { return; } + if (setter) { + // 如果存在原有的 setter 函数,则调用它设置新值 + setter.call(obj, newVal); + } else { + // 否则直接更新局部变量 val 的值 + val = newVal; + } + // 如果不是浅观察,则对新值进行观察 + childOb = !shallow && observe(newVal); + // 通知所有依赖该属性的观察者进行更新 + dep.notify(); + }, + +// 在对象上设置一个属性,如果属性不存在则添加该属性并触发变更通知 + function set (target, key, val) { + // 检查目标对象是否为 undefined、null 或原始类型,如果是则发出警告 + if (isUndef(target) || isPrimitive(target)) { + warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + // 如果目标是数组且键是有效的数组索引,则更新数组长度并插入新值 + target.length = Math.max(target.length, key); + target.splice(key, 1, val); + return val; + } + if (key in target && !(key in Object.prototype)) { + // 如果键已经存在于目标对象中且不是原型链上的属性,则直接更新该属性的值 + target[key] = val; + return val; + } + // 获取目标对象的观察者实例 + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + // 如果目标是 Vue 实例或其根数据,发出警告并返回值 + warn( + 'Avoid adding reactive properties to a Vue instance or its root $data ' + + 'at runtime - declare it upfront in the data option.' + ); + return val; + } + if (!ob) { + // 如果目标对象没有观察者,则直接设置属性值 + target[key] = val; + return val; + } + // 定义该属性为响应式属性 + defineReactive$$1(ob.value, key, val); + // 通知观察者更新 + ob.dep.notify(); + return val; + } + +// 删除一个属性,如果必要则触发变更通知 + function del (target, key) { + // 检查目标对象是否为 undefined、null 或原始类型,如果是则发出警告 + if (isUndef(target) || isPrimitive(target)) { + warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + // 如果目标是数组且键是有效的数组索引,则从数组中移除该元素 + target.splice(key, 1); + return; + } + // 获取目标对象的观察者实例 + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + // 如果目标是 Vue 实例或其根数据,发出警告并返回 + warn( + 'Avoid deleting properties on a Vue instance or its root $data ' + + '- just set it to null.' + ); + return; + } + if (!hasOwn(target, key)) { + // 如果目标对象没有该属性,则直接返回 + return; + } + // 删除目标对象的该属性 + delete target[key]; + if (!ob) { + // 如果目标对象没有观察者,则直接返回 + return; + } + // 通知观察者更新 + ob.dep.notify(); + } + +// 当数组被访问时,收集数组元素的依赖,因为无法像属性 getter 那样拦截数组元素的访问 + function dependArray (value) { + for (var e = (void 0), i = 0, l = value.length; i < l; i++) { + // 获取数组元素 + e = value[i]; + // 如果元素有观察者,则收集其依赖 + e && e.__ob__ && e.__ob__.dep.depend(); + if (Array.isArray(e)) { + // 如果元素是数组,则递归调用 dependArray 函数收集依赖 + dependArray(e); + } + } + } + +// 选项合并策略是处理如何将父选项值和子选项值合并为最终值的函数 + var strats = config.optionMergeStrategies; + +// 有使用限制的选项处理 + { + // 处理 el 和 propsData 选项的合并策略 + strats.el = strats.propsData = function (parent, child, vm, key) { + if (!vm) { + // 如果没有 Vue 实例,发出警告 + warn( + "option \"" + key + "\" can only be used during instance " + + 'creation with the `new` keyword.' + ); + } + // 使用默认合并策略返回结果 + return defaultStrat(parent, child); + }; + } + +// 递归合并两个数据对象的辅助函数 + function mergeData (to, from) { + if (!from) { + // 如果没有源对象,则直接返回目标对象 + return to; + } + var key, toVal, fromVal; + // 根据是否支持 Symbol 获取源对象的所有键 + var keys = hasSymbol + ? Reflect.ownKeys(from) + : Object.keys(from); + + for (var i = 0; i < keys.length; i++) { + // 获取当前键 + key = keys[i]; + // 跳过 __ob__ 属性 + if (key === '__ob__') { continue; } + // 获取目标对象和源对象上该键对应的值 + toVal = to[key]; + fromVal = from[key]; + if (!hasOwn(to, key)) { + // 如果目标对象没有该键,则使用 set 函数设置该键的值 + set(to, key, fromVal); + } else if ( + toVal !== fromVal && + isPlainObject(toVal) && + isPlainObject(fromVal) + ) { + // 如果目标对象和源对象上该键的值不相等,且都是普通对象,则递归合并 + mergeData(toVal, fromVal); + } + } + return to; + } + +// 合并数据函数或数据对象 + function mergeDataOrFn ( + parentVal, // 父数据值 + childVal, // 子数据值 + vm // Vue 实例 + ) { + if (!vm) { + // 在 Vue.extend 合并中,两者都应该是函数 + if (!childVal) { + // 如果没有子数据值,则返回父数据值 + return parentVal; + } + if (!parentVal) { + // 如果没有父数据值,则返回子数据值 + return childVal; + } + // 当父数据值和子数据值都存在时,返回一个函数,该函数返回两者合并的结果 + return function mergedDataFn () { + return mergeData( + typeof childVal === 'function' ? childVal.call(this, this) : childVal, + typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal + ); + }; + } else { + return function mergedInstanceDataFn () { + // 实例合并 + var instanceData = typeof childVal === 'function' + ? childVal.call(vm, vm) + : childVal; + var defaultData = typeof parentVal === 'function' + ? parentVal.call(vm, vm) + : parentVal; + if (instanceData) { + // 如果有实例数据,则合并实例数据和默认数据 + return mergeData(instanceData, defaultData); + } else { + // 否则返回默认数据 + return defaultData; + } + }; + } + } + +// 合并 data 选项的策略 + strats.data = function ( + parentVal, // 父级 data 选项值 + childVal, // 子级 data 选项值 + vm // Vue 实例 + ) { + if (!vm) { + if (childVal && typeof childVal !== 'function') { + // 如果子级 data 选项存在但不是函数,发出警告并返回父级 data 选项值 + warn( + 'The "data" option should be a function ' + + 'that returns a per-instance value in component ' + + 'definitions.', + vm + ); + return parentVal; + } + // 合并 data 选项 + return mergeDataOrFn(parentVal, childVal); + } + // 合并 data 选项并传入 Vue 实例 + return mergeDataOrFn(parentVal, childVal, vm); + }; + +// 生命周期钩子和 props 以数组形式合并 + function mergeHook ( + parentVal, // 父级钩子值 + childVal // 子级钩子值 + ) { + var res = childVal + ? parentVal + ? parentVal.concat(childVal) + : Array.isArray(childVal) + ? childVal + : [childVal] + : parentVal; + return res + ? dedupeHooks(res) + : res; + } + +// 去除钩子数组中的重复项 + function dedupeHooks (hooks) { + var res = []; + for (var i = 0; i < hooks.length; i++) { + if (res.indexOf(hooks[i]) === -1) { + // 如果结果数组中不存在该钩子,则添加到结果数组中 + res.push(hooks[i]); + } + } + return res; + } + +// 为每个生命周期钩子设置合并策略 + LIFECYCLE_HOOKS.forEach(function (hook) { + strats[hook] = mergeHook; + }); + +// 合并资产选项(如组件、指令、过滤器) + function mergeAssets ( + parentVal, // 父级资产选项值 + childVal, // 子级资产选项值 + vm, // Vue 实例 + key // 选项键名 + ) { + // 创建一个继承自父级资产选项的对象 + var res = Object.create(parentVal || null); + if (childVal) { + // 验证子级资产选项是否为对象类型 + assertObjectType(key, childVal, vm); + // 将子级资产选项合并到结果对象中 + return extend(res, childVal); + } else { + // 如果没有子级资产选项,则返回结果对象 + return res; + } + } + +// 为每个资产类型设置合并策略 + ASSET_TYPES.forEach(function (type) { + strats[type + 's'] = mergeAssets; + }); + +// 合并 watch 选项 + strats.watch = function ( + parentVal, // 父级 watch 选项值 + childVal, // 子级 watch 选项值 + vm, // Vue 实例 + key // 选项键名 + ) { + // 处理 Firefox 的 Object.prototype.watch + if (parentVal === nativeWatch) { parentVal = undefined; } + if (childVal === nativeWatch) { childVal = undefined; } + // 如果没有子级 watch 选项,则返回一个继承自父级 watch 选项的对象 + if (!childVal) { return Object.create(parentVal || null); } + { + // 验证子级 watch 选项是否为对象类型 + assertObjectType(key, childVal, vm); + } + if (!parentVal) { + // 如果没有父级 watch 选项,则返回子级 watch 选项 + return childVal; + } + var ret = {}; + // 将父级 watch 选项合并到结果对象中 + extend(ret, parentVal); + for (var key$1 in childVal) { + var parent = ret[key$1]; + var child = childVal[key$1]; + if (parent && !Array.isArray(parent)) { + // 如果父级 watch 选项不是数组,则将其转换为数组 + parent = [parent]; + } + ret[key$1] = parent + ? parent.concat(child) + : Array.isArray(child) ? child : [child]; + } + return ret; + } + +// 合并其他对象类型的选项(如 props、methods、inject、computed) + strats.props = + strats.methods = + strats.inject = + strats.computed = function ( + parentVal, // 父级选项值 + childVal, // 子级选项值 + vm, // Vue 实例 + key // 选项键名 + ) { + if (childVal && "development" !== 'production') { + // 在开发环境下,验证子级选项是否为对象类型 + assertObjectType(key, childVal, vm); + } + if (!parentVal) { + // 如果没有父级选项,则返回子级选项 + return childVal; + } + var ret = Object.create(null); + // 将父级选项合并到结果对象中 + extend(ret, parentVal); + if (childVal) { + // 如果有子级选项,则将其合并到结果对象中 + extend(ret, childVal); + } + return ret; + } +// 合并 provide 选项的策略 + strats.provide = mergeDataOrFn; + +// 默认合并策略,如果子选项值未定义,则返回父选项值,否则返回子选项值 + var defaultStrat = function (parentVal, childVal) { + return childVal === undefined + ? parentVal + : childVal; + } + +// 验证组件名称 + function checkComponents (options) { + for (var key in options.components) { + // 对每个组件名称进行验证 + validateComponentName(key); + } + } + +// 验证组件名称是否合法 + function validateComponentName (name) { + if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) { + // 如果组件名称不符合正则表达式,发出警告 + warn( + 'Invalid component name: "' + name + '". Component names ' + + 'should conform to valid custom element name in html5 specification.' + ); + } + if (isBuiltInTag(name) || config.isReservedTag(name)) { + // 如果组件名称是内置标签或保留标签,发出警告 + warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + name + ); + } + } + +// 确保所有 props 选项语法都被规范化为基于对象的格式。 + function normalizeProps (options, vm) { + // 获取 options 中的 props 选项 + var props = options.props; + // 如果 props 选项不存在,直接返回 + if (!props) { return } + // 用于存储规范化后的 props + var res = {}; + var i, val, name; + // 如果 props 是数组形式 + if (Array.isArray(props)) { + // 获取数组长度 + i = props.length; + // 从后往前遍历数组 + while (i--) { + // 获取当前元素 + val = props[i]; + if (typeof val === 'string') { + // 将字符串转换为驼峰命名 + name = camelize(val); + // 规范化为对象格式,类型为 null + res[name] = { type: null }; + } else { + // 发出警告,数组形式的 props 必须是字符串 + warn('props must be strings when using array syntax.'); + } + } + } else if (isPlainObject(props)) { + // 如果 props 是对象形式 + for (var key in props) { + // 获取当前属性值 + val = props[key]; + // 将键名转换为驼峰命名 + name = camelize(key); + res[name] = isPlainObject(val) + ? val + // 如果不是对象,将其包装为对象,指定类型 + : { type: val }; + } + } else { + // 发出警告,props 选项的值必须是数组或对象 + warn( + "Invalid value for option \"props\": expected an Array or an Object, " + + "but got " + (toRawType(props)) + ".", + vm + ); + } + // 将规范化后的 props 赋值给 options.props + options.props = res; + } + +// 将所有注入项规范化为基于对象的格式。 + function normalizeInject (options, vm) { + // 获取 options 中的 inject 选项 + var inject = options.inject; + // 如果 inject 选项不存在,直接返回 + if (!inject) { return } + // 用于存储规范化后的 inject + var normalized = options.inject = {}; + if (Array.isArray(inject)) { + // 如果 inject 是数组形式 + for (var i = 0; i < inject.length; i++) { + // 规范化为对象格式,指定 from 属性 + normalized[inject[i]] = { from: inject[i] }; + } + } else if (isPlainObject(inject)) { + // 如果 inject 是对象形式 + for (var key in inject) { + // 获取当前属性值 + var val = inject[key]; + normalized[key] = isPlainObject(val) + // 如果是对象,合并 from 属性 + ? extend({ from: key }, val) + // 否则,指定 from 属性 + : { from: val }; + } + } else { + // 发出警告,inject 选项的值必须是数组或对象 + warn( + "Invalid value for option \"inject\": expected an Array or an Object, " + + "but got " + (toRawType(inject)) + ".", + vm + ); + } + } + +// 将原始的函数指令规范化为对象格式。 + function normalizeDirectives (options) { + // 获取 options 中的 directives 选项 + var dirs = options.directives; + if (dirs) { + // 如果 directives 选项存在 + for (var key in dirs) { + // 获取当前指令定义 + var def$$1 = dirs[key]; + if (typeof def$$1 === 'function') { + // 如果是函数,将其转换为对象格式,指定 bind 和 update 方法 + dirs[key] = { bind: def$$1, update: def$$1 }; + } + } + } + } + +// 断言值是否为纯对象,如果不是则发出警告 + function assertObjectType (name, value, vm) { + if (!isPlainObject(value)) { + warn( + "Invalid value for option \"" + name + "\": expected an Object, " + + "but got " + (toRawType(value)) + ".", + vm + ); + } + } + +// 将两个选项对象合并为一个新对象。 +// 这是实例化和继承过程中使用的核心工具函数。 + function mergeOptions ( + parent, // 父选项对象 + child, // 子选项对象 + vm // Vue 实例 + ) { + // 检查子选项中的组件名称是否合法 + checkComponents(child); + + if (typeof child === 'function') { + // 如果子选项是函数,获取其 options 属性 + child = child.options; + } + + // 规范化子选项中的 props + normalizeProps(child, vm); + // 规范化子选项中的 inject + normalizeInject(child, vm); + // 规范化子选项中的 directives + normalizeDirectives(child); + + // 仅当子选项是原始选项对象(不是另一次 mergeOptions 调用的结果)时,应用 extends 和 mixins + // 只有合并后的选项才有 _base 属性 + if (!child._base) { + if (child.extends) { + // 递归合并 extends 选项 + parent = mergeOptions(parent, child.extends, vm); + } + if (child.mixins) { + // 遍历 mixins 数组,递归合并每个 mixin + for (var i = 0, l = child.mixins.length; i < l; i++) { + parent = mergeOptions(parent, child.mixins[i], vm); + } + } + } + + // 用于存储合并后的选项 + var options = {}; + var key; + // 遍历父选项的所有键 + for (key in parent) { + // 合并当前键对应的选项 + mergeField(key); + } + // 遍历子选项的所有键 + for (key in child) { + if (!hasOwn(parent, key)) { + // 如果父选项中不存在该键,合并该键对应的选项 + mergeField(key); + } + } + // 合并单个字段的函数 + function mergeField (key) { + // 获取该字段的合并策略,若不存在则使用默认策略 + var strat = strats[key] || defaultStrat; + // 执行合并策略,将结果赋值给合并后的选项 + options[key] = strat(parent[key], child[key], vm, key); + } + // 返回合并后的选项 + return options + } + +// 解析一个资产(如组件、指令、过滤器等)。 +// 该函数用于子实例访问其祖先链中定义的资产。 + function resolveAsset ( + options, // 选项对象 + type, // 资产类型(如 'components', 'directives', 'filters') + id, // 资产 ID + warnMissing // 是否在未找到资产时发出警告 + ) { + if (typeof id !== 'string') { + // 如果 ID 不是字符串,直接返回 + return + } + // 获取该类型的资产对象 + var assets = options[type]; + // 先检查本地注册的变体 + if (hasOwn(assets, id)) { return assets[id] } + // 将 ID 转换为驼峰命名 + var camelizedId = camelize(id); + if (hasOwn(assets, camelizedId)) { return assets[camelizedId] } + // 将驼峰命名的 ID 首字母大写 + var PascalCaseId = capitalize(camelizedId); + if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] } + // 回退到原型链查找 + var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; + if (warnMissing && !res) { + // 如果需要警告且未找到资产,发出警告 + warn( + 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, + options + ); + } + return res + } + +// 验证一个 prop 的值是否合法。 + function validateProp ( + key, // prop 的键名 + propOptions, // prop 的选项定义 + propsData, // props 的数据 + vm // Vue 实例 + ) { + // 获取该 prop 的选项定义 + var prop = propOptions[key]; + // 判断该 prop 是否未在 propsData 中提供 + var absent = !hasOwn(propsData, key); + // 获取该 prop 的值 + var value = propsData[key]; + // 布尔值转换 + var booleanIndex = getTypeIndex(Boolean, prop.type); + if (booleanIndex > -1) { + if (absent && !hasOwn(prop, 'default')) { + // 如果未提供且没有默认值,将值设为 false + value = false; + } else if (value === '' || value === hyphenate(key)) { + // 仅当布尔类型优先级高于字符串类型时,将空字符串或同名值转换为布尔值 + var stringIndex = getTypeIndex(String, prop.type); + if (stringIndex < 0 || booleanIndex < stringIndex) { + value = true; + } + } + } + // 检查默认值 + if (value === undefined) { + // 获取该 prop 的默认值 + value = getPropDefaultValue(vm, prop, key); + // 由于默认值是一个新副本,确保对其进行观察 + var prevShouldObserve = shouldObserve; + toggleObserving(true); + observe(value); + toggleObserving(prevShouldObserve); + } + // 断言该 prop 的值是否合法 + assertProp(prop, key, value, vm, absent); + return value + } + +// 获取一个 prop 的默认值。 + function getPropDefaultValue (vm, prop, key) { + // 如果没有默认值,返回 undefined + if (!hasOwn(prop, 'default')) { + return undefined + } + // 获取默认值 + var def = prop.default; + // 警告对象和数组类型的非工厂函数默认值 + if (isObject(def)) { + warn( + 'Invalid default value for prop "' + key + '": ' + + 'Props with type Object/Array must use a factory function ' + + 'to return the default value.', + vm + ); + } + // 如果之前渲染时该 prop 的原始值也是 undefined,返回之前的默认值以避免不必要的 watcher 触发 + if (vm && vm.$options.propsData && + vm.$options.propsData[key] === undefined && + vm._props[key] !== undefined + ) { + return vm._props[key] + } + // 对于非函数类型,调用工厂函数获取默认值 + // 如果一个值的原型是函数,即使在不同的执行上下文中,它也是函数 + return typeof def === 'function' && getType(prop.type) !== 'Function' + ? def.call(vm) + : def + } + +// 断言一个 prop 是否合法。 + function assertProp ( + prop, // prop 的选项定义 + name, // prop 的名称 + value, // prop 的值 + vm, // Vue 实例 + absent // 该 prop 是否未提供 + ) { + if (prop.required && absent) { + // 如果 prop 是必需的但未提供,发出警告 + warn( + 'Missing required prop: "' + name + '"', + vm + ); + return + } + if (value == null && !prop.required) { + // 如果值为 null 或 undefined 且 prop 不是必需的,直接返回 + return + } + // 获取 prop 的类型定义 + var type = prop.type; + // 初始化合法性标志 + var valid = !type || type === true; + // 存储期望的类型 + var expectedTypes = []; + if (type) { + if (!Array.isArray(type)) { + // 如果类型不是数组,将其转换为数组 + type = [type]; + } + // 遍历类型数组,检查值是否符合类型要求 + for (var i = 0; i < type.length && !valid; i++) { + var assertedType = assertType(value, type[i]); + // 存储期望的类型 + expectedTypes.push(assertedType.expectedType || ''); + // 更新合法性标志 + valid = assertedType.valid; + } + } + + if (!valid) { + // 如果值不合法,发出警告 + warn( + getInvalidTypeMessage(name, value, expectedTypes), + vm + ); + return + } + // 获取自定义验证器 + var validator = prop.validator; + if (validator) { + if (!validator(value)) { + // 如果自定义验证器检查失败,发出警告 + warn( + 'Invalid prop: custom validator check failed for prop "' + name + '".', + vm + ); + } + } + } + +// 简单类型检查的正则表达式 + var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/; + +// 断言值的类型是否符合期望类型 + function assertType (value, type) { + var valid; + // 获取期望类型的名称 + var expectedType = getType(type); + if (simpleCheckRE.test(expectedType)) { + // 如果是简单类型 + var t = typeof value; + // 检查基本类型是否匹配 + valid = t === expectedType.toLowerCase(); + // 处理原始包装对象 + if (!valid && t === 'object') { + valid = value instanceof type; + } + } else if (expectedType === 'Object') { + // 如果期望类型是对象,检查是否为纯对象 + valid = isPlainObject(value); + } else if (expectedType === 'Array') { + // 如果期望类型是数组,检查是否为数组 + valid = Array.isArray(value); + } else { + // 其他情况,检查是否为该类型的实例 + valid = value instanceof type; + } + return { + valid: valid, + expectedType: expectedType + } + } + +// 使用函数的字符串名称来检查内置类型, +// 因为在不同的 vm 或 iframe 中运行时,简单的相等检查会失败。 + function getType (fn) { + // 匹配函数名 + var match = fn && fn.toString().match(/^\s*function (\w+)/); + return match ? match[1] : '' + } + +// 判断两个类型是否相同 + function isSameType (a, b) { + return getType(a) === getType(b) + } + +// 获取类型在期望类型数组中的索引 + function getTypeIndex (type, expectedTypes) { + if (!Array.isArray(expectedTypes)) { + return isSameType(expectedTypes, type) ? 0 : -1 + } + for (var i = 0, len = expectedTypes.length; i < len; i++) { + if (isSameType(expectedTypes[i], type)) { + return i + } + } + return -1 + } + +// 获取无效类型的错误消息 + function getInvalidTypeMessage (name, value, expectedTypes) { + var message = "Invalid prop: type check failed for prop \"" + name + "\"." + + " Expected " + (expectedTypes.map(capitalize).join(', ')); + var expectedType = expectedTypes[0]; + var receivedType = toRawType(value); + var expectedValue = styleValue(value, expectedType); + var receivedValue = styleValue(value, receivedType); + // 检查是否需要指定期望的值 + if (expectedTypes.length === 1 && + isExplicable(expectedType) && + !isBoolean(expectedType, receivedType)) { + message += " with value " + expectedValue; + } + message += ", got " + receivedType + " "; + // 检查是否需要指定接收到的值 + if (isExplicable(receivedType)) { + message += "with value " + receivedValue + "."; + } + return message + } + +// 格式化值以显示类型 + function styleValue (value, type) { + if (type === 'String') { + return ("\"" + value + "\"") + } else if (type === 'Number') { + return ("" + (Number(value))) + } else { + return ("" + value) + } + } + +// 判断值是否可以明确表示 + function isExplicable (value) { + var explicitTypes = ['string', 'number', 'boolean']; + return explicitTypes.some(function (elem) { return value.toLowerCase() === elem; }) + } + +// 判断参数中是否包含布尔类型 + function isBoolean () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return args.some(function (elem) { return elem.toLowerCase() === 'boolean'; }) + } + +// 处理错误。 + function handleError (err, vm, info) { + // 在处理错误处理程序时停用依赖跟踪,以避免可能的无限渲染。 + // 参考:https://github.com/vuejs/vuex/issues/1505 + pushTarget(); + try { + if (vm) { + var cur = vm; + // 遍历父实例链 + while ((cur = cur.$parent)) { + // 获取父实例的 errorCaptured 钩子 + var hooks = cur.$options.errorCaptured; + if (hooks) { + // 遍历 errorCaptured 钩子数组 + for (var i = 0; i < hooks.length; i++) { + try { + // 调用钩子函数,若返回 false 则停止捕获 + var capture = hooks[i].call(cur, err, vm, info) === false; + if (capture) { return } + } catch (e) { + // 处理钩子函数内部的错误 + globalHandleError(e, cur, 'errorCaptured hook'); + } + } + } + } + } + // 全局处理错误 + globalHandleError(err, vm, info); + } finally { + // 恢复依赖跟踪 + popTarget(); + } + } + +// 调用带有错误处理的处理程序。 + function invokeWithErrorHandling ( + handler, // 处理程序函数 + context, // 处理程序的上下文 + args, // 传递给处理程序的参数 + vm, // Vue 实例 + info // 错误信息 + ) { + var res; + try { + // 调用处理程序 + res = args ? handler.apply(context, args) : handler.call(context); + if (res && !res._isVue && isPromise(res) && !res._handled) { + // 如果结果是 Promise 且未处理过,捕获错误并处理 + res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); }); + // 标记为已处理,避免多次捕获 + res._handled = true; + } + } catch (e) { + // 处理调用过程中的错误 + handleError(e, vm, info); + } + return res + } + +// 全局处理错误。 + function globalHandleError (err, vm, info) { + if (config.errorHandler) { + try { + // 调用用户配置的错误处理程序 + return config.errorHandler.call(null, err, vm, info) + } catch (e) { + // 如果用户在处理程序中故意抛出原始错误,不重复记录 + if (e !== err) { + logError(e, null, 'config.errorHandler'); + } + } + } + // 记录错误 + logError(err, vm, info); + } + +// 记录错误。 + function logError (err, vm, info) { + // 发出警告信息 + warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm); + if ((inBrowser || inWeex) && typeof console !== 'undefined') { + // 在浏览器或 Weex 环境中,使用 console.error 输出错误 + console.error(err); + } else { + // 其他环境抛出错误 + throw err + } + } + +// 标记是否使用微任务 + var isUsingMicroTask = false; + +// 存储回调函数的数组 + var callbacks = []; +// 标记是否有回调函数正在等待执行 + var pending = false; + +// 执行所有回调函数 + function flushCallbacks () { + // 标记为不再有回调函数等待执行 + pending = false; + // 复制回调函数数组 + var copies = callbacks.slice(0); + // 清空回调函数数组 + callbacks.length = 0; + // 遍历并执行所有回调函数 + for (var i = 0; i < copies.length; i++) { + copies[i](); + } + } + +// 这里我们使用微任务进行异步延迟包装。 +// 在 2.5 版本中,我们使用(宏)任务(结合微任务)。 +// 然而,当在重绘之前更改状态时,这会有一些微妙的问题 +// (例如 #6813,out-in 过渡效果)。 +// 此外,在事件处理程序中使用(宏)任务会导致一些无法避免的奇怪行为 +// (例如 #7109、#7153、#7546、#7834、#8109)。 +// 所以现在我们再次在所有地方使用微任务。 +// 这种权衡的一个主要缺点是,在某些情况下 +// 微任务的优先级太高,会在本应顺序执行的事件之间触发 +// (例如 #4521、#6690,这些都有解决方法) +// 甚至在同一事件的冒泡过程中触发(#6566)。 + var timerFunc; + +// nextTick 行为利用了微任务队列,可以通过原生 Promise.then 或 MutationObserver 访问。 +// MutationObserver 的支持范围更广,但在 iOS >= 9.3.3 的 UIWebView 中,当在触摸事件处理程序中触发时,它存在严重的 bug。 +// 触发几次后,它会完全停止工作……所以,如果原生 Promise 可用,我们将使用它: + if (typeof Promise !== 'undefined' && isNative(Promise)) { + var p = Promise.resolve(); + timerFunc = function () { + // 使用 Promise.then 异步执行回调函数 + p.then(flushCallbacks); + // 在有问题的 UIWebViews 中,Promise.then 不会完全崩溃,但 + // 它可能会陷入一种奇怪的状态,即回调被推入微任务队列,但队列没有被刷新,直到浏览器 + // 需要做一些其他工作,例如处理定时器。因此,我们可以 + // 通过添加一个空定时器来“强制”刷新微任务队列。 + if (isIOS) { setTimeout(noop); } + }; + // 标记为使用微任务 + isUsingMicroTask = true; + } else if (!isIE && typeof MutationObserver !== 'undefined' && ( + isNative(MutationObserver) || + // PhantomJS 和 iOS 7.x + MutationObserver.toString() === '[object MutationObserverConstructor]' + )) { + // 在原生 Promise 不可用时使用 MutationObserver, + // 例如 PhantomJS、iOS7、Android 4.4 + // (#6466 MutationObserver 在 IE11 中不可靠) + var counter = 1; + var observer = new MutationObserver(flushCallbacks); + var textNode = document.createTextNode(String(counter)); + observer.observe(textNode, { + characterData: true + }); + timerFunc = function () { + counter = (counter + 1) % 2; + textNode.data = String(counter); + }; + // 标记为使用微任务 + isUsingMicroTask = true; + } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { + // 回退到 setImmediate。 + // 从技术上讲,它利用了(宏)任务队列, + // 但它仍然比 setTimeout 更好。 + timerFunc = function () { + setImmediate(flushCallbacks); + }; + } else { + // 回退到 setTimeout。 + timerFunc = function () { + setTimeout(flushCallbacks, 0); + }; + } + + // 定义 nextTick 函数,用于在下次 DOM 更新循环结束之后执行延迟回调 + function nextTick (cb, ctx) { + // 定义 _resolve 变量,用于存储 Promise 的 resolve 函数 + var _resolve; + // 将回调函数添加到 callbacks 数组中 + callbacks.push(function () { + // 如果传入了回调函数 + if (cb) { + try { + // 执行回调函数,并绑定上下文 + cb.call(ctx); + } catch (e) { + // 处理回调函数执行过程中的错误 + handleError(e, ctx, 'nextTick'); + } + } else if (_resolve) { + // 如果没有传入回调函数,但有 _resolve 变量,调用 _resolve 并传入上下文 + _resolve(ctx); + } + }); + // 如果没有正在等待执行的回调函数 + if (!pending) { + // 标记为有回调函数正在等待执行 + pending = true; + // 调用 timerFunc 函数,异步执行回调函数 + timerFunc(); + } + // $flow-disable-line + // 如果没有传入回调函数,并且支持 Promise + if (!cb && typeof Promise !== 'undefined') { + // 返回一个 Promise 对象 + return new Promise(function (resolve) { + // 将 resolve 函数赋值给 _resolve 变量 + _resolve = resolve; + }) + } + } + +// 定义 mark 和 measure 变量,用于性能标记和测量 + var mark; + var measure; + +// 在开发环境下执行以下代码 + { + // 检查是否在浏览器环境中,并且支持 window.performance + var perf = inBrowser && window.performance; + // 如果支持性能标记和测量相关的方法 + if ( + perf && + perf.mark && + perf.measure && + perf.clearMarks && + perf.clearMeasures + ) { + // 定义 mark 函数,用于创建性能标记 + mark = function (tag) { return perf.mark(tag); }; + // 定义 measure 函数,用于测量两个标记之间的性能 + measure = function (name, startTag, endTag) { + // 测量两个标记之间的性能 + perf.measure(name, startTag, endTag); + // 清除开始标记 + perf.clearMarks(startTag); + // 清除结束标记 + perf.clearMarks(endTag); + // perf.clearMeasures(name) + }; + } + } + +// 不对此文件进行类型检查,因为 Flow 与 Proxy 不兼容 + var initProxy; + +// 在开发环境下执行以下代码 + { + // 定义一个允许的全局变量的映射 + var allowedGlobals = makeMap( + 'Infinity,undefined,NaN,isFinite,isNaN,' + + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + + 'require' // for Webpack/Browserify + ); + + // 定义一个警告函数,用于提示属性或方法未定义 + var warnNonPresent = function (target, key) { + warn( + "Property or method \"" + key + "\" is not defined on the instance but " + + 'referenced during render. Make sure that this property is reactive, ' + + 'either in the data option, or for class-based components, by ' + + 'initializing the property. ' + + 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', + target + ); + }; + + // 定义一个警告函数,用于提示以 $ 或 _ 开头的属性需要通过 $data 访问 + var warnReservedPrefix = function (target, key) { + warn( + "Property \"" + key + "\" must be accessed with \"$data." + key + "\" because " + + 'properties starting with "$" or "_" are not proxied in the Vue instance to ' + + 'prevent conflicts with Vue internals. ' + + 'See: https://vuejs.org/v2/api/#data', + target + ); + }; + + // 检查是否支持 Proxy + var hasProxy = + typeof Proxy !== 'undefined' && isNative(Proxy); + + // 如果支持 Proxy + if (hasProxy) { + // 定义一个内置修饰符的映射 + var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact'); + // 使用 Proxy 对 config.keyCodes 进行拦截 + config.keyCodes = new Proxy(config.keyCodes, { + // 拦截设置属性的操作 + set: function set (target, key, value) { + // 如果是内置修饰符 + if (isBuiltInModifier(key)) { + // 发出警告,避免覆盖内置修饰符 + warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key)); + return false + } else { + // 设置属性值 + target[key] = value; + return true + } + } + }); + } + + // 定义 has 拦截器的处理函数 + var hasHandler = { + // 拦截 in 操作符 + has: function has (target, key) { + // 检查对象是否有该属性 + var has = key in target; + // 检查是否是允许的全局变量,或者是以 _ 开头且不在 $data 中的属性 + var isAllowed = allowedGlobals(key) || + (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)); + // 如果对象没有该属性且不是允许的属性 + if (!has && !isAllowed) { + // 如果该属性在 $data 中,提示需要通过 $data 访问 + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + // 返回对象是否有该属性或是否是允许的属性 + return has || !isAllowed + } + }; + + // 定义 get 拦截器的处理函数 + var getHandler = { + // 拦截属性访问操作 + get: function get (target, key) { + // 如果属性名是字符串且对象没有该属性 + if (typeof key === 'string' && !(key in target)) { + // 如果该属性在 $data 中,提示需要通过 $data 访问 + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + // 返回属性值 + return target[key] + } + }; + + // 定义 initProxy 函数,用于初始化代理 + initProxy = function initProxy (vm) { + // 如果支持 Proxy + if (hasProxy) { + // 确定使用哪个代理处理程序 + var options = vm.$options; + var handlers = options.render && options.render._withStripped + ? getHandler + : hasHandler; + // 创建代理对象 + vm._renderProxy = new Proxy(vm, handlers); + } else { + // 如果不支持 Proxy,直接使用原对象 + vm._renderProxy = vm; + } + }; + } + +// 定义一个 Set 对象,用于存储已访问过的对象的依赖 ID + var seenObjects = new _Set(); + +// 递归遍历一个对象,触发所有转换后的 getter,以便将对象内的每个嵌套属性收集为 "深度" 依赖 + function traverse (val) { + // 调用 _traverse 函数进行遍历 + _traverse(val, seenObjects); + // 清空 seenObjects 集合 + seenObjects.clear(); + } + +// 内部递归遍历函数 + function _traverse (val, seen) { + var i, keys; + // 判断 val 是否为数组 + var isA = Array.isArray(val); + // 如果 val 既不是数组也不是对象,或者是冻结对象,或者是 VNode 实例,则直接返回 + if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { + return + } + // 如果 val 有 __ob__ 属性(表示是响应式对象) + if (val.__ob__) { + // 获取该对象的依赖 ID + var depId = val.__ob__.dep.id; + // 如果 seen 集合中已经存在该依赖 ID,则直接返回 + if (seen.has(depId)) { + return + } + // 将该依赖 ID 添加到 seen 集合中 + seen.add(depId); + } + // 如果 val 是数组 + if (isA) { + // 获取数组长度 + i = val.length; + // 从后往前遍历数组元素 + while (i--) { _traverse(val[i], seen); } + } else { + // 获取对象的所有键 + keys = Object.keys(val); + // 获取键的数量 + i = keys.length; + // 从后往前遍历键 + while (i--) { _traverse(val[keys[i]], seen); } + } + } + +// 缓存 normalizeEvent 函数的结果 + var normalizeEvent = cached(function (name) { + // 判断是否为被动事件 + var passive = name.charAt(0) === '&'; + // 去除被动事件标记 + name = passive ? name.slice(1) : name; + // 判断是否为一次性事件 + var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first + // 去除一次性事件标记 + name = once$$1 ? name.slice(1) : name; + // 判断是否为捕获事件 + var capture = name.charAt(0) === '!'; + // 去除捕获事件标记 + name = capture ? name.slice(1) : name; + // 返回事件信息对象 + return { + name: name, + once: once$$1, + capture: capture, + passive: passive + } + }); + +// 创建一个函数调用器 + function createFnInvoker (fns, vm) { + // 定义调用器函数 + function invoker () { + var arguments$1 = arguments; + + var fns = invoker.fns; + // 如果 fns 是数组 + if (Array.isArray(fns)) { + // 复制一份 fns 数组 + var cloned = fns.slice(); + // 遍历数组并调用每个函数 + for (var i = 0; i < cloned.length; i++) { + invokeWithErrorHandling(cloned[i], null, arguments$1, vm, "v-on handler"); + } + } else { + // 对于单个函数,返回其调用结果 + return invokeWithErrorHandling(fns, null, arguments, vm, "v-on handler") + } + } + // 将传入的 fns 赋值给调用器的 fns 属性 + invoker.fns = fns; + // 返回调用器函数 + return invoker + } + +// 更新事件监听器 + function updateListeners ( + on, + oldOn, + add, + remove$$1, + createOnceHandler, + vm + ) { + var name, def$$1, cur, old, event; + // 遍历新的事件监听器 + for (name in on) { + def$$1 = cur = on[name]; + old = oldOn[name]; + // 规范化事件名称 + event = normalizeEvent(name); + // 如果新的监听器无效 + if (isUndef(cur)) { + // 发出警告 + warn( + "Invalid handler for event \"" + (event.name) + "\": got " + String(cur), + vm + ); + } else if (isUndef(old)) { + // 如果旧的监听器不存在 + if (isUndef(cur.fns)) { + // 创建函数调用器 + cur = on[name] = createFnInvoker(cur, vm); + } + // 如果是一次性事件 + if (isTrue(event.once)) { + // 创建一次性事件处理程序 + cur = on[name] = createOnceHandler(event.name, cur, event.capture); + } + // 添加事件监听器 + add(event.name, cur, event.capture, event.passive, event.params); + } else if (cur !== old) { + // 如果新的监听器和旧的不同 + old.fns = cur; + on[name] = old; + } + } + // 遍历旧的事件监听器 + for (name in oldOn) { + // 如果新的监听器中不存在该事件 + if (isUndef(on[name])) { + // 规范化事件名称 + event = normalizeEvent(name); + // 移除事件监听器 + remove$$1(event.name, oldOn[name], event.capture); + } + } + } + +// 合并 VNode 钩子函数 + function mergeVNodeHook (def, hookKey, hook) { + // 如果 def 是 VNode 实例 + if (def instanceof VNode) { + // 获取或创建 data.hook 对象 + def = def.data.hook || (def.data.hook = {}); + } + var invoker; + // 获取旧的钩子函数 + var oldHook = def[hookKey]; + + // 定义包装后的钩子函数 + function wrappedHook () { + // 调用钩子函数 + hook.apply(this, arguments); + // 移除合并后的钩子函数,确保只调用一次,防止内存泄漏 + remove(invoker.fns, wrappedHook); + } + + // 如果旧的钩子函数不存在 + if (isUndef(oldHook)) { + // 创建一个新的调用器 + invoker = createFnInvoker([wrappedHook]); + } else { + // 如果旧的钩子函数已经是合并后的调用器 + if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { + // 使用旧的调用器 + invoker = oldHook; + // 将包装后的钩子函数添加到调用器的 fns 数组中 + invoker.fns.push(wrappedHook); + } else { + // 如果旧的钩子函数是普通函数 + invoker = createFnInvoker([oldHook, wrappedHook]); + } + } + + // 标记调用器为已合并 + invoker.merged = true; + // 将调用器赋值给对应的钩子键 + def[hookKey] = invoker; + } + +// 从 VNode 数据中提取 props + function extractPropsFromVNodeData ( + data, + Ctor, + tag + ) { + // 只提取原始值,验证和默认值在子组件中处理 + var propOptions = Ctor.options.props; + // 如果没有定义 props 选项,直接返回 + if (isUndef(propOptions)) { + return + } + var res = {}; + // 获取 data 中的 attrs 和 props + var attrs = data.attrs; + var props = data.props; + // 如果 attrs 或 props 存在 + if (isDef(attrs) || isDef(props)) { + // 遍历 propOptions 中的每个 prop + for (var key in propOptions) { + // 获取 kebab-case 形式的 prop 名称 + var altKey = hyphenate(key); + { + // 获取 prop 名称的小写形式 + var keyInLowerCase = key.toLowerCase(); + // 如果 prop 名称大小写不同,且 attrs 中存在小写形式的 prop + if ( + key !== keyInLowerCase && + attrs && hasOwn(attrs, keyInLowerCase) + ) { + // 提示 prop 名称大小写不一致的问题 + tip( + "Prop \"" + keyInLowerCase + "\" is passed to component " + + (formatComponentName(tag || Ctor)) + ", but the declared prop name is" + + " \"" + key + "\". " + + "Note that HTML attributes are case-insensitive and camelCased " + + "props need to use their kebab-case equivalents when using in-DOM " + + "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"." + ); + } + } + // 检查 props 中是否存在该 prop + checkProp(res, props, key, altKey, true) || + // 检查 attrs 中是否存在该 prop + checkProp(res, attrs, key, altKey, false); + } + } + // 返回提取的 props + return res + } + +// 检查并提取 prop + function checkProp ( + res, + hash, + key, + altKey, + preserve + ) { + // 如果 hash 存在 + if (isDef(hash)) { + // 如果 hash 中存在 key + if (hasOwn(hash, key)) { + // 将 key 对应的值赋给 res + res[key] = hash[key]; + // 如果不需要保留该属性 + if (!preserve) { + // 从 hash 中删除该属性 + delete hash[key]; + } + return true + } else if (hasOwn(hash, altKey)) { + // 如果 hash 中存在 altKey + res[key] = hash[altKey]; + // 如果不需要保留该属性 + if (!preserve) { + // 从 hash 中删除该属性 + delete hash[altKey]; + } + return true + } + } + return false + } + +// 模板编译器会在编译时静态分析模板,尽量减少规范化的需求。 +// 对于纯 HTML 标记,可以完全跳过规范化,因为生成的渲染函数保证返回 Array。但有两种情况需要额外的规范化: + +// 1. 当子节点包含组件时 - 因为函数式组件可能返回一个数组而不是单个根节点。在这种情况下,只需要简单的规范化 - 如果任何子节点是数组,我们使用 Array.prototype.concat 扁平化整个数组。由于函数式组件已经对自己的子节点进行了规范化,所以保证只有一层深度。 + function simpleNormalizeChildren (children) { + // 遍历子节点 + for (var i = 0; i < children.length; i++) { + // 如果子节点是数组 + if (Array.isArray(children[i])) { + // 使用 Array.prototype.concat 扁平化数组 + return Array.prototype.concat.apply([], children) + } + } + // 如果没有数组子节点,直接返回子节点数组 + return children + } + +// 2. 当子节点包含总是生成嵌套数组的结构时,例如