/*! * Vue.js v2.5.16 * (c) 2014-2018 Evan You * Released under the MIT License. * development 开发 * production 生产 /* * 兼容 amd cmd 模块写法 * */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.Vue = factory()); }(this, (function () { 'use strict'; /* */ //Object.freeze()阻止修改现有属性的特性和值,并阻止添加新属性。 var emptyObject = Object.freeze({}); // these helpers produces better vm code in JS engines due to their // explicitness and function inlining // these helpers produces better vm code in JS engines due to their // explicitness and function inlining //判断数据 是否是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 } /** * Check if value is primitive * //判断数据类型是否是string,number,symbol,boolean */ function isPrimitive(value) { //判断数据类型是否是string,number,symbol,boolean return ( typeof value === 'string' || typeof value === 'number' || // $flow-disable-line typeof value === 'symbol' || typeof value === 'boolean' ) } /** * Quick object check - this is primarily used to tell * Objects from primitive values when we know the value * is a JSON-compliant type. */ function isObject(obj) { //判断是否是对象 return obj !== null && typeof obj === 'object' } /** * Get the raw type string of a value e.g. [object Object] */ //获取toString 简写 var _toString = Object.prototype.toString; function toRawType(value) { //类型判断 返会Array ,Function,String,Object,Re 等 return _toString.call(value).slice(8, -1) } /** * Strict object type check. Only returns true * for plain JavaScript objects. */ function isPlainObject(obj) { //判断是否是对象 return _toString.call(obj) === '[object Object]' } function isRegExp(v) { //判断是否是正则对象 return _toString.call(v) === '[object RegExp]' } /** * Check if val is a valid array index. */ /** * Check if val is a valid array index. * 检查VAL是否是有效的数组索引。 */ function isValidArrayIndex(val) { //isFinite 检测是否是数据 //Math.floor 向下取整 var n = parseFloat(String(val)); //isFinite 如果 number 是有限数字(或可转换为有限数字),那么返回 true。否则,如果 number 是 NaN(非数字),或者是正、负无穷大的数,则返回 false。 return n >= 0 && Math.floor(n) === n && isFinite(val) } /** * Convert a value to a string that is actually rendered. */ function toString(val) { //将对象或者其他基本数据 变成一个 字符串 return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val) } /** * Convert a input value to a number for persistence. * If the conversion fails, return original string. */ function toNumber(val) { //字符串转数字,如果失败则返回字符串 var n = parseFloat(val); return isNaN(n) ? val : n } /** * Make a map and return a function for checking if a key * is in that map. * * //map 对象中的[name1,name2,name3,name4] 变成这样的map{name1:true,name2:true,name3:true,name4:true} * 并且传进一个key值取值,这里用到策略者模式 */ 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; //map 对象中的[name1,name2,name3,name4] 变成这样的map{name1:true,name2:true,name3:true,name4:true} } return expectsLowerCase ? function (val) { return map[val.toLowerCase()]; } //返回一个柯里化函数 toLowerCase转换成小写 : function (val) { return map[val]; } //返回一个柯里化函数 并且把map中添加一个 属性建 } /** * Check if a tag is a built-in tag. * 检查标记是否为内置标记。 */ var isBuiltInTag = makeMap('slot,component', true); /** * Check if a attribute is a reserved attribute. * 检查属性是否为保留属性。 * isReservedAttribute=function(vale){ map{key:true,ref:true,slot-scope:true,is:true,vaule:undefined} } */ var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is'); /** * Remove an item from an array * //删除数组 */ function remove(arr, item) { if (arr.length) { var index = arr.indexOf(item); if (index > -1) { return arr.splice(index, 1) } } } /** * Check whether the object has the property. *检查对象属性是否是实例化还是原型上面的 */ var hasOwnProperty = Object.prototype.hasOwnProperty; function hasOwn(obj, key) { return hasOwnProperty.call(obj, key) } /** * Create a cached version of a pure function. */ /** * Create a cached version of a pure function. * 创建纯函数的缓存版本。 * 创建一个函数,缓存,再return 返回柯里化函数 * 闭包用法 */ /*********************************************************************************************** *函数名 :cached *函数功能描述 : 创建纯函数的缓存版本。 创建一个函数,缓存,再return 返回柯里化函数 闭包用法 *函数参数 : fn 函数 *函数返回值 : fn *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ /* * var aFn = cached(function(string){ * * return string * }) * aFn(string1); * aFn(string2); * aFn(string); * aFn(string1); * aFn(string2); * * aFn 函数会多次调用 里面就能体现了 * 用对象去缓存记录函数 * */ function cached(fn) { var cache = Object.create(null); return (function cachedFn(str) { var hit = cache[str]; return hit || (cache[str] = fn(str)) }) } /** * Camelize a hyphen-delimited string. * 用连字符分隔的字符串。 * camelize = cachedFn(str)=>{ var hit = cache[str]; return hit || (cache[str] = fn(str))} 调用一个camelize 存一个建进来 调用两次 如果建一样就返回 hit 横线-的转换成驼峰写法 可以让这样的的属性 v-model 变成 vModel */ var camelizeRE = /-(\w)/g; var camelize = cached(function (str) { return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) }); /** * Capitalize a string. 将首字母变成大写。 */ var capitalize = cached(function (str) { return str.charAt(0).toUpperCase() + str.slice(1) }); /** * Hyphenate a camelCase string. * \B的用法 \B是非单词分界符,即可以查出是否包含某个字,如“ABCDEFGHIJK”中是否包含“BCDEFGHIJK”这个字。 */ var hyphenateRE = /\B([A-Z])/g; var hyphenate = cached(function (str) { //大写字母,加完减号又转成小写了 比如把驼峰 aBc 变成了 a-bc //匹配大写字母并且两面不是空白的 替换成 '-' + '字母' 在全部转换成小写 return str.replace(hyphenateRE, '-$1').toLowerCase(); }); /** * Simple bind polyfill for environments that do not support it... e.g. * PhantomJS 1.x. Technically we don't need this anymore since native bind is * now more performant in most browsers, but removing it would be breaking for * code that was able to run in PhantomJS 1.x, so this must be kept for * backwards compatibility. * 改变this 上下文 * 执行方式 */ /* istanbul ignore next */ //绑定事件 并且改变上下文指向 function polyfillBind(fn, ctx) { function boundFn(a) { var l = arguments.length; return l ? l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx) } boundFn._length = fn.length; return boundFn } //执行方式 function nativeBind(fn, ctx) { return fn.bind(ctx) } //bing 改变this上下文 var bind = Function.prototype.bind ? nativeBind : polyfillBind; /** * Convert an Array-like object to a real Array. * 将假的数组转换成真的数组 */ function toArray(list, start) { start = start || 0; var i = list.length - start; var ret = new Array(i); while (i--) { ret[i] = list[i + start]; } return ret } /** * Mix properties into target object. * * 浅拷贝 */ /*********************************************************************************************** *函数名 :extend *函数功能描述 : 浅拷贝 *函数参数 : to 超类, _from 子类 *函数返回值 : 合并类 *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ //对象浅拷贝,参数(to, _from)循环_from的值,会覆盖掉to的值 function extend(to, _from) { for (var key in _from) { to[key] = _from[key]; } return to } /** * Merge an Array of Objects into a single Object. * */ /*********************************************************************************************** *函数名 :toObject *函数功能描述 : 和并对象数组合并成一个对象 *函数参数 : arr 数组对象类 *函数返回值 : *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ function toObject(arr) { var res = {}; for (var i = 0; i < arr.length; i++) { if (arr[i]) { extend(res, arr[i]); } } return res } /** * Perform no operation. * Stubbing args to make Flow happy without leaving useless transpiled code * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/) */ function noop(a, b, c) { } /** * Always return false. * 返回假的 */ var no = function (a, b, c) { return false; }; /** * Return same value *返回相同值 */ var identity = function (_) { return _; }; /** * Generate a static keys string from compiler modules. * * [{ staticKeys:1},{staticKeys:2},{staticKeys:3}] * 连接数组对象中的 staticKeys key值,连接成一个字符串 str=‘1,2,3’ */ function genStaticKeys(modules) { return modules.reduce( function (keys, m) { //累加staticKeys的值变成数组 return keys.concat(m.staticKeys || []) }, [] ).join(',') //转换成字符串 } /** * Check if two values are loosely equal - that is, * if they are plain objects, do they have the same shape? * 检测a和b的数据类型,是否是不是数组或者对象,对象的key长度一样即可,数组长度一样即可 */ function looseEqual(a, b) { if (a === b) { return true } //如果a和b是完全相等 则true var isObjectA = isObject(a); var isObjectB = isObject(b); if (isObjectA && isObjectB) { //如果a和都是对象则让下走 try { var isArrayA = Array.isArray(a); var isArrayB = Array.isArray(b); if (isArrayA && isArrayB) { //如果a和b都是数组 // every 条件判断 return a.length === b.length && a.every(function (e, i) { //如果a长度和b长度一样的时候 return looseEqual(e, b[i]) //递归 }) } else if (!isArrayA && !isArrayB) { //或者a和b都不是数组 var keysA = Object.keys(a); // 获取到a的key值 变成一个数组 var keysB = Object.keys(b); // 获取到b的key值 变成一个数组 //他们的对象key值长度是一样的时候 则加载every 条件函数 return keysA.length === keysB.length && keysA.every(function (key) { //递归 a和b的值 return looseEqual(a[key], b[key]) }) } else { //如果不是对象跳槽循环 /* istanbul ignore next */ return false } } catch (e) { //如果不是对象跳槽循环 /* istanbul ignore next */ return false } } else if (!isObjectA && !isObjectB) { //b和a 都不是对象的时候 //把a和b变成字符串,判断他们是否相同 return String(a) === String(b) } else { return false } } // 判断 arr数组中的数组 是否和val相等。 // 或者 arr数组中的对象,或者对象数组 是否和val 相等 function looseIndexOf(arr, val) { for (var i = 0; i < arr.length; i++) { if (looseEqual(arr[i], val)) { return i } } return -1 } /** * Ensure a function is called only once. * 确保该函数只调用一次 闭包函数 */ function once(fn) { var called = false; return function () { if (!called) { called = true; fn.apply(this, arguments); } } } //ssr标记属性 var SSR_ATTR = 'data-server-rendered'; var ASSET_TYPES = [ 'component', //组建指令 'directive', //定义指令 指令 'filter' //过滤器指令 ]; var LIFECYCLE_HOOKS = [ 'beforeCreate', // 生命周期 开始实例化 vue 指令 'created', //生命周期 结束实例化完 vue 指令 'beforeMount', //生命周期 开始渲染虚拟dom ,挂载event 事件 指令 'mounted', //生命周期 渲染虚拟dom ,挂载event 事件 完 指令 'beforeUpdate', //生命周期 开始更新wiew 数据指令 'updated', //生命周期 结束更新wiew 数据指令 'beforeDestroy', //生命周期 开始销毁 new 实例 指令 'destroyed', //生命周期 结束销毁 new 实例 指令 'activated', //keep-alive组件激活时调用。 'deactivated', //deactivated keep-alive组件停用时调用。 'errorCaptured' // 具有此钩子的组件捕获其子组件树(不包括其自身)中的所有错误(不包括在异步回调中调用的那些)。 ]; /* */ var config = ({ /** * Option merge strategies (used in core/util/options) */ // $flow-disable-line //合并对象 策略 optionMergeStrategies: Object.create(null), /** * Whether to suppress warnings. * * 是否禁止警告。 */ silent: false, /** * Show production mode tip message on boot? * 在引导时显示生产模式提示消息? * webpack打包判断执行环境是不是生产环境,如果是生产环境会压缩并且没有提示警告之类的东西 */ productionTip: "development" !== 'production', /** * Whether to enable devtools * 是否启用DevTools */ devtools: "development" !== 'production', /** * Whether to record perf * 是否记录PERF */ performance: false, /** * Error handler for watcher errors *监视器错误的错误处理程序 */ errorHandler: null, /** * Warn handler for watcher warns * 观察加警告处理。 */ warnHandler: null, /** * Ignore certain custom elements * 忽略某些自定义元素 */ ignoredElements: [], /** * Custom user key aliases for v-on * 用于V-on的自定义用户密钥别名 键盘码 */ // $flow-disable-line keyCodes: Object.create(null), /** * Check if a tag is reserved so that it cannot be registered as a * component. This is platform-dependent and may be overwritten. * 检查是否保留了一个标签,使其不能注册为组件。这是平台相关的,可能会被覆盖。 */ isReservedTag: no, /** * Check if an attribute is reserved so that it cannot be used as a component * prop. This is platform-dependent and may be overwritten. * 检查属性是否被保留,使其不能用作组件支持。这是平台相关的,可能会被覆盖。 */ isReservedAttr: no, /** * Check if a tag is an unknown element. * Platform-dependent. * Check if a tag is an unknown element. Platform-dependent. * 检查标签是否为未知元素依赖于平台的检查,如果标签是未知元素。平台相关的 * */ isUnknownElement: no, /** * Get the namespace of an element * 获取元素的命名空间 */ getTagNamespace: noop, /** * Parse the real tag name for the specific platform. * 解析真实的标签平台 */ parsePlatformTagName: identity, /** * Check if an attribute must be bound using property, e.g. value * Platform-dependent. * 检查属性是否必须使用属性绑定,例如依赖于依赖于平台的属性。 */ mustUseProp: no, /** * Exposed for legacy reasons * 因遗产原因暴露 * 声明周期对象 */ _lifecycleHooks: LIFECYCLE_HOOKS }) /* */ /** * Check if a string starts with $ or _ * 检查一个字符串是否以$或者_开头 */ function isReserved(str) { var c = (str + '').charCodeAt(0); return c === 0x24 || c === 0x5F } /** * Define a property. * 用defineProperty 定义属性 * 详细地址 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty 第一个参数是对象 第二个是key 第三个是vue 第四个是 是否可以枚举 */ function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, //值 enumerable: !!enumerable, //定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。 writable: true, //可以 改写 value configurable: true //configurable特性表示对象的属性是否可以被删除,以及除writable特性外的其他特性是否可以被修改。 }); } /** * Parse simple path. * 解析。 */ var bailRE = /[^\w.$]/; //匹配不是 数字字母下划线 $符号 开头的为true function parsePath(path) { console.log(path) if (bailRE.test(path)) { //匹配上 返回 true return } //匹配不上 path在已点分割 var segments = path.split('.'); return function (obj) { for (var i = 0; i < segments.length; i++) { //如果没有参数则返回 if (!obj) { return } //将对象中的一个key值 赋值给该对象 相当于 obj = obj[segments[segments.length-1]]; obj = obj[segments[i]]; } //否则返回一个对象 return obj } } /* */ // can we use __proto__? var hasProto = '__proto__' in {}; // Browser environment sniffing //判断设备和浏览器 var inBrowser = typeof window !== 'undefined'; //如果不是浏览器 var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; //weex 环境 一个 vue做app包的框架 var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();//weex 环境 一个 vue做app包的框架 //window.navigator.userAgent属性包含了浏览器类型、版本、操作系统类型、浏览器引擎类型等信息,通过这个属性来判断浏览器类型 var UA = inBrowser && window.navigator.userAgent.toLowerCase(); //获取浏览器 var isIE = UA && /msie|trident/.test(UA); //ie var isIE9 = UA && UA.indexOf('msie 9.0') > 0; //ie9 var isEdge = UA && UA.indexOf('edge/') > 0; //ie10 以上 var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); //安卓 var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); //ios var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; //谷歌浏览器 // Firefox has a "watch" function on Object.prototype... var nativeWatch = ({}).watch; //兼容火狐浏览器写法 var supportsPassive = false; if (inBrowser) { try { var opts = {}; Object.defineProperty(opts, 'passive', ({ get: function get() { /* istanbul ignore next */ supportsPassive = true; } })); // https://github.com/facebook/flow/issues/285 window.addEventListener('test-passive', null, opts); } catch (e) { } } // this needs to be lazy-evaled because vue may be required before // vue-server-renderer can set VUE_ENV //vue 服务器渲染 可以设置 VUE_ENV var _isServer; //判断是不是node 服务器环境 var isServerRendering = function () { if (_isServer === undefined) { /* istanbul ignore if */ //如果不是浏览器 并且global 对象存在,那么有可能是node 脚本 if (!inBrowser && typeof global !== 'undefined') { // // detect presence of vue-server-renderer and avoid // Webpack shimming the process //_isServer 设置是服务器渲染 _isServer = global['process'].env.VUE_ENV === 'server'; } else { _isServer = false; } } return _isServer }; // detect devtools //检测开发者工具。 var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; /* istanbul ignore next */ function isNative(Ctor) { //或者判断该函数是不是系统内置函数 //判断一个函数中是否含有 'native code' 字符串 比如 // function code(){ // var native='native code' // } // 或者 // function code(){ // var native='native codeasdfsda' // } return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) } //判断是否支持Symbol 数据类型 var hasSymbol = //Symbol es6新出来的一种数据类型,类似于string类型,声明唯一的数据值 typeof Symbol !== 'undefined' && isNative(Symbol) && // Reflect.ownKeys // Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和。 typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); var _Set; /* istanbul ignore if */ // $flow-disable-line //ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 // Set 本身是一个构造函数,用来生成 Set 数据结构。 //判断是否有set这个方法 if (typeof Set !== 'undefined' && isNative(Set)) { // use native Set when available. _Set = Set; } else { // a non-standard Set polyfill that only works with primitive keys. //如果没有他自己写一个 _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); // work around flow check 绕流检查 var formatComponentName = (noop); { //判断是否有console 打印输出属性 var hasConsole = typeof console !== 'undefined'; var classifyRE = /(?:^|[-_])(\w)/g; //非捕获 匹配不分组 。 就是可以包含,但是不匹配上 //过滤掉class中的 -_ 符号 并且把字母开头的改成大写 var classify = function (str) { return str.replace(classifyRE, function (c) { return c.toUpperCase(); }).replace(/[-_]/g, ''); }; /*********************************************************************************************** *函数名 :warn *函数功能描述 : 警告信息提示 *函数参数 : msg: 警告信息, vm:vue对象 *函数返回值 : void *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ warn = function (msg, vm) { //vm 如果没有传进来就给空, 不然给执行generateComponentTrace 收集 vue错误码 var trace = vm ? generateComponentTrace(vm) : ''; //warnHandler 如果存在 则调用他 if (config.warnHandler) { config.warnHandler.call(null, msg, vm, trace); } else if (hasConsole && (!config.silent)) { //如果config.warnHandler 不存在则 console 内置方法打印 console.error(("[Vue warn]: " + msg + trace)); } }; //也是个警告输出方法 tip = function (msg, vm) { if (hasConsole && (!config.silent)) { // console.warn("[Vue tip]: " + msg + ( vm ? generateComponentTrace(vm) : '' )); } }; /*********************************************************************************************** *函数名 :formatComponentName *函数功能描述 : 格式组件名 *函数参数 : msg: 警告信息, vm:vue对象 *函数返回值 : void *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ formatComponentName = function (vm, includeFile) { if (vm.$root === vm) { return '' } /* * 如果 vm === 'function' && vm.cid != null 条件成立 则options等于vm.options * 当vm === 'function' && vm.cid != null 条件不成立的时候 vm._isVue ? vm.$options || vm.constructor.options : vm || {}; * vm._isVue为真的时候 vm.$options || vm.constructor.options ,vm._isVue为假的时候 vm || {} * */ var options = typeof vm === 'function' && vm.cid != null ? vm.options : vm._isVue ? vm.$options || vm.constructor.options : vm || {}; var name = options.name || options._componentTag; console.log('name=' + name); var file = options.__file; if (!name && file) { //匹配.vue 后缀的文件名 //如果文件名中含有vue的文件将会被匹配出来 但是会多虑掉 \符号 var match = file.match(/([^/\\]+)\.vue$/); name = match && match[1]; } //可能返回 classify(name) //name 组件名称或者是文件名称 /* * classify 去掉-_连接 大些字母连接起来 * 如果name存在则返回name * 如果name不存在那么返回‘’+ 如果file存在并且includeFile!==false的时候 返回" at " + file 否则为空 * * */ return ( (name ? ("<" + (classify(name)) + ">") : "") + (file && includeFile !== false ? (" at " + file) : '') ) }; /* *重复 递归 除2次 方法+ str * */ var repeat = function (str, n) { var res = ''; while (n) { if (n % 2 === 1) { res += str; } if (n > 1) { str += str; } n >>= 1; //16 8 //15 7 相当于除2 向下取整2的倍数 //console.log( a >>= 1) } return res }; /*********************************************************************************************** *函数名 :generateComponentTrace *函数功能描述 : 生成组建跟踪 vm=vm.$parent递归收集到msg出处。 *函数参数 : vm 组建 *函数返回值 : *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ generateComponentTrace = function (vm) { if (vm._isVue && vm.$parent) { //如果_isVue 等于真,并且有父亲节点的 var tree = []; //记录父节点 var currentRecursiveSequence = 0; while (vm) { //循环 vm 节点 if (tree.length > 0) {//tree如果已经有父节点的 var last = tree[tree.length - 1]; if (last.constructor === vm.constructor) { //上一个节点等于父节点 个人感觉这里用户不会成立 currentRecursiveSequence++; vm = vm.$parent; continue } else if (currentRecursiveSequence > 0) { //这里也不会成立 tree[tree.length - 1] = [last, currentRecursiveSequence]; currentRecursiveSequence = 0; } } tree.push(vm); //把vm添加到队列中 vm = vm.$parent; } return '\n\nfound in\n\n' + tree .map(function (vm, i) { //如果i是0 则输出 ‘---->’ //如果i 不是0的时候输出组件名称 return ("" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + ( Array.isArray(vm) ? ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)") : formatComponentName(vm) ) ); }) .join('\n') } else { //如果没有父组件则输出一个组件名称 return ("\n\n(found in " + (formatComponentName(vm)) + ")") } }; } /* */ /* */ var uid = 0; /** * A dep is an observable that can have multiple dep是可观察到的,可以有多个 * directives subscribing to it.订阅它的指令。 * */ //主题对象Dep构造函数 主要用于添加发布事件后,用户更新数据的 响应式原理之一函数 var Dep = function Dep() { //uid 初始化为0 this.id = uid++; /* 用来存放Watcher对象的数组 */ this.subs = []; }; Dep.prototype.addSub = function addSub(sub) { /* 在subs中添加一个Watcher对象 */ this.subs.push(sub); }; Dep.prototype.removeSub = function removeSub(sub) { /*删除 在subs中添加一个Watcher对象 */ remove(this.subs, sub); }; //this$1.deps[i].depend(); //为Watcher 添加 为Watcher.newDeps.push(dep); 一个dep对象 Dep.prototype.depend = function depend() { //添加一个dep target 是Watcher dep就是dep对象 if (Dep.target) { //像指令添加依赖项 Dep.target.addDep(this); } }; /* 通知所有Watcher对象更新视图 */ Dep.prototype.notify = function notify() { // stabilize the subscriber list first var subs = this.subs.slice(); for (var i = 0, l = subs.length; i < l; i++) { //更新数据 subs[i].update(); } }; // the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. //当前正在评估的目标监视程序。 //这在全球是独一无二的,因为只有一个 //观察者在任何时候都被评估。 Dep.target = null; var targetStack = []; function pushTarget(_target) { //target 是Watcher dep就是dep对象 if (Dep.target) { //静态标志 Dep当前是否有添加了target //添加一个pushTarget targetStack.push(Dep.target); } Dep.target = _target; } // function popTarget() { // 出盏一个pushTarget Dep.target = targetStack.pop(); } /* * 创建标准的vue vnode * * */ var VNode = function VNode( tag, /*当前节点的标签名*/ data, /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ children, //子节点 text, //文本 elm, /*当前节点的dom */ context, /*编译作用域*/ componentOptions, /*组件的option选项*/ asyncFactory/*异步工厂*/) { /*当前节点的标签名*/ this.tag = tag; /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ this.data = data; /*当前节点的子节点,是一个数组*/ this.children = children; /*当前节点的文本*/ this.text = text; /*当前虚拟节点对应的真实dom节点*/ this.elm = elm; /*当前节点的名字空间*/ this.ns = undefined; /*编译作用域 vm*/ this.context = context; this.fnContext = undefined; this.fnOptions = undefined; this.fnScopeId = undefined; /*节点的key属性,被当作节点的标志,用以优化*/ this.key = data && data.key; /*组件的option选项*/ this.componentOptions = componentOptions; /*当前节点对应的组件的实例*/ this.componentInstance = undefined; /*当前节点的父节点*/ this.parent = undefined; /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/ this.raw = false; /*静态节点标志*/ this.isStatic = false; /*是否作为跟节点插入*/ this.isRootInsert = true; /*是否为注释节点*/ this.isComment = false; /*是否为克隆节点*/ this.isCloned = false; /*是否有v-once指令*/ this.isOnce = false; /*异步工厂*/ this.asyncFactory = asyncFactory; this.asyncMeta = undefined; this.isAsyncPlaceholder = false; }; //当且仅当该属性描述符的类型可以被改变并且该属性可以从对应对象中删除。默认为 false var prototypeAccessors = {child: {configurable: true}}; // DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ prototypeAccessors.child.get = function () { return this.componentInstance }; /*设置所有VNode.prototype 属性方法 都为 { 'child':{ configurable: true, get:function(){ return this.componentInstance } } } */ 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 }; //创建一个文本节点 function createTextVNode(val) { return new VNode( undefined, undefined, undefined, String(val) ) } // optimized shallow clone // used for static nodes and slot nodes because they may be reused across // multiple renders, cloning them avoids errors when DOM manipulations rely // on their elm reference. //优化浅克隆 //用于静态节点和时隙节点,因为它们可以被重用。 //多重渲染,克隆它们避免DOM操作依赖时的错误 //他们的榆树参考。 //克隆节点 把节点变成静态节点 function cloneVNode(vnode, deep) { // var componentOptions = vnode.componentOptions; /*组件的option选项*/ var cloned = new VNode( vnode.tag, vnode.data, vnode.children, vnode.text, vnode.elm, vnode.context, componentOptions, vnode.asyncFactory ); cloned.ns = vnode.ns;/*当前节点的名字空间*/ cloned.isStatic = vnode.isStatic;/*静态节点标志*/ cloned.key = vnode.key;/*节点的key属性,被当作节点的标志,用以优化*/ cloned.isComment = vnode.isComment;/*是否为注释节点*/ cloned.fnContext = vnode.fnContext; //函数上下文 cloned.fnOptions = vnode.fnOptions; //函数Options选项 cloned.fnScopeId = vnode.fnScopeId; //函数范围id cloned.isCloned = true; /*是否为克隆节点*/ if (deep) { //如果deep存在 if (vnode.children) { //如果有子节点 //深度拷贝子节点 cloned.children = cloneVNodes(vnode.children, true); } if (componentOptions && componentOptions.children) { //深度拷贝子节点 componentOptions.children = cloneVNodes(componentOptions.children, true); } } return cloned } //克隆多个节点 为数组的 function cloneVNodes(vnodes, deep) { var len = vnodes.length; var res = new Array(len); for (var i = 0; i < len; i++) { res[i] = cloneVNode(vnodes[i], deep); } return res } /* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto); var methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]; /** * Intercept mutating methods and emit events */ /*********************************************************************************************** *函数名 :methodsToPatch *函数功能描述 : 更新数据时候如果是数组拦截方法,如果在数据中更新用的是'push','pop','shift','unshift','splice','sort','reverse' 方法则会调用这里 *函数参数 : *函数返回值 : *作者 : *函数创建日期 : *函数修改日期 : *修改人 : *修改原因 : *版本 : *历史版本 : ***********************************************************************************************/ methodsToPatch.forEach(function (method) { console.log('methodsToPatch') // cache original method var original = arrayProto[method]; console.log('==method==') console.log(method) console.log('==original==') console.log(original) def(arrayMethods, method, function mutator() { console.log('==def_original==') console.log(original) var args = [], len = arguments.length; while (len--) args[len] = arguments[len]; var result = original.apply(this, args); var ob = this.__ob__; console.log('this.__ob__') console.log(this.__ob__) var inserted; switch (method) { case 'push': case 'unshift': inserted = args; break case 'splice': inserted = args.slice(2); break } if (inserted) { //观察数组数据 ob.observeArray(inserted); } // notify change //更新通知 ob.dep.notify(); console.log('====result====') console.log(result) return result }); }); /* */ // 方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组,只包括实例化的属性和方法,不包括原型上的。 var arrayKeys = Object.getOwnPropertyNames(arrayMethods); /** * In some cases we may want to disable observation inside a component's * update computation. *在某些情况下,我们可能希望禁用组件内部的观察。 *更新计算。 */ var shouldObserve = true; //标志是否禁止还是添加到观察者模式 function toggleObserving(value) { shouldObserve = value; } /** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. * *每个观察到的观察者类 *对象。一旦被连接,观察者就转换目标。 *对象的属性键为吸收器/设置器 *收集依赖关系并发送更新。 * * 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性 */ var Observer = function Observer(value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; //设置监听 value 必须是对象 def(value, '__ob__', this); if (Array.isArray(value)) { //判断是不是数组 var augment = hasProto //__proto__ 存在么 高级浏览器都会有这个 ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); this.observeArray(value); } else { this.walk(value); } }; /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. * *遍历每个属性并将其转换为 * getter / setter。此方法只应在调用时调用 *值类型是Object。 */ Observer.prototype.walk = function walk(obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } }; /** * Observe a list of Array items. * 观察数组项的列表。 * 把数组拆分一个个 添加到观察者 上面去 */ Observer.prototype.observeArray = function observeArray(items) { for (var i = 0, l = items.length; i < l; i++) { console.log('items[i]') console.log(items[i]) observe(items[i]); } }; // helpers /** * Augment an target Object or Array by intercepting * the prototype chain using __proto__ * 通过拦截来增强目标对象或数组 * 使用原型原型链 * target 目标对象 * src 原型 对象或者属性、 * keys key * */ function protoAugment(target, src, keys) { /* eslint-disable no-proto */ target.__proto__ = src; /* eslint-enable no-proto */ } /** * Augment an target Object or Array by defining * hidden properties. * 复制扩充 * 定义添加属性 并且添加 监听 *target 目标对象 * src对象 * keys 数组keys */ /* 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]); } } /** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. *尝试为值创建一个观察者实例, *如果成功观察,返回新的观察者; *或现有的观察员,如果值已经有一个。 * * 判断value 是否有__ob__ 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性 返回 new Observer 实例化的对象 */ function observe(value, asRootData) { if (!isObject(value) || value instanceof VNode) { //value 不是一个对象 或者 实例化 的VNode console.log(value) return } var ob; if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { console.log('hasOwn value') console.log(value) ob = value.__ob__; } else if ( shouldObserve && //shouldObserve 为真 !isServerRendering() && //并且不是在服务器node环境下 (Array.isArray(value) || isPlainObject(value)) && //是数组或者是对象 Object.isExtensible(value) && //Object.preventExtensions(O) 方法用于锁住对象属性,使其不能够拓展,也就是不能增加新的属性,但是属性的值仍然可以更改,也可以把属性删除,Object.isExtensible用于判断对象是否可以被拓展 !value._isVue //_isVue为假 ) { console.log('new Observer value') console.log(value) //实例化 dep对象 为 value添加__ob__ 属性 ob = new Observer(value); } console.log(value) //如果是RootData,即咱们在新建Vue实例时,传到data里的值,只有RootData在每次observe的时候,会进行计数。 vmCount是用来记录此Vue实例被使用的次数的, 比如,我们有一个组件logo,页面头部和尾部都需要展示logo,都用了这个组件,那么这个时候vmCount就会计数,值为2 if (asRootData && ob) { //是根节点数据的话 并且 ob 存在 ob.vmCount++; //统计有几个vm } // * 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性 return ob } /** * Define a reactive property on an Object. * 在对象上定义一个无功属性。 * 更新数据 * 通过defineProperty的set方法去通知notify()订阅者subscribers有新的值修改 * 添加观察者 get set方法 */ function defineReactive(obj, //对象 key,//对象的key val, //监听的数据 返回的数据 customSetter, // 日志函数 shallow //是否要添加__ob__ 属性 ) { //实例化一个主题对象,对象中有空的观察者列表 var dep = new Dep(); //获取描述属性 var property = Object.getOwnPropertyDescriptor(obj, key); var _property = Object.getOwnPropertyNames(obj); //获取实力对象属性或者方法,包括定义的描述属性 console.log(property); console.log(_property); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; console.log('arguments.length=' + arguments.length) if (!getter && arguments.length === 2) { val = obj[key]; } var setter = property && property.set; console.log(val) //判断value 是否有__ob__ 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性递归把val添加到观察者中 返回 new Observer 实例化的对象 var childOb = !shallow && observe(val); //定义描述 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; if (Dep.target) { //Dep.target 静态标志 标志了Dep添加了Watcher 实例化的对象 //添加一个dep dep.depend(); if (childOb) { //如果子节点存在也添加一个dep childOb.dep.depend(); if (Array.isArray(value)) { //判断是否是数组 如果是数组 dependArray(value); //则数组也添加dep } } } return value }, set: function reactiveSetter(newVal) { 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 * 不是生产环境的情况下 * */ if ("development" !== 'production' && customSetter) { customSetter(); } if (setter) { //set 方法 设置新的值 setter.call(obj, newVal); } else { //新的值直接给他 val = newVal; } console.log(newVal) //observe 添加 观察者 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. **在对象上设置属性。添加新属性和 *触发器更改通知,如果该属性不 *已经存在。 */ //如果是数组 并且key是数字 就更新数组 //如果是对象则重新赋值 //如果 (target).__ob__ 存在则表明该数据以前添加过观察者对象中 //通知订阅者ob.value更新数据 添加观察者 define set get 方法 function set(target, key, val) { if ("development" !== 'production' && //判断数据 是否是undefined或者null (isUndef(target) || isPrimitive(target)) //判断数据类型是否是string,number,symbol,boolean ) { //必须是对象数组才可以 否则发出警告 warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); } //如果是数组 并且key是数字 if (Array.isArray(target) && isValidArrayIndex(key)) { //设置数组的长度 target.length = Math.max(target.length, key); //像数组尾部添加一个新数据,相当于push target.splice(key, 1, val); return val } //判断key是否在target 上,并且不是在Object.prototype 原型上,而不是通过父层原型链查找的 if (key in target && !(key in Object.prototype)) { target[key] = val; //赋值 return val } var ob = (target).__ob__; //声明一个对象ob 值为该target对象中的原型上面的所有方法和属性 ,表明该数据加入过观察者中 //vmCount 记录vue被实例化的次数 //是不是vue if (target._isVue || (ob && ob.vmCount)) { //如果不是生产环境,发出警告 "development" !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ); return val } //如果ob不存在 说明他没有添加观察者 则直接赋值 if (!ob) { target[key] = val; return val } //通知订阅者ob.value更新数据 添加观察者 define set get 方法 defineReactive(ob.value, key, val); //通知订阅者ob.value更新数据 ob.dep.notify(); return val } /** * Delete a property and trigger change if necessary. * 删除属性并在必要时触发更改数据。 */ function del(target, key) { //如果不是生产环境 if ("development" !== 'production' && (isUndef(target) || isPrimitive(target)) ) { //无法删除未定义的、空的或原始值的无功属性: warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); } //如果是数据则用splice方法删除 if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1); return } var ob = (target).__ob__; //vmCount 记录vue被实例化的次数 //是不是vue if (target._isVue || (ob && ob.vmCount)) { //如果是开发环境就警告 "development" !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ); return } //如果不是target 实例化不删除原型方法 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. * 在数组被触摸时收集数组元素的依赖关系,因为 * 我们不能拦截数组元素访问,如属性吸收器。 * 参数是数组 */ function dependArray(value) { for (var e = (void 0), i = 0, l = value.length; i < l; i++) { e = value[i]; //添加一个dep 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 * 选择与限制 */ { strats.el = strats.propsData = function (parent, child, vm, key) { 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 = Object.keys(from); //获取对象的keys 变成数组 for (var i = 0; i < keys.length; i++) { key = keys[i]; //获取对象的key toVal = to[key]; // fromVal = from[key]; //获取对象的值 if (!hasOwn(to, key)) { //如果from对象的key在to对象中没有 set(to, key, fromVal); } else if (isPlainObject(toVal) && isPlainObject(fromVal)) { //深层递归 mergeData(toVal, fromVal); } } return to } /** * Data * mergeDataOrFn递归合并数据 深度拷贝。如果vm不存在,并且childVal不存在就返回parentVal。如果vm不存在并且parentVal不存在则返回childVal。如果vm不存在parentVal和childVal都存在则返回mergedDataFn。如果vm存在则返回 mergedInstanceDataFn函数 */ function mergeDataOrFn( parentVal, childVal, vm ) { //vm不存在的时候 if (!vm) { // in a Vue.extend merge, both should be functions Vue。扩展合并,两者都应该是函数 if (!childVal) { return parentVal } if (!parentVal) { return childVal } // when parentVal & childVal are both present, // we need to return a function that returns the // merged result of both functions... no need to // check if parentVal is a function here because // it has to be a function to pass previous merges. //当父母和孩子都在场时, //我们需要返回一个函数,该函数返回 //两个函数的合并结果…不需要 //检查parentVal是否是一个函数,因为 //它必须是一个函数来传递以前的合并。 return function mergedDataFn() { //如果childVal,parentVal是函数 先改变this return mergeData( typeof childVal === 'function' ? childVal.call(this, this) : childVal, typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal ) } } else { //如果vm 存在 则是合并vm的数据 return function mergedInstanceDataFn() { // instance merge 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 } } } } strats.data = function ( parentVal, childVal, vm ) { if (!vm) { if (childVal && typeof childVal !== 'function') { "development" !== 'production' && warn( 'The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.', vm ); return parentVal } return mergeDataOrFn(parentVal, childVal) } return mergeDataOrFn(parentVal, childVal, vm) }; /** * Hooks and props are merged as arrays. * 钩子和道具被合并成数组。 * 判断childVal存在么?如果不存在 则返回parentVal * 如果childVal存在 则判断parentVal存在么。如果parentVal存在则返回 parentVal.concat(childVal),如果不存在,则判断childVal是不是数组如果是数组直接返回去, * 如果不是数组把childVal变成数组在返回出去 */ function mergeHook( parentVal, childVal ) { return childVal ? (parentVal ? parentVal.concat(childVal) : (Array.isArray(childVal) ? childVal : [childVal] ) ):parentVal } LIFECYCLE_HOOKS.forEach(function (hook) { strats[hook] = mergeHook; }); /** * Assets * * When a vm is present (instance creation), we need to do * a three-way merge between constructor options, instance * options and parent options. * *资产 *当存在虚拟机(实例创建)时,我们需要做 *构造函数选项之间的三路合并,实例 *选项和父选项。 * 创建一个res对象,获取parentVal对象中的数据。如果parentVal存在则获取parentVal对象 的数据存在res中的 __props__ 中,如果没有则创建一个空的对象。 * 如果childVal 存在,则用浅拷贝吧 childVal 合并到res中,返回res对象 */ function mergeAssets( parentVal, childVal, vm, key ) { var res = Object.create(parentVal || null); if (childVal) { "development" !== 'production' && 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. * *观察者散列不应该覆盖一个 *另一个,所以我们将它们合并为数组。 * * 循环childVal。获取到子节点childVal的key如果在父亲节点上面有,则先获取到父亲节点的值,如果父亲节点的上没有值得获取子节点的值。 变成数组存在ret对象中。 */ strats.watch = function ( parentVal, //父节点值 childVal, //子节点值 vm, //vm vue实例化的对象 key) { // key值 // work around Firefox's Object.prototype.watch... 在Firefox的对象周围工作。原型 //// Firefox has a "watch" function on Object.prototype... //var nativeWatch = ({}).watch; if (parentVal === nativeWatch) { parentVal = undefined; } if (childVal === nativeWatch) { childVal = undefined; } /* istanbul ignore if */ if (!childVal) { //如果子节点不存在 则创建一个 对象 return Object.create(parentVal || null) } { //检测childVal是不是对象 assertObjectType(key, childVal, vm); } if (!parentVal) { //如果父节点不存在 则返回子节点 return childVal } var ret = {}; extend(ret, parentVal); //合并对象 一个新的对象 for (var key$1 in childVal) { //循环子节点 var parent = ret[key$1]; // 把子节点的kye放到父节点中 var child = childVal[key$1]; //获取子节点的值 if (parent && !Array.isArray(parent)) { //如果子节点的key放到父节点中能获取到子节点 ,并且子节点不是一个数组 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, vm, key ) { if (childVal && "development" !== 'production') { //判断是否是对象 assertObjectType(key, childVal, vm); } if (!parentVal) { return childVal } var ret = Object.create(null); //对象浅拷贝,参数(to, _from)循环_from的值,会覆盖掉to的值 extend(ret, parentVal); if (childVal) { //对象浅拷贝,参数(to, _from)循环_from的值,会覆盖掉to的值 extend(ret, childVal); } return ret }; strats.provide = mergeDataOrFn; /** * Default strategy. * 如果没有子节点就返回父节点,如果有子节点就返回子节点 */ var defaultStrat = function (parentVal, childVal) { return childVal === undefined ? parentVal : childVal }; /** * Validate component names *验证组件名称 */ function checkComponents(options) { for (var key in options.components) { // 验证组件名称 必须是大小写,并且是-横杆 validateComponentName(key); } } //验证组件名称 必须是大小写,并且是-横杆 function validateComponentName(name) { if (!/^[a-zA-Z][\w-]*$/.test(name)) { warn( 'Invalid component name: "' + name + '". Component names ' + 'can only contain alphanumeric characters and the hyphen, ' + 'and must start with a letter.' ); } 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 * 确保所有props选项语法都规范化为 * Object-based format. * 基于对象格式 * * 检查 props 数据类型 * normalizeProps 检查 props 数据类型,并把type标志打上。如果是数组循环props属性数组,如果val是string则把它变成驼峰写法 res[name] = {type: null}; 。如果是对象也循环props把key变成驼峰,并且判断val是不是对象如果是对象则 res[name] 是{type: val}否则 res[name] 是val。 * */ function normalizeProps(options, vm) { //参数中有没有props var props = options.props; if (!props) { return } 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); res[name] = {type: null}; } else { //当使用数组语法时,道具必须是字符串。 如果是props 是数组必须是字符串 warn('props must be strings when using array syntax.'); } } } else if (isPlainObject(props)) { //如果是对象 for (var key in props) { //for in 提取值 val = props[key]; name = camelize(key); //把含有横岗的字符串 变成驼峰写法 res[name] = isPlainObject(val) //判断值是不是对象 ? val : {type: val}; } } else { //如果不是对象和数组则警告 warn( "Invalid value for option \"props\": expected an Array or an Object, " + "but got " + (toRawType(props)) + ".", vm ); } options.props = res; } /** * Normalize all injections into Object-based format * 将所有注入规范化为基于对象的格式 * * * 将数组转化成对象 比如 [1,2,3]转化成 * normalized[1]={from: 1} * normalized[2]={from: 2} * normalized[3]={from: 3} * * * * */ function normalizeInject(options, vm) { // provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。 // 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。 var inject = options.inject; if (!inject) { return } var normalized = options.inject = {}; if (Array.isArray(inject)) { //如果是数组 for (var i = 0; i < inject.length; i++) { // * 将数组转化成对象 比如 [1,2,3]转化成 // * normalized[1]={from: 1} // * normalized[2]={from: 2} // * normalized[3]={from: 3} normalized[inject[i]] = {from: inject[i]}; } } else if (isPlainObject(inject)) { //如果是对象 for (var key in inject) { var val = inject[key]; normalized[key] = isPlainObject(val) ? extend({from: key}, val) : {from: val}; } } else { warn( "Invalid value for option \"inject\": expected an Array or an Object, " + "but got " + (toRawType(inject)) + ".", vm ); } } /** * Normalize raw function directives into object format. * * 将原始函数指令归一化为对象格式。 * * * normalizeDirectives获取到指令对象值。循环对象指令的值,如果是函数则把它变成dirs[key] = {bind: def, update: def} 这种形式 */ function normalizeDirectives(options) { //获取参数中的指令 var dirs = options.directives; console.log(options) if (dirs) { //如果指令存在 for (var key in dirs) { //循环该指令 var def = dirs[key]; //获取到指令的值 console.log(def) if (typeof def === 'function') { //如果是函数 //为该函数添加一个对象和值 dirs[key] = {bind: def, update: def}; } } } } //判断是否是对象 function assertObjectType(name, value, 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, //子值 优选取子值 vm) { { //检验子组件 checkComponents(child); } if (typeof child === 'function') { //如果child 是函数则获取他的参数 child = child.options; } //检查 props 数据类型 normalizeProps(child, vm); // 将数组转化成对象 比如 [1,2,3]转化成 normalizeInject(child, vm); // * normalizeDirectives获取到指令对象值。循环对象指令的值,如果是函数则把它变成dirs[key] = {bind: def, update: def} 这种形式 normalizeDirectives(child); //子组件是否有需要合并的对象继承 方式 var extendsFrom = child.extends; if (extendsFrom) { //如果有则递归 parent = mergeOptions(parent, extendsFrom, vm); } //如果 子组件有mixins 数组 则也递归合并,继承 方式 mixins 必须是数组 if (child.mixins) { 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) { //循环合并后的key mergeField(key); } for (key in child) { //循环子组件的 if (!hasOwn(parent, key)) { mergeField(key); } } //获取到key 去读取strats类的方法 // strats类 有方法 el,propsData,data,provide,watch,props,methods,inject,computed,components,directives,filters 。 // strats类里面的方法都是 合并数据 如果没有子节点childVal, // 就返回父节点parentVal,如果有子节点childVal就返回子节点childVal。 function mergeField(key) { //defaultStrat 获取子值还是父组的值 var strat =strats[key] || // defaultStrat; //* 如果没有子节点就返回父节点,如果有子节点就返回子节点 //获取子值还是父组的值 options[key] = strat(parent[key], child[key], vm, key); } //返回参数 return options } /** * Resolve an asset. * This function is used because child instances need access * to assets defined in its ancestor chain. * 检测指令是否在 组件对象上面 ,返回注册指令或者组建的对象, 包括检查directives , filters ,components * */ function resolveAsset(options, //参数 type, // 类型:directives , filters ,components id, // 指令的key 属性 warnMissing //警告的信息 true ) { console.log('==resolveAsset==') console.log(options) console.log(type) console.log(id) console.log(warnMissing) /* istanbul ignore if 如果id不是字符串 */ if (typeof id !== 'string') { return } var assets = options[type]; // console.log('==assets==') console.log(assets) // check local registration variations first //首先检查本地注册的变化 检查id是否是assets 实例化的属性或者方法 if (hasOwn(assets, id)) { return assets[id] } // 可以让这样的的属性 v-model 变成 vModel 变成驼峰 var camelizedId = camelize(id); console.log('==camelizedId==') console.log(camelizedId) // 检查camelizedId是否是assets 实例化的属性或者方法 if (hasOwn(assets, camelizedId)) { return assets[camelizedId] } console.log('==assets==') console.log(assets) // 将首字母变成大写 变成 VModel var PascalCaseId = capitalize(camelizedId); console.log('==PascalCaseId==') console.log(PascalCaseId) // 检查PascalCaseId是否是assets 实例化的属性或者方法 if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] } console.log('==assets==') console.log(assets) console.log('==id-camelizedId-PascalCaseId==') console.log(assets) console.log(assets[id]) console.log(assets[camelizedId]) console.log(assets[PascalCaseId]) // fallback to prototype chain 回到原型链 var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; //如果检查不到id 实例化则如果是开发环境则警告 if ("development" !== 'production' && warnMissing && !res) { warn( 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, options ); } console.log('==res==') console.log(res) //返回注册指令或者组建的对象 return res } /* *验证支柱 验证 prosp 是否是规范数据 并且为props 添加 value.__ob__ 属性,把prosp添加到观察者中 * 校验 props 参数 就是组建 定义的props 类型数据,校验类型 * * 判断prop.type的类型是不是Boolean或者String,如果不是他们两类型,调用getPropDefaultValue获取默认值并且把value添加到观察者模式中 * * */ function validateProp( key, //key propOptions, //原始props 参数 propsData, //转义过的组件props数据 vm // VueComponent 组件构造函数 ) { //vm this属性 var prop = propOptions[key]; //获取组件定义的props 属性 var absent = !hasOwn(propsData, key); // 如果该为假的那么可能 a-b 这样的key才能获取到值 var value = propsData[key]; // 获取值 // boolean casting //Boolean 传一个布尔值 但是 一般是函数或者数组函数才有意义,而且是函数声明的函数并不是 函数表达式prop.type 也需要是函数 //返回的是相同的索引 判断 属性类型定义的是否是Boolean var booleanIndex = getTypeIndex(Boolean, prop.type); if (booleanIndex > -1) { //如果是boolean值 if (absent && !hasOwn(prop, 'default')) { //如果key 不是propsData 实例化,或者 没有定义default 默认值的时候 设置value 为false value = false; } else if ( value === '' //如果value 是空 || value === hyphenate(key) //或者key转出 - 形式和value 相等的时候 ) { // // only cast empty string / same name to boolean if 仅将空字符串/相同名称转换为boolean if // boolean has higher priority 获取到相同的 //判断prop.type 的类型是否是string字符串类型 var stringIndex = getTypeIndex(String, prop.type); if ( stringIndex < 0 || //如果匹配不到字符串 booleanIndex < stringIndex) { //或者布尔值索引小于字符串 索引的时候 value = true; } } } // check default value 检查默认值 if (value === undefined) { //如果没有值 value 也不是boolean, 也不是string的时候 // 有可能是 函数 value = getPropDefaultValue(vm, prop, key); // since the default value is a fresh copy, 由于默认值是一个新的副本, // make sure to observe it. 一定要遵守。 var prevShouldObserve = shouldObserve; toggleObserving(true); console.log('===value===') console.log(value); //为 value添加 value.__ob__ 属性,把value添加到观察者中 observe(value); toggleObserving(prevShouldObserve); } { console.log( prop, key, value, vm, absent) //检查prop 是否合格 assertProp( prop, //属性的type值 key, //props属性中的key value, //view 属性的值 vm, // VueComponent 组件构造函数 absent //false ); } return value } /** * Get the default value of a prop. *获取prop 属性默认的vue值 */ function getPropDefaultValue(vm, prop, key) { // no default, return undefined //判断该对象prop 中的default 是否是prop 实例化的 if (!hasOwn(prop, 'default')) { return undefined } var def = prop.default; // warn against non-factory defaults for Object & Array //警告对象和数组的非工厂默认值 if ("development" !== 'production' && 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 ); } // the raw prop value was also undefined from previous render, //原始PROP值也未从先前的渲染中定义, // return previous default value to avoid unnecessary watcher trigger //返回先前的默认值以避免不必要的监视触发器 if (vm && vm.$options.propsData && vm.$options.propsData[key] === undefined && vm._props[key] !== undefined ) { return vm._props[key] } // call factory function for non-Function types //非功能类型调用工厂函数 // a value is Function if its prototype is function even across different execution context //一个值是函数,即使它的原型在不同的执行上下文中也是函数。 //getType检查函数是否是函数声明 如果是函数表达式或者匿名函数是匹配不上的 //判断def 是不是函数 如果是则执行,如果不是则返回props的PropDefaultValue return typeof def === 'function' && getType(prop.type) !== 'Function' ? def.call(vm) : def } /** * Assert whether a prop is valid. * 断言一个属性是否有效。 * * * * prop, //属性的type值 key, //props属性中的key value, //view 属性的值 vm, //组件构造函数 absent //false */ function assertProp( prop, //属性的type值 name, //props属性中的key value, //view 属性的值 vm, //组件构造函数 absent//false ) { //必须有required 和 absent if (prop.required && absent) { warn( 'Missing required prop: "' + name + '"', vm ); return } //如果vual 为空 或者 不是必填项 则不执行下面代码 if (value == null && !prop.required) { return } //类型 var type = prop.type; //如果类型为真 或者类型 不存在 var valid = !type || type === true; var expectedTypes = []; if (type) { //如果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( "Invalid prop: type check failed for prop \"" + name + "\"." + " Expected " + (expectedTypes.map(capitalize).join(', ')) + ", got " + (toRawType(value)) + ".", vm ); return } var validator = prop.validator; if (validator) { if (!validator(value)) { warn( 'Invalid prop: custom validator check failed for prop "' + name + '".', vm ); } } } //检测数据类型 是否是String|Number|Boolean|Function|Symbol 其中的一个数据类型 var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/; //获取type类型 function assertType(value, type) { var valid; //getType检查函数是否是函数声明 如果是函数表达式或者匿名函数是匹配不上的 //type 必须是String|Number|Boolean|Function|Symbol 构造函数 var expectedType = getType(type); //检测改函数是什么类型 if (simpleCheckRE.test(expectedType)) { //type 必须是String|Number|Boolean|Function|Symbol 构造函数 这里才为真 (String|Number|Boolean|Function|Symbol) var t = typeof value; //转换成小写 valid = t === expectedType.toLowerCase(); //布尔值 // for primitive wrapper objects 对于原始包装对象 if (!valid && t === 'object') { valid = value instanceof type; } } else if (expectedType === 'Object') { //检测是否是真正的对象 valid = isPlainObject(value); } else if (expectedType === 'Array') { //检测是否是真正的数组 valid = Array.isArray(value); } else { //判断 value 是否是type中的实例化对象 valid = value instanceof type; } //返回出去值 return { valid: valid, expectedType: expectedType } } /** * Use function string name to check built-in types, * because a simple equality check will fail when running * across different vms / iframes. * 检查函数是否是函数声明 如果是函数表达式或者匿名函数是匹配不上的 * * */ 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) } //判断expectedTypes 中的函数和 type 函数是否有相等的如有有则返回索引index 如果没有则返回-1 function getTypeIndex(type, expectedTypes) { //如果不是数组直接比较 如果真则返回0 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 } /* 向外暴露了一个 handleError 方法,在需要捕获异常的地方调用。 handleError 方法中首先获取到报错的组件,之后递归查找当前组件的父组件, 依次调用 errorCaptured 方法。在遍历调用完所有 errorCaptured 方法、或 errorCaptured 方法有报错时, 会调用 globalHandleError 方法。 globalHandleError 方法调用了全局的 errorHandler 方法。 如果 errorHandler 方法自己又报错了呢?生产环境下会使用 console.error 在控制台中输出。 可以看到 errorCaptured 和 errorHandler 的触发时机都是相同的,不同的是 errorCaptured 发生在前, 且如果某个组件的 errorCaptured 方法返回了 false,那么这个异常信息不会再向上冒泡也不会再调用 errorHandler 方法。 */ function handleError(err, vm, info) { if (vm) { var cur = vm; //循环父组件 while ((cur = cur.$parent)) { //如果hooks 存在 则循环 所有的hooks var hooks = cur.$options.errorCaptured; if (hooks) { for (var i = 0; i < hooks.length; i++) { try { //调用hooks 中函数,如果发生错误则调用globalHandleError var capture = hooks[i].call(cur, err, vm, info) === false; if (capture) { return } } catch (e) { //调用全局日志输出 globalHandleError(e, cur, 'errorCaptured hook'); } } } } } //调用全局日志输出 globalHandleError(err, vm, info); } function globalHandleError(err, vm, info) { //如果errorHandler 存在 则调用 errorHandler函数 if (config.errorHandler) { try { return config.errorHandler.call(null, err, vm, info) } catch (e) { //错误日志信息输出 logError(e, null, 'config.errorHandler'); } } logError(err, vm, info); } //错误日志信息输出 function logError(err, vm, info) { { warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm); } /* istanbul ignore else 如果是浏览器或者是 微信端,输出console */ if ((inBrowser || inWeex) && typeof console !== 'undefined') { console.error(err); } else { //如果是服务器端 则抛出错误 throw err } } /* */ /* globals MessageChannel 全局消息通道 */ //回调函数队列 var callbacks = []; var pending = false; // 触发 callbacks 队列中的函数 function flushCallbacks() { pending = false; //.slice(0) 浅拷贝 var copies = callbacks.slice(0); callbacks.length = 0; console.log(copies) for (var i = 0; i < copies.length; i++) { //执行回调函数 copies[i](); } } // Here we have async deferring wrappers using both microtasks and (macro) tasks. 在这里,我们使用了微任务和宏任务的异步包装器。 // In < 2.4 we used microtasks everywhere, but there are some scenarios where 在< 2.4中,我们到处使用微任务,但也有一些场景。 // microtasks have too high a priority and fire in between supposedly 微任务优先级太高,据称介于两者之间。 // sequential events (e.g. #4521, #6690) or even between bubbling of the same 序贯事件(例如α4521,α6690),甚至在同一气泡之间 // event (#6566). However, using (macro) tasks everywhere also has subtle problems 事件(α6566)。然而,到处使用(宏)任务也有微妙的问题。 // when state is changed right before repaint (e.g. #6813, out-in transitions). 当状态在重新绘制之前被正确改变(例如,α6813,在过渡中出现)。 // Here we use microtask by default, but expose a way to force (macro) task when 这里,我们默认使用微任务,但是暴露一种方法来强制(宏)任务 // needed (e.g. in event handlers attached by v-on). 需要的(例如在事件处理程序中附加的V-on)。 var microTimerFunc; //微计时器功能 var macroTimerFunc; //宏计时器功能 var useMacroTask = false; //使用宏任务 // Determine (macro) task defer implementation. 确定(宏)任务延迟实现。 // Technically setImmediate should be the ideal choice, but it's only available 技术上应该是理想的选择,但它是唯一可用的。 // in IE. The only polyfill that consistently queues the callback after all DOM 在IE.中,唯一的填充在所有DOM之后始终排队回叫。 // events triggered in the same loop is by using MessageChannel. 在同一循环中触发的事件是通过使用消息通道。 /* istanbul ignore if */ //判断setImmediate 是否存在,如果存在则判断下是是否是系统内置函数 if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { //函数表达式赋值给macroTimerFunc macroTimerFunc = function () { setImmediate(flushCallbacks); }; } else if (typeof MessageChannel !== 'undefined' && ( isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === '[object MessageChannelConstructor]' )) { //如果有 消息体 内置函数则实例化 var channel = new MessageChannel(); //获取端口2 var port = channel.port2; //设置端口1 的接受函数为flushCallbacks channel.port1.onmessage = flushCallbacks; //端口2推送信息给端口1 macroTimerFunc = function () { port.postMessage(1); }; } else { /* istanbul ignore next */ // 异步执行 macroTimerFunc = function () { setTimeout(flushCallbacks, 0); }; } // Determine microtask defer implementation. //确定微任务延迟执行。 /* istanbul ignore next, $flow-disable-line */ if (typeof Promise !== 'undefined' && isNative(Promise)) { // 声明一个成功的 Promise var p = Promise.resolve(); //microTimerFunc 一个异步 队列函数 microTimerFunc = function () { p.then(flushCallbacks); // in problematic UIWebViews, Promise.then doesn't completely break, but 在有问题的UIWebVIEW中,Promise.then并没有完全崩溃,而是 // it can get stuck in a weird state where callbacks are pushed into the 它可能会陷入一种怪异的状态,其中回调被推到 // microtask queue but the queue isn't being flushed, until the browser 微任务队列,但队列没有刷新,直到浏览器 // needs to do some other work, e.g. handle a timer. Therefore we can 需要做一些其他的工作,例如处理计时器。因此我们可以 // "force" the microtask queue to be flushed by adding an empty timer. [强制]通过添加空计时器来刷新微任务队列。 //如果是ios 执行下 noop 空函数 if (isIOS) { setTimeout(noop); } }; } else { // fallback to macro //回归宏 microTimerFunc = macroTimerFunc; } /** * Wrap a function so that if any code inside triggers state change, 包装一个函数,如果内部的任何代码触发状态改变, * the changes are queued using a (macro) task instead of a microtask. 使用宏(宏)任务而不是微任务对这些队列进行排队 */ function withMacroTask(fn) { //宏任务 return fn._withTask || (fn._withTask = function () { useMacroTask = true; var res = fn.apply(null, arguments); useMacroTask = false; return res }) } //为callbacks 收集队列cb 函数 并且根据 pending 状态是否要触发callbacks 队列函数 function nextTick(cb, ctx) { //cb 回调函数 //ctx this的指向 var _resolve; //添加一个回调函数到队列里面去 callbacks.push(function () { if (cb) { //如果cb存在 并且是一个函数就执行 try { cb.call(ctx); } catch (e) { //如果不是函数则报错 handleError(e, ctx, 'nextTick'); } } else if (_resolve) { //_resolve 如果存在则执行 _resolve(ctx); } }); console.log('==callbacks==') console.log(callbacks) console.log(pending) if (!pending) { pending = true; //执行异步宏任务 if (useMacroTask) { macroTimerFunc(); //异步触发 或者 实现观察者 触发 callbacks 队列中的函数 } else { microTimerFunc(); //异步触发 或者 实现观察者 触发 callbacks 队列中的函数 } } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { //如果回调函数不存在 则声明一个Promise 函数 return new Promise(function (resolve) { _resolve = resolve; }) } } /* */ var mark; var measure; { //浏览器性能监控 var perf = inBrowser && window.performance; /* istanbul ignore if */ if ( perf && perf.mark && perf.measure && perf.clearMarks && perf.clearMeasures ) { mark = function (tag) { return perf.mark(tag); }; measure = function (name, startTag, endTag) { perf.measure(name, startTag, endTag); perf.clearMarks(startTag); perf.clearMarks(endTag); perf.clearMeasures(name); }; } } /* not type checking this file because flow doesn't play well with Proxy 不检查此文件,因为流不能很好地使用代理 * */ var initProxy; { //map 对象中的[name1,name2,name3,name4] 变成这样的map{name1:true,name2:true,name3:true,name4:true} /*全局api 匹配'Infinity,undefined,NaN,isFinite,isNaN,' + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + 'require' */ 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 ); //不存在的key 发出警告 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 ); }; //判断 系统内置 函数有没有 es6的Proxy 代理对象api var hasProxy = typeof Proxy !== 'undefined' && isNative(Proxy); if (hasProxy) { //这些修改键就是 Shift、Ctrl、Alt和 Meta(在 Windows键盘中是 Windows键,在苹果机中 是 Cmd 键)它们经常被用来修改鼠标事件的行为。 var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact'); //声明代理拦截对象 config.keyCodes = new Proxy(config.keyCodes, { set: function set(target, key, value) { if (isBuiltInModifier(key)) { //匹配键盘上的快捷键 'stop,prevent,self,ctrl,shift,alt,meta,exact' //避免在配置键代码中重写内置修改器: 在一些快捷键中不需要加vue事件修饰器 warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key)); return false } else { //记录不是快捷键的键盘码 target[key] = value; return true } } }); } var hasHandler = { has: function has(target, key) { var has = key in target; //是否含有全局api 就是window 的内置函数 //全局api // 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 isAllowed = allowedGlobals(key) || key.charAt(0) === '_'; //如果 key 在target对象中 不存在 或者 isAllowed 不是全局api 并且 第一个字符不是_的时候 发出警告 if (!has && !isAllowed) { //不存在key发出警告 warnNonPresent(target, key); } //返回true return has || !isAllowed } }; var getHandler = { get: function get(target, key) { //key必须是等于string 并且 key在target中含有属性或者方法 if (typeof key === 'string' && !(key in target)) { //如果没有则发出警告 warnNonPresent(target, key); } //返回target值 return target[key] } }; //初始化 代理 监听 initProxy = function initProxy(vm) { if (hasProxy) { // determine which proxy handler to use 确定使用哪个代理处理程序 var options = vm.$options; //获取vm中的参数 //render 渲染 如果是渲染 并且含有_withStripped var handlers = options.render && options.render._withStripped ? getHandler //获取值 : hasHandler; //判断内部函数,这样vue中模板就可以使用内置函数 //实例化 代理对象,只是这里添加了 警告的日志而已 vm._renderProxy = new Proxy(vm, handlers); } else { //如果不能代理直接赋值 vm._renderProxy = vm; } }; } /* * 实例化set对象 * */ var seenObjects = new _Set(); /** * Recursively traverse an object to evoke all converted 递归遍历对象以唤起所有转换 * getters, so that every nested property inside the object 吸收器,以便对象内的每个嵌套属性 * is collected as a "deep" dependency. 被收集为一个“深度”依赖。 * 为 seenObjects 深度收集val 中的key */ function traverse(val) { // 搜索seen 为seen添加depId //seenObjects set对象 // 为 seenObjects 深度收集val 中的key _traverse(val, seenObjects); //清除对象 给对象置空 seenObjects.clear(); } //搜集依赖 /* * 搜索seen 为seen添加depId * 为 seenObjects 深度收集val 中的key * * */ //看到这里 function _traverse(val, seen) { console.log(val) console.log(seen.add) var i, keys; //判断是否是数组 var isA = Array.isArray(val); //isFrozen 方法判断一个对象是否被冻结。 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen //val 是否是被VNode 实例化 if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { return } console.log(val.__ob__) //如果val 有__ob__ 属性 if (val.__ob__) { var depId = val.__ob__.dep.id; // seen 中是否含有depId 属性或者方法 if (seen.has(depId)) { return } console.log(seen.add) // seen 是 seenObjects = new _Set(); add 就是set对象中的add方法,添加为一的值得key //如果没有则添加进去 seen.add(depId); } //如果是数组 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函数主要用于将传入的带有特殊前缀的事件修饰符分解为具有特定值的事件对象 * cachedFn * function cached(fn) { var cache = Object.create(null); return (function cachedFn(str) { var hit = cache[str]; return hit || (cache[str] = fn(str)) }) } * normalizeEvent 得到的是一个函数 如果传入的 name 中 在cache 对象中有值 则返回这个值 * 如果该对象没有值则 调用该函数 并且用返回值 记录 当前执行函数返回值记录起来 * */ //该函数是过滤 vue 事件中的修饰符 var normalizeEvent = cached(function (name) { //判断第一个字符是否是'& var passive = name.charAt(0) === '&'; //slice(),返回一个新的字符串,该方法可从已有的数组中,或者字符串中返回选定的元素。 name = passive ? name.slice(1) : name; //判断第一个字符串是否是~ var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first //slice(),返回一个新的字符串,该方法可从已有的数组中,或者字符串中返回选定的元素。 name = once$$1 ? name.slice(1) : name; //判断第一个位是否是 ! var capture = name.charAt(0) === '!'; //slice(),返回一个新的字符串,该方法可从已有的数组中,或者字符串中返回选定的元素。 name = capture ? name.slice(1) : name; return { name: name, once: once$$1, capture: capture, passive: passive } }); //createFnInvoker 创建一个调用程序 创建一个钩子函数 //createFnInvoker,如果事件只是个函数就为为事件添加多一个静态类, invoker.fns = fns; 把真正的事件放在fns。而 invoker 则是转义fns然后再运行fns function createFnInvoker(fns) { function invoker() { //获取传进来的参数,是一个数组 var arguments$1 = arguments; //静态方法传进来的函数 赋值给fns var fns = invoker.fns; //判断fns 是否是一个数组 if (Array.isArray(fns)) { //如果是数组 浅拷贝 var cloned = fns.slice(); //执行fns 数组中的函数 并且把 invoker arguments$1参数一个个传给fns 函数中 for (var i = 0; i < cloned.length; i++) { cloned[i].apply(null, arguments$1); } } else { // return handler return value for single handlers //如果fns 不是数组函数,而是一个函数 则执行arguments$1参数一个个传给fns 函数中 return fns.apply(null, arguments) } } invoker.fns = fns; return invoker //静态类 } //更新事件 并且为新的值 添加函数 旧的值删除函数等功能 function updateListeners( on, //新的事件 oldOn, //旧的事件 add, //添加事件函数 remove$$1, //删除事件函数 vm//vue 实例化对象 ) { var name, def, cur, old, event; for (name in on) { // 遍历on def = cur = on[name]; //on 新的事件值 old = oldOn[name]; //oldOn 对象中的 与 name 匹配 并且赋值 old 估计这个是旧的值 event = normalizeEvent(name); //normalizeEvent 如果是事件,则过滤 事件修饰符 /* istanbul ignore if */ // isUndef 判断值存在 并且是空的 return v === undefined || v === null if (isUndef(cur)) { //如果不是生产环境 "development" !== 'production' && warn( "Invalid handler for event \"" + (event.name) + "\": got " + String(cur), vm ); } else if (isUndef(old)) { //判断旧的值是否存在 为空的时候 没有定义旧的事件 if (isUndef(cur.fns)) { //如果函数不存在 则绑定函数 //函数 获取钩子函数 // 创建函数调用器并重新复制给cur和on[name] cur = on[name] = createFnInvoker(cur); //这个时候cur.fns就存在了 } name = '&' + name; // mark the event as passive 将事件标记为被动的 //添加事件 add( event.name, //事件名称 cur, // 转义过的事件 执行静态类 event.once, //是否只触发一次的状态 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); } } } /* * * 合并vue vnode 钩子函数, * def[hookKey] = invoker; //把钩子函数用对象存起来 * */ function mergeVNodeHook(def, hookKey, hook) { //判断def 是否 是vnode 实例化的对象 if (def instanceof VNode) { // 重新赋值def 把def.data.hook 赋值给def def = def.data.hook || (def.data.hook = {}); } var invoker; //获取旧的oldHook 钩子 var oldHook = def[hookKey]; function wrappedHook() { //执行钩子函数 hook.apply(this, arguments); // important: remove merged hook to ensure it's called only once // and prevent memory leak //重要:删除合并钩子以确保只调用一次 //和防止内存泄漏 remove(invoker.fns, wrappedHook); } if (isUndef(oldHook)) { //如果旧的钩子函数没有 为空的时候 // no existing hook 无现有钩 则创建一个钩子函数 invoker = createFnInvoker([wrappedHook]); } else { /* istanbul ignore if 如果有老的钩子函数,并且fns钩子函数存在 并且已经合并过*/ if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { // already a merged invoker 已合并的调用程序 invoker = oldHook; //直接老的钩子函数直接覆盖新的钩子函数 //为钩子函数的fns 添加一个函数 invoker.fns.push(wrappedHook); } else { // existing plain hook invoker = createFnInvoker([oldHook, wrappedHook]); } } invoker.merged = true; //把钩子函数用对象存起来 def[hookKey] = invoker; } /* extractPropsFromVNodeData 从 props属性中获取vnode数据 extractPropsFromVNodeData循环propOptions对象,把驼峰的key转换成横杠的key。校验props属性的key是否和attrs属性值相同,如果相同删除掉attrs属性的同样key的值。获取props属性的值添加搞res对象中,返回出去 * * */ function extractPropsFromVNodeData( data, //tag标签属性数据 Ctor, //组件构造函数VueComponent tag //tag标签名称 ) { // we are only extracting raw values here. // validation and default values are handled in the child // component itself. //我们只是在这里提取原始值。 //验证和默认值在孩子中被处理 //组件本身。 //获取Ctor 参数中的 props var propOptions = Ctor.options.props; //获取组件的props属性 console.log(Ctor.options) //如果propOptions 属性是空或者不存在 这不执行下面代码 if (isUndef(propOptions)) { return } var res = {}; var attrs = data.attrs; var props = data.props; //如果data中的属性attrs或者props 属性 数据存在 if (isDef(attrs) || isDef(props)) { //遍历propOptions props属性中的值 for (var key in propOptions) { //altKey获取到一个函数,该函数功能是把 abCd 驼峰字母改写成 ab-c 如果是 aB cd 则是 ab cd //大写字母,加完减号又转成小写了 比如把驼峰 aBc 变成了 a-bc //匹配大写字母并且两面不是空白的 替换成 '-' + '字母' 在全部转换成小写 var altKey = hyphenate(key); { //把key 转换成小写 var keyInLowerCase = key.toLowerCase(); //如果他们key不相同 并且 属性attrs存在 并且keyInLowerCase 属性存在 attrs对象中 if ( key !== keyInLowerCase && attrs && hasOwn(attrs, keyInLowerCase) ) { //输出一个警告信息 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 + "\"." ); } } //检查属性 checkProp( res, //空对象 props, //props 属性 key, //propOptions 的原始key altKey, //转换后的 横杆key true ) || checkProp( res, attrs, key, altKey, false ); } } return res } //检查 属性 检查key和altKey 在hash属性对象中有没有,如果有则赋值给res对象 function checkProp( res, //需要添加值的对象 hash, // 属性对象 key, // 原始key altKey, //转换后的 横杆key preserve //是否要删除hash 对象中的属性或者方法 状态 布尔值 ) { //hash 值存在 if (isDef(hash)) { //如果是hash对象中含有key 属性或者方法 if (hasOwn(hash, key)) { //添加res值 res[key] = hash[key]; //preserve 不存在的时候则在hash对象中删除该key 属性或者方法 if (!preserve) { delete hash[key]; } return true } else if (hasOwn(hash, altKey)) { //如果是hash对象中含有altKey 属性或者方法 //添加res值 res[key] = hash[altKey]; //preserve 不存在的时候则在hash对象中删除该key 属性或者方法 if (!preserve) { delete hash[altKey]; } return true } } return false } /* */ // The template compiler attempts to minimize the need for normalization by 模板编译器试图最小化对规范化的需要。 // statically analyzing the template at compile time. 在编译时静态分析模板。 // // For plain HTML markup, normalization can be completely skipped because the 对于普通HTML标记,可以完全跳过标准化,因为 // generated render function is guaranteed to return Array. There are 生成的渲染函数保证返回数组。有 // two cases where extra normalization is needed: 需要额外标准化的两种情况: // 1. When the children contains components - because a functional component 当儿童包含组件时,因为函数组件 // may return an Array instead of a single root. In this case, just a simple 可以返回数组而不是单个根。在这种情况下,只是一个简单的例子 // normalization is needed - if any child is an Array, we flatten the whole 规范化是必要的-如果任何一个孩子是一个数组,我们扁平化整个 // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep 和Array.prototype.concat在一起。保证仅为1级深 // because functional components already normalize their own children. 因为功能组件已经规范了他们自己的孩子。 //循环子节点children,把他连在一起,其实就是把伪数组变成真正的数组 function simpleNormalizeChildren(children) { for (var i = 0; i < children.length; i++) { if (Array.isArray(children[i])) { return Array.prototype.concat.apply([], children) } } return children } // 2. When the children contains constructs that always generated nested Arrays, 2。当子类包含总是生成嵌套数组的结构时, // e.g.