vue源碼業餘時間差很少看了一年,之前在網上找帖子,發現不少帖子很零散,都是一部分一部分說,斷章的不少,因此本身下定決定一行行看,通過本身堅持與努力,如今基本看完了,差ddf那部分,由於考慮到本身要換工做了,因此暫緩下來先,ddf那塊後期我會補上去。這個vue源碼逐行分析,我基本每一行都打上註釋,加上整個框架的流程思惟導圖,基本上是小白也能看懂的vue源碼了。
說的很是的詳細,裏面的源碼註釋,有些是參考網上帖子的,有些是本身多年開發vue經驗而猜想的,有些是本身跑上下文程序知道的,本人水平可能有限,不必定是很正確,若是有不足的地方能夠聯繫我QQ羣 :302817612 修改,或者發郵件給我281113270@qq.com 謝謝。 javascript
1.vue源碼解讀流程 1.nwe Vue 調用的是 Vue.prototype._init 從該函數開始 通過 $options 參數合併以後 initLifecycle 初始化生命週期標誌 初始化事件,初始化渲染函數。初始化狀態就是數據。把數據添加到觀察者中實現雙數據綁定。css
2.雙數據綁定原理是:obersve()方法判斷value沒有沒有__ob___屬性而且是否是Obersve實例化的,
value是否是Vonde實例化的,若是不是則調用Obersve 去把數據添加到觀察者中,爲數據添加__ob__屬性, Obersve 則調用defineReactive方法,該方法是鏈接Dep和wacther方法的一個通道,利用Object.definpropty() 中的get和set方法 監聽數據。get方法中是new Dep調用depend()。爲dep添加一個wacther類,watcher中有個方法是更新視圖的是run調用update去更新vonde 而後更新視圖。 而後set方法就是調用dep中的ontify 方法調用wacther中的run 更新視圖
3.vue從字符串模板怎麼到真實的dom呢?是經過$mount掛載模板,就是獲取到html,而後經過paseHTML這個方法轉義成ast模板,他大概算法是 while(html) 若是匹配到開始標籤,結束標籤,或者是屬性,都會截取掉html,而後收集到一個對象中,知道循環結束 html被截取完。最後變成一個ast對象,ast對象好了以後,在轉義成vonde 須要渲染的函數,好比_c('div' s('')) 等這類的函數,編譯成vonde 虛擬dom。而後到updata更新數據 調用__patch__ 把vonde 經過ddf算法變成正真正的dom元素。html
具體看我源碼和流程圖,這裏文字就不描述這麼多了,流程圖是下面這中的網盤,源碼是vue.js,基本每一行都有註釋,而後diff待更新中。前端
程序流程圖太大了無法在線看,只能網盤下載到本地看了,給一個大概圖vue
連接:https://pan.baidu.com/s/10IxV6mQ2TIwkRACKu2T0ng
提取碼:1fnu
html5
github源碼,包括日常我看vue中的一些小demo。看到該帖子朋友,幫忙去github點贊下,謝謝,有你的支持,我會更加努力。java
https://github.com/qq281113270/vue node
/*! * 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 '<Root>' } /* * 若是 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不存在那麼返回‘<Anonymous>’+ 若是file存在而且includeFile!==false的時候 返回" at " + file 不然爲空 * * */ return ( (name ? ("<" + (classify(name)) + ">") : "<Anonymous>") + (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<VNode>. There are 生成的渲染函數保證返回數組<VNoCT>。有 // 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. <template>, <slot>, v-for, or when the children is provided by user 例如,模板縫隙><>、<V時,或當孩子由用戶提供 // with hand-written render functions / JSX. In such cases a full normalization 具備手寫渲染功能/JSX。在這種狀況下,徹底歸一化。 // is needed to cater to all possible types of children values. 須要知足全部可能的兒童價值類型。 //判斷children的數據類型 而建立不一樣的虛擬dom vonde function normalizeChildren(children) { return isPrimitive(children) ? //判斷數據類型是不是string,number,symbol,boolean [createTextVNode(children)] // 建立一個文本節點 : Array.isArray(children) ? //判斷是不是數組 normalizeArrayChildren(children) //建立一個規範的子節點數組。 : undefined } //判斷是不是文本節點 function isTextNode(node) { return isDef(node) && isDef(node.text) && isFalse(node.isComment) } //規範的子節點 // normalizeArrayChildren接收 2 個參數, // children 表示要規範的子節點,nestedIndex 表示嵌套的索引, // 主要的邏輯就是遍歷 children,得到單個節點 c,而後對 c 的類型判斷, // 若是是一個數組類型,則遞歸調用 normalizeArrayChildren; // 若是是基礎類型,則經過 createTextVNode 方法轉換成 VNode 類型; // 不然就已是 VNode 類型了,若是 children // 是一個列表而且列表還存在嵌套的狀況,則根據 nestedIndex // 去更新它的 key。這裏須要注意一點,在遍歷的過程當中, // 對這 3 種狀況都作了以下處理:若是存在兩個連續的 text 節點, // 會把它們合併成一個 text 節點。 // 由於單個 child 多是一個數組類型。把這個深層的數組遍歷到一層數組上面去。若是是深層數組則調用遞.歸 normalizeArrayChildren function normalizeArrayChildren( children, nestedIndex ) { var res = []; var i, c, lastIndex, last; for (i = 0; i < children.length; i++) { //循環數組子節點children c = children[i]; //判斷是不是空 而且 c是一個布爾值的時候 if (isUndef(c) || typeof c === 'boolean') { continue } // 獲取 res 數組的長度 lastIndex = res.length - 1; //獲取res 最後一個數據 last = res[lastIndex]; // nested if (Array.isArray(c)) { //若是c 子節點仍是一個數組 if (c.length > 0) { //而且 長度 不爲0 //數組則用遞歸 nestedIndex 有多是 0_0 0_0_0 0_0_1 0_0_2 0_1 0_1_0 0_1_1 0_1_2 //若是含有子節點,則遞歸,把全部子節點變成文本節點 c = normalizeArrayChildren(c, ((nestedIndex || '') + "_" + i)); // merge adjacent text nodes 合併相鄰文本節點 //若是c[0] 中的第一個是文本節點 而且 res 最後一個節點是 文本節點 if (isTextNode(c[0]) && isTextNode(last)) { //建立一個文本節點 而且是合併他們的文本內容 res[lastIndex] = createTextVNode(last.text + (c[0]).text); //從c 出棧第一個數據 c.shift(); } //res 添加 數據 至關於 concat 連接數組 res.push.apply(res, c); } } else if (isPrimitive(c)) { //判斷數據類型是不是string,number,symbol,boolean //若是res最後數據一個是文本節點 if (isTextNode(last)) { // merge adjacent text nodes 合併相鄰文本節點 // this is necessary for SSR hydration because text nodes are 這對於SSR水化是必要的,由於文本節點是 // essentially merged when rendered to HTML strings 當呈現到HTML字符串時本質上合併 // 建立文本節點 res[lastIndex] = createTextVNode(last.text + c); } else if (c !== '') { //c不等於空 // convert primitive to vnode //轉換成 vnode 建立 文本節點 res.push(createTextVNode(c)); } } else { //若是c 中的第一個是文本節點 而且 res 最後一個節點是 文本節點 if (isTextNode(c) && isTextNode(last)) { // merge adjacent text nodes 合併相鄰文本節點 //建立文本節點 res[lastIndex] = createTextVNode(last.text + c.text); } else { // default key for nested array children (likely generated by v-for) //嵌套數組子的默認鍵 可能v-for產生的 if ( isTrue(children._isVList) && //若是children._isVList 爲true isDef(c.tag) && //c.tag 不爲空 isUndef(c.key) && //c.key 爲空的時候 isDef(nestedIndex)) { //nestedIndex不爲空 //賦值key的值爲__vlist+1+"_" + 1 + "__"; c.key = "__vlist" + nestedIndex + "_" + i + "__"; } //把VNode 添加到res 中 res.push(c); } } } console.log(res) //返回 res 值 return res } /* 判斷是不是對象 若是是則合併起來 */ function ensureCtor(comp, base) { //https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag if ( //__webpack_require__.n會判斷module是否爲es模塊,當__esModule爲true的時候,標識module爲es模塊,那麼module.a默認返回module.default,不然返回module。 //https://segmentfault.com/a/1190000010955254 comp.__esModule || //若是 comp.__esModule 存在 (hasSymbol && comp[Symbol.toStringTag] === 'Module') //或者 支持hasSymbol 類型 而且判斷 對象類的標籤屬性是Module "[object Module]" ) { //將 comp 默認屬性給 comp comp = comp.default; } //若是comp 是對象 則合併 base,不然返回comp return isObject(comp) ? base.extend(comp) : comp } //createAsyncPlaceholder 建立簡單的佔位符 建立一個節點 //解決異步組件 function createAsyncPlaceholder(factory, //工廠 data, //數據 context, //語境 children, //子節點 tag) { //標籤 //建立一個空節點 var node = createEmptyVNode(); node.asyncFactory = factory; /*異步工廠*/ node.asyncMeta = { data: data, context: context, children: children, tag: tag }; return node } // 解析異步組件 更新數據 function resolveAsyncComponent( factory, //函數工廠 baseCtor, //構造函數或者vue context //vue實例化 對象 ) { console.log(factory); console.log(baseCtor); console.log(context); //若是 有錯誤 則返回錯誤信息 if (isTrue(factory.error) && isDef(factory.errorComp)) { return factory.errorComp } //成功狀態 if (isDef(factory.resolved)) { return factory.resolved } //等待狀態 if (isTrue(factory.loading) && isDef(factory.loadingComp)) { return factory.loadingComp } //環境 if (isDef(factory.contexts)) { // already pending 已經等待 factory.contexts.push(context); } else { var contexts = factory.contexts = [context]; //轉化成數組 var sync = true; //渲染 var forceRender = function () { for (var i = 0, l = contexts.length; i < l; i++) { //更新數據 觀察者數據 contexts[i].$forceUpdate(); } }; //成功 狀態渲染 var resolve = once(function (res) { //確保只是渲染一次 // cache resolved factory.resolved = ensureCtor(res, baseCtor); // invoke callbacks only if this is not a synchronous resolve // (async resolves are shimmed as synchronous during SSR) //只有在這不是同步解析時才調用回調 //(異步解析在SSR期間以同步的方式進行調整) if (!sync) { //渲染組件更新數據 forceRender(); } }); //失敗狀態 var reject = once(function (reason) { "development" !== 'production' && warn( "Failed to resolve async component: " + (String(factory)) + (reason ? ("\nReason: " + reason) : '') ); if (isDef(factory.errorComp)) { factory.error = true; //渲染組件更新數據 forceRender(); } }); var res = factory(resolve, reject); if (isObject(res)) { //若是是對象 代表支持promise //若是 then 是函數 if (typeof res.then === 'function') { // () => Promise 執行 promise if (isUndef(factory.resolved)) {//沒有定義 resolved 成功 res.then(resolve, reject); //執行 then } } else if (isDef(res.component) && typeof res.component.then === 'function') { //若是組件有定義而且有值,並且組件是異步的then是函數 res.component.then(resolve, reject); //執行組件的異步 if (isDef(res.error)) { //若是有錯誤則 把錯誤合併 factory.errorComp = ensureCtor(res.error, baseCtor); } if (isDef(res.loading)) { //若是組件在加載 //則合併組件加載時候baseCtor合併 factory.loadingComp = ensureCtor(res.loading, baseCtor); if (res.delay === 0) { //delay 在加載等待 factory.loading = true; } else { setTimeout(function () { //若是沒有resolved成功 而且沒有錯誤 if (isUndef(factory.resolved) && isUndef(factory.error)) { factory.loading = true; //渲染組件更新數據 forceRender(); } }, res.delay || 200); } } if (isDef(res.timeout)) { //若是有定義通常渲染時間 setTimeout(function () { if (isUndef(factory.resolved)) { //沒有執行成功 reject( // 則執行失敗 "timeout (" + (res.timeout) + "ms)" ); } }, res.timeout); } } } sync = false; // return in case resolved synchronously 在同步解析的狀況下返回 return factory.loading ? factory.loadingComp : factory.resolved } } /* * 判斷是不是異步的 * */ function isAsyncPlaceholder(node) { return node.isComment && node.asyncFactory } /* * 獲取第一個子組件而且子組件有options參數,而且是異步組件的 * * */ function getFirstComponentChild(children) { if (Array.isArray(children)) { //若是組件是個數組 for (var i = 0; i < children.length; i++) { //循環子組件 var c = children[i]; //若是子組件存在,而且子組件有options參數,不是空組件的,而且是異步組件的 if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) { return c } } } } /* * 初始化事件 * */ function initEvents(vm) { vm._events = Object.create(null); vm._hasHookEvent = false; // init parent attached events 初始化 父親事件 var listeners = vm.$options._parentListeners; if (listeners) { //更新組件事件 updateComponentListeners(vm, listeners); } } var target; /** * 添加事件 * event 添加事件名稱 * fn 函數 * * */ function add(event, fn, once) { if (once) { //第一個參數是事件類型,第二個參數是事件的函數 target.$once(event, fn); } else { //第一個參數是事件類型,第二個參數是事件的函數 target.$on(event, fn); } } //解綁事件 function remove$1(event, fn) { target.$off(event, fn); } //更新組件事件 function updateComponentListeners(vm, //虛擬dom listeners, //新的數據隊列 oldListeners //舊的事件數據隊列 ) { target = vm; //更新數據源 而且爲新的值 添加函數 舊的值刪除函數等功能 updateListeners(listeners, oldListeners || {}, add, remove$1, vm); target = undefined; } /* 初始化事件綁定方法 * */ function eventsMixin(Vue) { var hookRE = /^hook:/; //開頭是^hook: 的字符串 /* * 添加綁定事件 * vm._events[event] * */ Vue.prototype.$on = function (event, fn) { var this$1 = this; var vm = this; //若是事件是數組 if (Array.isArray(event)) { for (var i = 0, l = event.length; i < l; i++) { //綁定事件 this$1.$on(event[i], fn); } } else { //把全部事件拆分存放到_events 數組中 (vm._events[event] || (vm._events[event] = [])).push(fn); // optimize hook:event cost by using a boolean flag marked at registration // instead of a hash lookup //若是是 hook: 開頭的標記爲vue vue系統內置鉤子函數 好比vue 生命週期函數等 if (hookRE.test(event)) { vm._hasHookEvent = true; } } return vm }; /* * 添加事件 * */ Vue.prototype.$once = function (event, fn) { var vm = this; function on() { //解綁事件 vm.$off(event, on); //執行事件 fn.apply(vm, arguments); } on.fn = fn; //添加事件 vm.$on(event, on); return vm }; /* * vue把事件添加到一個數組隊列裏面,經過刪除該數組事件隊列,而達到解綁事件 * */ Vue.prototype.$off = function (event, fn) { var this$1 = this; var vm = this; // all 若是沒有參數的狀況下 返回 this vm if (!arguments.length) { //建立一個事件對象 vm._events = Object.create(null); return vm } // array of events 若是事件是數組事件 則循環回調遞歸 if (Array.isArray(event)) { for (var i = 0, l = event.length; i < l; i++) { this$1.$off(event[i], fn); } return vm } // specific event 特定的事件 若是事件不存在則返回vm var cbs = vm._events[event]; if (!cbs) { return vm } if (!fn) { //若是函數不存在則清空函數對象屬性 vm._events[event] = null; return vm } if (fn) { // specific handler 具體的處理程序 //若是函數存在 而且事件cbs是一個數組 var cb; var i$1 = cbs.length; while (i$1--) { cb = cbs[i$1]; if (cb === fn || cb.fn === fn) { //清空事件數組 cbs.splice(i$1, 1); break } } } return vm }; //觸發事件 Vue.prototype.$emit = function (event) { var vm = this; { var lowerCaseEvent = event.toLowerCase(); //轉成小寫 //若是事件轉成小寫以後並不相等之前字符串,而且是不存在_events 事件隊列中 if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) { //而後根據組件追蹤發出一個警告 tip( "Event \"" + lowerCaseEvent + "\" is emitted in component " + (formatComponentName(vm)) + " but the handler is registered for \"" + event + "\". " + "Note that HTML attributes are case-insensitive and you cannot use " + "v-on to listen to camelCase events when using in-DOM templates. " + "You should probably use \"" + (hyphenate(event)) + "\" instead of \"" + event + "\"." ); } } //獲取事件值 var cbs = vm._events[event]; if (cbs) { //若是長度大於1 將它變成一個真正的數組 cbs = cbs.length > 1 ? toArray(cbs) : cbs; //將參數變成一個真正數組 var args = toArray(arguments, 1); //循環事件 for (var i = 0, l = cbs.length; i < l; i++) { try { //執行觸發事件 cbs[i].apply(vm, args); } catch (e) { //若是發生錯誤則發出報錯警告 handleError(e, vm, ("event handler for \"" + event + "\"")); } } } return vm }; } /* */ /** * Runtime helper for resolving raw children VNodes into a slot object. * 用於將原始子節點vnode解析爲槽對象的運行時幫助器。 * * 判斷children 有沒有分發式插槽 而且過濾掉空的插槽,而且收集插槽 * */ function resolveSlots(children, context) { var slots = {}; //若是沒有子節點 則返回一個空對象 if (!children) { return slots } //循環子節點 for (var i = 0, l = children.length; i < l; i++) { //獲取單個子節點 var child = children[i]; //獲取子節點數據 var data = child.data; // remove slot attribute if the node is resolved as a Vue slot node //若是節點被解析爲Vue槽節點,則刪除slot屬性 slot 分發式屬性 if (data && data.attrs && data.attrs.slot) { delete data.attrs.slot; } //只有在 // named slots should only be respected if the vnode was rendered in the //若是在VN節點中呈現VNT,則只應命名命名槽。 // same context. //一樣的背景。 //context 上下文 if ((child.context === context || child.fnContext === context) && data && data.slot != null ) { //若是有內容分發 插槽 var name = data.slot; var slot = (slots[name] || (slots[name] = [])); //child 有模板 if (child.tag === 'template') { //把子節點的 子節點 添加 到slot插槽中 slot.push.apply(slot, child.children || []); } else { //把子節點 添加 到slot插槽中 slot.push(child); } } else { // (slots.default || (slots.default = [])).push(child); } } // ignore slots that contains only whitespace //忽略只包含空白的槽 for (var name$1 in slots) { //刪除空的插槽 if (slots[name$1].every(isWhitespace)) { delete slots[name$1]; } } return slots } function isWhitespace(node) { //不是異步 return (node.isComment && !node.asyncFactory) || node.text === ' ' } //解決範圍槽 //把對象數組事件分解成 對象 /* * [ * * { * key:'name', * fn:()=>{} * }, * { * key:'name1', * fn:()=>{} * }, * { * key:'name2', * fn:()=>{} * }, * { * key:'name3', * fn:()=>{} * }, * ] * 變成 * { * name:()=>{}, * name1:()=>{}, * name2:()=>{}, * name3:()=>{}, * } * */ function resolveScopedSlots(fns, // see flow/vnode res) { res = res || {}; for (var i = 0; i < fns.length; i++) { // if (Array.isArray(fns[i])) { //若是是數組則遞歸 resolveScopedSlots(fns[i], res); } else { //能夠去重 res[fns[i].key] = fns[i].fn; } } return res } /* */ var activeInstance = null; var isUpdatingChildComponent = false; //初始化生命週期 function initLifecycle(vm) { var options = vm.$options; // locate first non-abstract parent //定位第一個非抽象父節點 var parent = options.parent; if (parent && !options.abstract) { //判斷parent父親節點是否存在,而且判斷抽象節點是否存在 while (parent.$options.abstract && parent.$parent) { //若是有父親抽象節點,則把父層或爺爺節點 給當前節點的父親節點 parent = parent.$parent; } //子節點添加 vm parent.$children.push(vm); } //添加$parent 參數 vm.$parent = parent; //判斷parent 是不是頂層 root 若是是 則$root賦值給$root vm.$root = parent ? parent.$root : vm; // 狀況 $children 節點 vm.$children = []; //獲取節點的key vm.$refs = {}; vm._watcher = null; //觀察者 vm._inactive = null; //禁用的組件狀態標誌 vm._directInactive = false; // 不活躍 禁用的組件標誌 vm._isMounted = false; //標誌是否 觸發過 鉤子Mounted vm._isDestroyed = false; //是否已經銷燬的組件標誌 vm._isBeingDestroyed = false; //是否已經銷燬的組件標誌 若是爲true 則不觸發 beforeDestroy 鉤子函數 和destroyed 鉤子函數 } //初始化vue 更新 銷燬 函數 function lifecycleMixin(Vue) { //更新數據函數 Vue.prototype._update = function (vnode, hydrating) { var vm = this; if (vm._isMounted) { //觸發更新數據 觸發生命週期函數 callHook(vm, 'beforeUpdate'); } //獲取 vue 的el節點 var prevEl = vm.$el; //vue 的標準 vnode var prevVnode = vm._vnode; //標誌上一個 vonde console.log(prevVnode) var prevActiveInstance = activeInstance; activeInstance = vm; vm._vnode = vnode; //標誌上一個 vonde // Vue.prototype.__patch__ is injected in entry points 注入入口點 // based on the rendering backend used. 基於所使用的呈現後端。 if (!prevVnode) { //若是這個prevVnode不存在表示上一次沒有建立過vnode,這個組件或者new Vue 是第一次進來 // initial render 起始指令 //建立dmo 虛擬dom console.log('vm.$el=') console.log(vm.$el) console.log(['vnode=', vnode]) console.log(['hydrating=', hydrating]) console.log(['vm.$options._parentElm=', vm.$options._parentElm]) console.log(['vm.$options._refElm=', vm.$options._refElm]) console.log( '====vm.$el===') console.log( vm.$el) debugger; //更新虛擬dom vm.$el = vm.__patch__( vm.$el, //真正的dom vnode, //vnode hydrating, // 空 false /* removeOnly */, vm.$options._parentElm, //父節點 空 vm.$options._refElm //當前節點 空 ); console.log('=vm.$el=') console.log(vm.$el) // no need for the ref nodes after initial patch 初始補丁以後不須要ref節點 // this prevents keeping a detached DOM tree in memory (#5851) 這能夠防止在內存中保留分離的DOM樹 vm.$options._parentElm = vm.$options._refElm = null; } else { //若是這個prevVnode存在,表示vno的已經建立過,只是更新數據而已 // updates 更新 上一個舊的節點prevVnode 更新虛擬dom vm.$el = vm.__patch__(prevVnode, vnode); } activeInstance = prevActiveInstance; //vue實例化的對象 // update __vue__ reference 更新vue參考 console.log('==prevEl==') console.log(prevEl) console.log(typeof prevEl) console.log(Object.prototype.toString.call(prevEl)) console.log(vm); if (prevEl) { prevEl.__vue__ = null; } if (vm.$el) { //更新 __vue__ vm.$el.__vue__ = vm; } // if parent is an HOC, update its $el as well //若是parent是一個HOC,那麼也要更新它的$el if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el; } // updated hook is called by the scheduler to ensure that children are //調度器調用update hook以確保子節點是 // updated in a parent's updated hook. //在父類的更新鉤子中更新。 }; //更新數據 觀察者數據 Vue.prototype.$forceUpdate = function () { var vm = this; //若是_watcher 觀察者在就更新數據 if (vm._watcher) { vm._watcher.update(); //更新觀察者數據 } }; //銷燬組建周期函數 Vue.prototype.$destroy = function () { var vm = this; //若是是已經銷燬過則不會再執行 if (vm._isBeingDestroyed) { return } //觸發生命週期beforeDestroy 鉤子函數 callHook(vm, 'beforeDestroy'); vm._isBeingDestroyed = true; // remove self from parent //從父節點移除self var parent = vm.$parent; //刪除父節點 if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm); } // teardown watchers 拆卸觀察者 if (vm._watcher) { vm._watcher.teardown(); } //獲取觀察者的長度 var i = vm._watchers.length; // //把觀察者添加到隊列裏面 當前Watcher添加到vue實例上 //vm._watchers.push(this); while (i--) { vm._watchers[i].teardown(); } // remove reference from data ob //從數據ob中刪除引用 // frozen object may not have observer. //被凍結的對象可能沒有觀察者。 if (vm._data.__ob__) { vm._data.__ob__.vmCount--; } // call the last hook... //調用最後一個鉤子… vm._isDestroyed = true; // invoke destroy hooks on current rendered tree //調用當前渲染樹上的銷燬鉤子 vm.__patch__(vm._vnode, null); // fire destroyed hook // 銷燬組建 callHook(vm, 'destroyed'); // turn off all instance listeners. //銷燬事件監聽器 vm.$off(); // remove __vue__ reference //刪除vue 參數 if (vm.$el) { vm.$el.__vue__ = null; } // release circular reference (#6759) //釋放循環引用 銷燬父節點 if (vm.$vnode) { vm.$vnode.parent = null; } }; } //安裝組件 function mountComponent( vm, //vnode el, //dom hydrating ) { vm.$el = el; //dom console.log(vm.$options.render) //若是參數中沒有渲染 if (!vm.$options.render) { //實例化vm的渲染函數,虛擬dom調用參數的渲染函數 //建立一個空的組件 vm.$options.render = createEmptyVNode; { /* istanbul ignore if */ //若是參數中的模板第一個不爲# 號則會 警告 if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ); } else { warn( 'Failed to mount component: template or render function not defined.', vm ); } } } //執行生命週期函數 beforeMount callHook(vm, 'beforeMount'); //更新組件 var updateComponent; /* istanbul ignore if */ //若是開發環境 if ("development" !== 'production' && config.performance && mark) { updateComponent = function () { var name = vm._name; var id = vm._uid; var startTag = "vue-perf-start:" + id; var endTag = "vue-perf-end:" + id; mark(startTag); //插入一個名稱 而且記錄插入名稱的時間 var vnode = vm._render(); mark(endTag); measure(("vue " + name + " render"), startTag, endTag); mark(startTag); //瀏覽器 性能時間戳監聽 //更新組件 vm._update(vnode, hydrating); mark(endTag); measure(("vue " + name + " patch"), startTag, endTag); }; } else { updateComponent = function () { console.log(vm._render()) //直接更新view試圖 vm._update( /* render 是 虛擬dom,須要執行的編譯函數 相似於這樣的函數 (function anonymous( ) { with(this){return _c('div',{attrs:{"id":"app"}},[_c('input',{directives:[{name:"info",rawName:"v-info"},{name:"data",rawName:"v-data"}],attrs:{"type":"text"}}),_v(" "),_m(0)])} }) */ vm._render(), //先執行_render,返回vnode hydrating ); }; } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined //咱們將其設置爲vm。在觀察者的構造函數中 //由於觀察者的初始補丁可能調用$forceUpdate(例如inside child) //組件的掛載鉤子),它依賴於vm。_watcher已經定義 //建立觀察者 new Watcher( vm, //vm vode updateComponent, //數據綁定完以後回調該函數。更新組件函數 更新 view試圖 noop, //回調函數 null, //參數 true //是否渲染過得觀察者 /* isRenderWatcher */); hydrating = false; // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook //手動掛載實例,調用掛載在self上 // 在插入的鉤子中爲呈現器建立的子組件調用// mount if (vm.$vnode == null) { vm._isMounted = true; //執行生命週期函數mounted callHook(vm, 'mounted'); } return vm } //更新子組件 循環props 把他們添加到觀察者中 ,更新事件 function updateChildComponent( vm,// 虛擬dom vonde propsData, //props 數據屬性 listeners, //事件 parentVnode, //父親 虛擬dom vonde renderChildren) { //子節點 { isUpdatingChildComponent = true; //標誌 是否已經更新過了子組件 } // determine whether component has slot children 肯定組件是否有槽子組件 // we need to do this before overwriting $options._renderChildren 在覆蓋$options._renderChildren以前,咱們須要這樣作 // var hasChildren = !!( renderChildren || // has new static slots 是否有新的靜態插槽 vm.$options._renderChildren || // has old static slots 是否有舊的 靜態插槽 parentVnode.data.scopedSlots || // has new scoped slots 是否有範圍插槽 vm.$scopedSlots !== emptyObject // has old scoped slots 是否有舊的範圍插槽 emptyObject 是一個空的對象 ); vm.$options._parentVnode = parentVnode; //父親 虛擬dom vonde vm.$vnode = parentVnode; // update vm's placeholder node without re-render 無需從新渲染便可更新vm的佔位符節點 if (vm._vnode) { // update child tree's parent 更新子樹的父樹 vm._vnode.parent = parentVnode; } vm.$options._renderChildren = renderChildren; //子節點 // update $attrs and $listeners hash // these are also reactive so they may trigger child update if the child // used them during render //更新$attrs和$listener散列 //它們也是反應性的,所以若是子進程更新,它們可能觸發子進程更新 //渲染時使用它們 vm.$attrs = parentVnode.data.attrs || emptyObject; //虛擬dom的屬性 vm.$listeners = listeners || emptyObject; //虛擬dom的 事件 // update props 更新props 屬性 if (propsData && vm.$options.props) { toggleObserving(false); // 標誌是否禁止仍是添加到觀察者模式 var props = vm._props; //獲取屬性對象 var propKeys = vm.$options._propKeys || []; //獲取屬性的prop的key for (var i = 0; i < propKeys.length; i++) { //循環props屬性 var key = propKeys[i]; //獲取props 單個 屬性的key var propOptions = vm.$options.props; // wtf flow? /* 驗證支柱 驗證 prosp 是不是規範數據 而且爲props 添加 value.__ob__ 屬性,把prosp添加到觀察者中 * 校驗 props 參數 就是組建 定義的props 類型數據,校驗類型 * * 判斷prop.type的類型是否是Boolean或者String,若是不是他們兩類型,調用getPropDefaultValue獲取默認值而且把value添加到觀察者模式中 */ props[key] = validateProp(key, propOptions, propsData, vm); } toggleObserving(true); // keep a copy of raw propsData //保留原始propsData的副本 vm.$options.propsData = propsData; } // update listeners 更新事件 listeners = listeners || emptyObject; var oldListeners = vm.$options._parentListeners; //舊的事件 vm.$options._parentListeners = listeners; //新的事件 //更新組件事件 updateComponentListeners(vm, listeners, oldListeners); // resolve slots + force update if has children //解決插槽+強制更新若是有 子節點 if (hasChildren) { //判斷children 有沒有分發式插槽 而且過濾掉空的插槽,而且收集插槽 vm.$slots = resolveSlots(renderChildren, parentVnode.context); //更新數據 觀察者數據 vm.$forceUpdate(); } { isUpdatingChildComponent = false; } } //循環父樹層 若是有不活躍的則返回真 function isInInactiveTree(vm) { //活動中的樹 while (vm && (vm = vm.$parent)) { //循環父節點若是父節點有_inactive 則返回true if (vm._inactive) { //不活躍 return true } } return false } //判斷是否有不活躍的組件 禁用他 若是有活躍組件則觸發鉤子函數activated function activateChildComponent(vm, // 虛擬dom vode direct //布爾值 ) { if (direct) { vm._directInactive = false; if (isInInactiveTree(vm)) { //若是有不活躍的樹,或者被禁用組件 return } } else if (vm._directInactive) { //單個不活躍的 return } if (vm._inactive || vm._inactive === null) { //若是 _inactive=true 不活躍組件 或者 vm._inactive === null vm._inactive = false; for (var i = 0; i < vm.$children.length; i++) { //循環禁止子組件 activateChildComponent(vm.$children[i]); //遞歸循環 禁用子組件 } callHook(vm, 'activated'); //觸發activated 生命週期鉤子函數 } } // 循環子組件 和父組件 判斷是否有禁止的組件 若是有活躍組件則執行生命後期函數deactivated function deactivateChildComponent(vm, direct) { if (direct) { vm._directInactive = true; if (isInInactiveTree(vm)) { return } } if (!vm._inactive) { //若是該組件是活躍的 vm._inactive = true; //設置活動中的樹 for (var i = 0; i < vm.$children.length; i++) { deactivateChildComponent(vm.$children[i]); } //執行生命週期函數deactivated callHook(vm, 'deactivated'); } } //觸發鉤子函數 function callHook(vm, //虛擬dom vonde hook //鉤子函數的key ) { // #7573 disable dep collection when invoking lifecycle hooks //調用生命週期鉤子時禁用dep集合 //Dep.target = _target; //存儲 pushTarget(); //在vm 中添加聲明周期函數 var handlers = vm.$options[hook]; console.log('hook=' + hook) console.log('vm.$options[hook]') console.log(vm.$options[hook]) console.log('==handlers==') console.log(handlers) if (handlers) { //數組 for (var i = 0, j = handlers.length; i < j; i++) { try { //執行生命週期函數 handlers[i].call(vm); } catch (e) { handleError(e, vm, (hook + " hook")); } } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook); } popTarget(); } /* */ var MAX_UPDATE_COUNT = 100; var queue = []; //記錄觀察者隊列的數組 var activatedChildren = []; //記錄活躍的子組件 var has = {}; // 記錄觀察者的id var circular = {}; //持續循環更新的次數,若是超過100次 則判斷已經進入了死循環,則會報錯 var waiting = false; //觀察者在更新數據時候 等待的標誌 var flushing = false; //進入flushSchedulerQueue 函數等待標誌 var index = 0; //queue 觀察者隊列的索引 /** * Reset the scheduler's state. * 重置調度程序的狀態。 * 清空觀察者watcher隊列中的數據 */ function resetSchedulerState() { index = queue.length = activatedChildren.length = 0; has = {}; //觀察者記錄的id { circular = {}; } waiting = flushing = false; } /** * Flush both queues and run the watchers. 刷新兩個隊列並運行監視程序。 * 更新觀察者 運行觀察者watcher.run() 函數 而且 調用組件更新和激活的鉤子 */ function flushSchedulerQueue() { flushing = true; var watcher, id; // Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. //刷新前對隊列排序。 //這確保: // 1。組件從父組件更新到子組件。由於父母老是在孩子以前建立) // 2。組件的用戶觀察者在其呈現觀察者以前運行(由於用戶觀察者是在渲染觀察者以前建立的) // 3。若是一個組件在父組件的監視程序運行期間被銷燬,能夠跳過它的觀察者。 //觀察者根據id去排序 queue.sort(function (a, b) { return a.id - b.id; }); // do not cache length because more watchers might be pushed 不要緩存長度,由於可能會推入更多的觀察者 // as we run existing watchers 咱們運行現有的觀察者 for (index = 0; index < queue.length; index++) { watcher = queue[index]; //獲取單個觀察者 id = watcher.id; has[id] = null; watcher.run(); //運行觀察者 // in dev build, check and stop circular updates. 在dev build中,檢查並中止循環更新。 if ("development" !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1; if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? ("in watcher with expression \"" + (watcher.expression) + "\"") : "in a component render function." ), watcher.vm ); break } } } // keep copies of post queues before resetting state 在重置狀態以前保留post隊列的副本 var activatedQueue = activatedChildren.slice(); // 淺拷貝 var updatedQueue = queue.slice();// 淺拷貝 //清空觀察者watcher隊列中的數據 resetSchedulerState(); // call component updated and activated hooks 調用組件更新和激活的鉤子 callActivatedHooks(activatedQueue); callUpdatedHooks(updatedQueue); // devtool hook /* istanbul ignore if */ //觸發父層flush 鉤子函數 if (devtools && config.devtools) { devtools.emit('flush'); } } //觸發更新updated 鉤子函數 function callUpdatedHooks(queue) { var i = queue.length; while (i--) { var watcher = queue[i]; var vm = watcher.vm; //獲取到虛擬dom if (vm._watcher === watcher && vm._isMounted) { //判斷watcher與vm._watcher 相等 _isMounted已經更新觸發了 mounted 鉤子函數 //觸發updated 更新數據鉤子函數 callHook(vm, 'updated'); } } } /** * Queue a kept-alive component that was activated during patch. 對補丁期間激活的kept-alive組件進行隊列。 * The queue will be processed after the entire tree has been patched. 隊列將在整個樹被修補以後處理。 * 添加活躍的組件函數 把活躍的vm添加到activatedChildren 中 */ function queueActivatedComponent(vm) { // setting _inactive to false here so that a render function can 在這裏將_inactive設置爲false,以便呈現函數能夠 // rely on checking whether it's in an inactive tree (e.g. router-view) 依賴於檢查它是否在非活動樹中(例如router-view) vm._inactive = false; activatedChildren.push(vm); } // 調用組件激活的鉤子 function callActivatedHooks(queue) { for (var i = 0; i < queue.length; i++) { queue[i]._inactive = true; //判斷是否有不活躍的組件 禁用他 若是有活躍組件則觸發鉤子函數activated activateChildComponent(queue[i], true /* true */); } } /** * Push a watcher into the watcher queue. *將一個觀察者推入觀察者隊列。 * Jobs with duplicate IDs will be skipped unless it's id重複的做業將被跳過,除非是 * pushed when the queue is being flushed. *刷新隊列時推送。 * * 將觀察者推動 queue 隊列中 過濾重複的 id 除非是*刷新隊列時推送。 */ function queueWatcher(watcher) { var id = watcher.id; if (has[id] == null) { has[id] = true; // flushing=true; //這個標誌須要去掉 console.log(flushing) if (!flushing) { queue.push(watcher); //把觀察者添加到隊列中 } else { // if already flushing, splice the watcher based on its id 若是已經刷新,則根據監視程序的id拼接它 // if already past its id, it will be run next immediately. 若是已經經過了它的id,那麼將當即運行next。 var i = queue.length - 1; while (i > index && queue[i].id > watcher.id) { i--; } //根據id大小拼接插入在數組的哪一個位置 queue.splice(i + 1, 0, watcher); } console.log(waiting) // queue the flush if (!waiting) { waiting = true; //爲callbacks 收集隊列cb 函數 而且根據 pending 狀態是否要觸發callbacks 隊列函數 nextTick( flushSchedulerQueue//更新觀察者 運行觀察者watcher.run() 函數 而且 調用組件更新和激活的鉤子 ); } } } /* */ var uid$1 = 0; //觀察者的id /** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. * *觀察者分析表達式,收集依賴項, *並在表達式值更改時觸發回調。 *這用於$watch() api和指令。 * 當前vue實例、updateComponent函數、空函數。 */ var Watcher = function Watcher( vm, //vm dom expOrFn, //獲取值的函數,或者是更新viwe試圖函數 cb, //回調函數,回調值給回調函數 options, //參數 isRenderWatcher//是否渲染過得觀察者 ) { console.log('====Watcher====') this.vm = vm; //是不是已經渲染過得觀察者 if (isRenderWatcher) { //把當前 Watcher 對象賦值給 vm._watcher上 vm._watcher = this; } //把觀察者添加到隊列裏面 當前Watcher添加到vue實例上 vm._watchers.push(this); // options if (options) { //若是有參數 this.deep = !!options.deep; //實際 this.user = !!options.user; //用戶 this.lazy = !!options.lazy; //懶惰 ssr 渲染 this.sync = !!options.sync; //若是是同步 } else { this.deep = this.user = this.lazy = this.sync = false; } this.cb = cb; //回調函數 this.id = ++uid$1; // uid for batching uid爲批處理 監聽者id this.active = true; //激活 this.dirty = this.lazy; // for lazy watchers 對於懶惰的觀察者 this.deps = []; // 觀察者隊列 this.newDeps = []; // 新的觀察者隊列 // 內容不可重複的數組對象 this.depIds = new _Set(); this.newDepIds = new _Set(); // 把函數變成字符串形式 this.expression = expOrFn.toString(); // parse expression for getter //getter的解析表達式 if (typeof expOrFn === 'function') { //獲取值的函數 this.getter = expOrFn; } else { //若是是keepAlive 組件則會走這裏 //path 因該是路由地址 if (bailRE.test(path)) { // 匹配上 返回 true var bailRE = /[^\w.$]/; //匹配不是 數字字母下劃線 $符號 開頭的爲true return } // //匹配不上 path在已點分割 // var segments = path.split('.'); // return function (obj) { // // for (var i = 0; i < segments.length; i++) { // //若是有參數則返回真 // if (!obj) { // return // } // //將對象中的一個key值 賦值給該對象 至關於 segments 以點拆分的數組作obj 的key // obj = obj[segments[i]]; // } // //不然返回一個對象 // return obj // } //匹配不是 數字字母下劃線 $符號 開頭的爲true this.getter = parsePath(expOrFn); if (!this.getter) { //若是不存在 則給一個空的數組 this.getter = function () { }; "development" !== 'production' && warn( "Failed watching path: \"" + expOrFn + "\" " + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ); } } this.value = this.lazy ? // lazy爲真的的時候才能獲取值 這個有是組件才爲真 undefined : this.get(); //計算getter,並從新收集依賴項。 獲取值 }; /** * Evaluate the getter, and re-collect dependencies. * 計算getter,並從新收集依賴項。 獲取value值 */ Watcher.prototype.get = function get() { //添加一個dep target pushTarget(this); var value; var vm = this.vm; try { console.log(this.getter) //獲取值 若是報錯 則執行catch value = this.getter.call(vm, vm); console.log(value) } catch (e) { if (this.user) { handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\"")); } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching //「觸摸」每一個屬性,以便它們都被跟蹤爲 //依賴深度觀察 if (this.deep) { // //若是val 有__ob__ 屬性 // if (val.__ob__) { // var depId = val.__ob__.dep.id; // // seen 中是否含有depId 屬性或者方法 // if (seen.has(depId)) { // return // } // //若是沒有則添加進去 // seen.add(depId); // } //爲 seenObjects 深度收集val 中的key traverse(value); } // 出盞一個pushTarget popTarget(); //清理依賴項集合。 this.cleanupDeps(); } //返回值 return value }; /** * Add a dependency to this directive. 向該指令添加依賴項。 */ Watcher.prototype.addDep = function addDep(dep) { var id = dep.id; //dep.id 一個持續相加的id if (!this.newDepIds.has(id)) {//若是id存在 this.newDepIds.add(id); //添加一個id this.newDeps.push(dep); //添加一個deps if (!this.depIds.has(id)) { //若是depIds 不存在id則添加一個addSub //添加一個sub dep.addSub(this); } } }; /** * Clean up for dependency collection. * 清理觀察者依賴項集合。 */ Watcher.prototype.cleanupDeps = function cleanupDeps() { var this$1 = this; var i = this.deps.length; //遍歷 while (i--) { var dep = this$1.deps[i]; if (!this$1.newDepIds.has(dep.id)) { //清除 sub dep.removeSub(this$1); } } var tmp = this.depIds; //獲取depid this.depIds = this.newDepIds; //獲取新的depids this.newDepIds = tmp; //舊的覆蓋新的 this.newDepIds.clear(); //清空對象 //互換值 tmp = this.deps; // this.deps = this.newDeps; this.newDeps = tmp; this.newDeps.length = 0; }; /** * Subscriber interface.用戶界面。 * Will be called when a dependency changes. * 將在依賴項更改時調用。 */ Watcher.prototype.update = function update() { /* istanbul ignore else 伊斯坦布爾忽略其餘 */ if (this.lazy) { //懶惰的 忽略 this.dirty = true; } else if (this.sync) { //若是是同步 //更新數據 this.run(); } else { //若是是多個觀察者 queueWatcher(this); //隊列中的觀察者 } }; /** * Scheduler job interface. 調度器的工做界面。 * Will be called by the scheduler. 將被調度程序調用。 */ Watcher.prototype.run = function run() { if (this.active) { //活躍 var value = this.get(); //獲取值 函數 expOrFn if ( value !== this.value || //若是值不相等 // Deep watchers and watchers on Object/Arrays should fire even 深度觀察和對象/數組上的觀察應該是均勻的 // when the value is the same, because the value may 當值相等時,由於值能夠 // have mutated. 有突變。 isObject(value) || //或者值的object this.deep //獲取deep爲true ) { // set new value var oldValue = this.value; //獲取舊的值 this.value = value; //新的值賦值 if (this.user) { //若是是user 用更新值 try { this.cb.call(this.vm, value, oldValue); //更新回調函數 獲取到新的值 和舊的值 } catch (e) { handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\"")); } } else { this.cb.call(this.vm, value, oldValue);//更新回調函數 獲取到新的值 和舊的值 } } } }; /** * Evaluate the value of the watcher. 評估觀察者的值。 * This only gets called for lazy watchers. 這隻適用於懶惰的觀察者。 */ Watcher.prototype.evaluate = function evaluate() { this.value = this.get(); //獲取值 this.dirty = false; // 懶惰者標誌 標誌已經獲取過一次值 }; /** * Depend on all deps collected by this watcher. * 依賴於此監視程序收集的全部dep。 * 循環deps 收集 newDeps dep 當newDeps 數據被清空的時候從新收集依賴 */ Watcher.prototype.depend = function depend() { // this.newDeps.push(dep); //添加一個deps //deps=this.newDeps var this$1 = this; var i = this.deps.length; console.log('== this.deps.length ==') while (i--) { // 爲Watcher 添加dep 對象 // this.newDeps.push(dep); //添加一個deps this$1.deps[i].depend(); } }; /** * Remove self from all dependencies' subscriber list. * 從全部依賴項的訂閱方列表中刪除self。 */ Watcher.prototype.teardown = function teardown() { var this$1 = this; if (this.active) { // remove self from vm's watcher list 從vm的監視者列表中刪除self // this is a somewhat expensive operation so we skip it 這是一個有點昂貴的操做,因此咱們跳過它 // if the vm is being destroyed. 若是vm被銷燬。 if (!this.vm._isBeingDestroyed) { //是否銷燬的標誌 remove(this.vm._watchers, this); //刪除觀察者 } var i = this.deps.length; while (i--) { //刪除 removeSub this$1.deps[i].removeSub(this$1); } this.active = false; } }; /* * Object.defineProperty(person,'name',{ configurable:false,//可否使用delete、可否需改屬性特性、或可否修改訪問器屬性、,false爲不可從新定義,默認值爲true 是否能夠編輯 enumerable:false,//對象屬性是否可經過for-in循環,flase爲不可循環,默認值爲true 是否能夠枚舉遍歷 writable:false,//對象屬性是否可修改,flase爲不可修改,默認值爲true value:'' //對象屬性的默認值,默認值爲undefined }); * */ var sharedPropertyDefinition = { //共享屬性定義 enumerable: true, configurable: true, get: noop, set: noop }; // var Odata={ // data:{ // name:'yao', // age:28, // array:[1,2,3,4,5,6,7,8,9], // obj:{ // area:'guangxi', // work:'engineer' // // } // } // } // 設置 監聽 觀察者, 該函數是可讓 對象中的三級key 直接冒泡到1級key中 //好比 name 只能在Odata.data.name 獲取到數據,執行 proxy(Odata,'data','name')以後能夠Odata.name 獲取值 function proxy(target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter() { //設置get函數 return this[sourceKey][key] }; sharedPropertyDefinition.set = function proxySetter(val) {//設置set函數 this[sourceKey][key] = val; }; Object.defineProperty(target, key, sharedPropertyDefinition); //設置監聽觀察者 } //初始化狀態 function initState(vm) { vm._watchers = []; //初始化觀察者隊列 var opts = vm.$options; //初始化參數 //判斷是否有props屬性,若是有則添加觀察者 if (opts.props) { //初始化props 檢驗props 數據格式是不是規範的若是是規範的則添加到觀察者隊列中 initProps(vm, opts.props); } if (opts.methods) { //事件 // 初始化事件Methods 把事件 冒泡到 vm[key] 虛擬dom 最外層中 initMethods(vm, opts.methods); } if (opts.data) { //初始化數據 // 初始化數據 獲取options.data 的數據 將他們添加到 監聽者中 console.log(vm) initData(vm); console.log(vm) } else { console.log('vm._data') console.log(vm._data) // 判斷value 是否有__ob__ 實例化 dep對象,獲取dep對象 爲 value添加__ob__ 屬性,把vm._data添加到觀察者中 返回 new Observer 實例化的對象 observe(vm._data = {}, true /* asRootData */); } if (opts.computed) { //計算屬性 //初始化計算屬性 而且判斷屬性的key 是否 在 data ,將 計算屬性的key 添加入監聽者中 initComputed(vm, opts.computed); } //options 中的 watch if (opts.watch && opts.watch !== nativeWatch) { //初始化Watch initWatch(vm, opts.watch); } } //初始化props 檢驗props 數據格式是不是規範的若是是規範的則添加到觀察者隊列中 function initProps(vm, propsOptions) { var propsData = vm.$options.propsData || {}; var props = vm._props = {}; // cache prop keys so that future props updates can iterate using Array //緩存道具鍵,以便之後道具更新可使用數組迭代 // instead of dynamic object key enumeration. //而不是動態對象鍵枚舉。 var keys = vm.$options._propKeys = []; var isRoot = !vm.$parent; // root instance props should be converted //應該轉換根實例道具 if (!isRoot) { //則不會監聽 觀察者 toggleObserving(false); } var loop = function (key) { keys.push(key); /* 驗證支柱 驗證 prosp 是不是規範數據 而且爲props 添加 value.__ob__ 屬性,把prosp添加到觀察者中 * 校驗 props 參數 就是組建 定義的props 類型數據,校驗類型 * * 判斷prop.type的類型是否是Boolean或者String,若是不是他們兩類型,調用getPropDefaultValue獲取默認值而且把value添加到觀察者模式中 */ var value = validateProp( key, //props 對象的key propsOptions, propsData, vm ); /* istanbul ignore else 伊斯坦布爾忽略其餘 */ { //大寫字母,加完減號又轉成小寫了 好比把駝峯 aBc 變成了 a-bc //匹配大寫字母而且兩面不是空白的 替換成 - 在轉換成小寫 var hyphenatedKey = hyphenate(key); // 檢查屬性是否爲保留屬性。 //var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is'); if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) { //輸出警告 warn( ("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."), vm ); } //經過defineProperty的set方法去通知notify()訂閱者subscribers有新的值修改 defineReactive(props, key, value, function () { if (vm.$parent && !isUpdatingChildComponent) { warn( "Avoid mutating a prop directly since the value will be " + "overwritten whenever the parent component re-renders. " + "Instead, use a data or computed property based on the prop's " + "value. Prop being mutated: \"" + key + "\"", vm ); } }); } // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. if (!(key in vm)) { //若是vm中沒有props屬性,則把他添加到vm中,這樣組件this.[propsKey] 就能夠獲取到值了 proxy(vm, "_props", key); } }; //循環校驗 props 是否 是合格數據 而且添加觀察者 for (var key in propsOptions) loop(key); toggleObserving(true); } //初始化數據 獲取options.data 的數據 將他們添加到 監聽者中 function initData(vm) { //獲取到$options.data 數據 var data = vm.$options.data; //獲取data中的數據 判斷若是是函數則 data = vm._data = typeof data === 'function' //若是data是函數 ? getData(data, vm) //轉換數據 若是數據是 一個函數的時候 執行該函數 拿到數據 : data || {}; //直接獲取數據 if (!isPlainObject(data)) { //若是不是對象 則發出警告日誌 data = {}; "development" !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ); } // proxy data on instance var keys = Object.keys(data); //獲取數據的key var props = vm.$options.props; //獲取props 屬性 var methods = vm.$options.methods; //獲取事件 var i = keys.length; //獲取數據key的長度 while (i--) { //循環data var key = keys[i]; { if (methods && hasOwn(methods, key)) { //若是數據中的 key 與事件 中的定義的key 同樣 則發出警告 warn( ("Method \"" + key + "\" has already been defined as a data property."), vm ); } } if (props && hasOwn(props, key)) { //若是數據中的 key 與props屬性 中的定義的key 同樣 則發出警告 "development" !== 'production' && warn( "The data property \"" + key + "\" is already declared as a prop. " + "Use prop default value instead.", vm ); } else if (!isReserved(key)) { //若是不是 以$或者_開頭 console.log(vm) console.log(key) proxy(vm, "_data", key); //把數據添加到監聽者中 console.log(vm) } } // observe data console.log('data') console.log(data) observe(data, true /* asRootData */); } //轉換數據 若是數據是 一個函數的時候 執行該函數 拿到數據 function getData(data, vm) { // #7573 disable dep collection when invoking data getters //調用數據getter時禁用dep收集 pushTarget(); try { //執行函數 獲取數據 return data.call(vm, vm) } catch (e) { //收集錯誤信息 handleError(e, vm, "data()"); return {} } finally { //調用數據getter時禁用dep收集 popTarget(); } } var computedWatcherOptions = {lazy: true}; //初始化計算屬性 而且判斷屬性的key 是否 在 data ,將 計算屬性的key 添加入監聽者中 function initComputed(vm, computed) { // $flow-disable-line //建立一個新的監聽者對象空對象 var watchers = vm._computedWatchers = Object.create(null); // computed properties are just getters during SSR 計算的屬性只是SSR期間的getter var isSSR = isServerRendering(); // 服務器呈現 判斷是否是node 服務器環境 for (var key in computed) { var userDef = computed[key]; //獲取值 var getter = typeof userDef === 'function' ? userDef : userDef.get; //獲取值函數 if ("development" !== 'production' && getter == null) { //若是getter 是 空 警告 warn( ("Getter is missing for computed property \"" + key + "\"."), vm ); } if (!isSSR) { //若是不是node ssr渲染 // create internal watcher for the computed property. watchers[key] = new Watcher( vm, //vm vode getter || noop, //函數 noop, //回調函數 computedWatcherOptions //參數 lazy = true ); } // component-defined computed properties are already defined on the 組件定義的計算屬性已經在 // component prototype. We only need to define computed properties defined 組件原型。咱們只須要定義已定義的計算屬性 // at instantiation here. 在實例化。 if (!(key in vm)) { //若是computed 屬性key 不在虛擬dom中 defineComputed(vm, key, userDef); //定義計算屬性 而且 把屬性的數據 添加到對象監聽中 } else { if (key in vm.$data) { //若是判斷屬性監聽的key在 data 中則發出警告 warn(("The computed property \"" + key + "\" is already defined in data."), vm); } else if (vm.$options.props && key in vm.$options.props) { warn(("The computed property \"" + key + "\" is already defined as a prop."), vm); } } } } //定義計算屬性 而且 把屬性的數據 添加到對象監聽中 function defineComputed(target, //目標 key, //key userDef //值 ) { var shouldCache = !isServerRendering(); //若是不是node服務器 是瀏覽器 if (typeof userDef === 'function') { //屬性的值若是是個函數 sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) //若是不是node服務器 是瀏覽器 建立計算屬性 獲取值 收集 dep 依賴 : userDef; //node 服務器取值 直接調用該函數 sharedPropertyDefinition.set = noop; //賦值一個空函數 } else { sharedPropertyDefinition.get = userDef.get ?//若是userDef.get 存在 (shouldCache && userDef.cache !== false ? //緩存 createComputedGetter(key) : //建立計算屬性 獲取值 收集 dep 依賴 userDef.get ) : noop; //若是userDef.get 不存在給一個空的函數 sharedPropertyDefinition.set = userDef.set //若是userDef.set 存在 ? userDef.set : noop; } if ("development" !== 'production' && sharedPropertyDefinition.set === noop) { //若是設置值等於一個空函數則警告 sharedPropertyDefinition.set = function () { warn( ("Computed property \"" + key + "\" was assigned to but it has no setter."), this ); }; } //添加對象監聽 Object.defineProperty(target, key, sharedPropertyDefinition); } //建立計算屬性 獲取值 收集 dep 依賴 function createComputedGetter(key) { return function computedGetter() { // Watcher 實例化以後的對象 var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { //this.value 獲取值 this.getter watcher.evaluate(); //評估 } if (Dep.target) { //爲Watcher 添加 爲Watcher.newDeps.push(dep); 一個dep對象 //循環deps 收集 newDeps dep 當newDeps 數據被清空的時候從新收集依賴 watcher.depend(); } //返回值 return watcher.value } } } //初始化事件Methods 把事件 冒泡到 vm[key] 虛擬dom 最外層中 function initMethods(vm, methods) { var props = vm.$options.props; //循環 methods 事件對象 for (var key in methods) { { //若是事件是null則發出警告 if (methods[key] == null) { warn( "Method \"" + key + "\" has an undefined value in the component definition. " + "Did you reference the function correctly?", vm ); } //判斷key是不是改對象實例化的 //若是屬性中定義了key,則在methods中不能定義一樣的key if (props && hasOwn(props, key)) { warn( ("Method \"" + key + "\" has already been defined as a prop."), vm ); } //isReserved 檢查一個字符串是否以$或者_開頭的字母 if ((key in vm) && isReserved(key)) { //事件不能以$或者_開頭的字母 warn( "Method \"" + key + "\" conflicts with an existing Vue instance method. " + "Avoid defining component methods that start with _ or $." ); } } //把事件放在最外層對象中,若是是函數爲空則給一個空函數,若是是有函數則執行改函數 vm[key] = methods[key] == null ? noop : bind(methods[key], vm); } } //初始化Watch監聽 function initWatch(vm, watch) { //循環watch對象 for (var key in watch) { var handler = watch[key]; //獲取單個watch //若是他是數組handler if (Array.isArray(handler)) { //循環數組 建立 監聽 for (var i = 0; i < handler.length; i++) { createWatcher( vm, //vm 是 vue對象 key, //key handler[i]//函數或者對象 ); } } else { //循環數組 建立 監聽 createWatcher( vm, // vm 是 vue對象 key, //key handler //函數或者對象 ); } } } // 轉義handler 而且爲數據 建立 Watcher 觀察者 function createWatcher(vm, //vm對象 expOrFn, // key 值 或者函數 handler, // 函數 或者 對象 或者key options // 參數 ) { if (isPlainObject(handler)) { //判斷是不是對象 options = handler; handler = handler.handler; //對象中的handler 必定是函數或者字符串 } if (typeof handler === 'string') { //判斷handler 是不是字符串 若是是 則是key handler = vm[handler]; //取值 vm 就是Vue 最外層 中的函數 } //轉義handler 而且爲數據 建立 Watcher 觀察者 return vm.$watch( expOrFn,// key 值 或者函數 handler, //函數 options //參數 ) } //數據綁定,$watch方法 function stateMixin(Vue) { // flow somehow has problems with directly declared definition object //流在某種程度上與直接聲明的定義對象有問題 // when using Object.defineProperty, so we have to procedurally build up //在使用Object.defineProperty時,咱們必須按部就班地進行構建 // the object here. 這裏的對象。 var dataDef = {}; //從新定義get 和set方法 dataDef.get = function () { return this._data //獲取data中的數據 }; var propsDef = {}; propsDef.get = function () { return this._props// 獲取props 數據 }; { dataDef.set = function (newData) { //避免替換實例根$data。 使用嵌套數據屬性代替 warn( 'Avoid replacing instance root $data. ' + 'Use nested data properties instead.', this ); }; propsDef.set = function () { //props 只是可度的數據不能夠設置更改 warn("$props is readonly.", this); }; } console.log('==dataDef==') console.log(dataDef) Object.defineProperty(Vue.prototype, '$data', dataDef); Object.defineProperty(Vue.prototype, '$props', propsDef); //添加多一個數組數據或者對象數據 Vue.prototype.$set = set; //刪除一個數組數據或者對象數據 Vue.prototype.$delete = del; Vue.prototype.$watch = function (expOrFn, //用戶手動監聽 cb, // 監聽 變化以後 回調函數 options //參數 ) { var vm = this; if (isPlainObject(cb)) { //判斷是不是對象 若是是對象則遞歸 深層 監聽 直到它不是一個對象的時候纔會跳出遞歸 // 轉義handler 而且爲數據 建立 Watcher 觀察者 return createWatcher( vm, expOrFn, cb, options ) } options = options || {}; options.user = true; //用戶手動監聽, 就是在 options 自定義的 watch console.log(expOrFn) //實例化Watcher 觀察者 var watcher = new Watcher( vm, //vm vode expOrFn, //函數 手動 cb, //回調函數 options //參數 ); if (options.immediate) { //回調觸發函數 cb.call(vm, watcher.value); } return function unwatchFn() { //卸載觀察者 //從全部依賴項的訂閱方列表中刪除self。 watcher.teardown(); } }; } /* provide 選項應該是一個對象或返回一個對象的函數。該對象包含可注入其子孫的屬性,用於組件之間通訊。 * */ function initProvide(vm) { var provide = vm.$options.provide; //provide 選項應該是一個對象或返回一個對象的函數。該對象包含可注入其子孫的屬性。 if (provide) { //判斷provide 存在麼 vm._provided = typeof provide === 'function' //判斷是不是函數若是是函數則執行 ? provide.call(vm) : provide; } } //初始化 inject function initInjections(vm) { //provide 和 inject 主要爲高階插件/組件庫提供用例。並不推薦直接用於應用程序代碼中。 //這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。若是你熟悉 React,這與 React 的上下文特性很類似。 //更多詳情信息https://cn.vuejs.org/v2/api/#provide-inject var result = resolveInject(vm.$options.inject, vm); if (result) { toggleObserving(false); Object.keys(result).forEach(function (key) { //注入的值不能修改,至關於props屬性同樣 /* istanbul ignore else */ { // 經過defineProperty的set方法去通知notify()訂閱者subscribers有新的值修改 // * 添加觀察者 get set方法 defineReactive( vm, key, result[key], function () { warn( "Avoid mutating an injected value directly since the changes will be " + "overwritten whenever the provided component re-renders. " + "injection being mutated: \"" + key + "\"", vm ); }); } }); toggleObserving(true); } } // inject 選項應該是一個字符串數組或一個對象,該對象的 key 表明了本地綁定的名稱,value 爲其 key (字符串或 Symbol) 以在可用的注入中搜索。 function resolveInject(inject, vm) { if (inject) { // inject is :any because flow is not smart enough to figure out cached // inject是:any,由於flow不夠智能,沒法計算緩存 var result = Object.create(null); var keys = hasSymbol ? //判斷是否支持Symbol 數據類型 Reflect.ownKeys(inject).filter(function (key) { //Object.getOwnPropertyDescriptor 查看描述對象 而且獲取到enumerable 爲true 的時候纔會獲取到該數組 return Object.getOwnPropertyDescriptor(inject, key).enumerable }) : Object.keys(inject); //若是不支持hasSymbol 則降級用 Object.keys // 將數組轉化成對象 好比 [1,2,3]轉化成 // * normalized[1]={from: 1} for (var i = 0; i < keys.length; i++) { //循環key var key = keys[i]; //獲取單個key值 var provideKey = inject[key].from; //normalized[3]={from: 3} 獲取key的值 var source = vm; while (source) { if (source._provided && hasOwn(source._provided, provideKey)) { //判斷_provided 存在麼 而且是對象的時候,而且實例化屬性provideKey 存在 result[key] = source._provided[provideKey]; //獲取值 存起來 break } source = source.$parent; //循環父節點 } if (!source) { //若是vm 不存在 if ('default' in inject[key]) { // 判斷default key存在inject[key]中麼 var provideDefault = inject[key].default; //若是存在則獲取默認default的值 result[key] = typeof provideDefault === 'function' //若是是函數則執行 ? provideDefault.call(vm) : provideDefault; } else { warn(("Injection \"" + key + "\" not found"), vm); } } } return result } } /* */ /** * Runtime helper for rendering v-for lists. * 用於呈現v-for列表的運行時助手。 * 根據value 判斷是數字,數組,對象,字符串,循環渲染 */ function renderList(val, //值 render //渲染函數 ) { var ret, i, l, keys, key; // if ( Array.isArray(val) || //若是是數組 typeof val === 'string' //或者字符串 ) { ret = new Array(val.length); //獲取長度 for (i = 0, l = val.length; i < l; i++) { //循環數組或者字符串 ret[i] = render(val[i], i); } } else if (typeof val === 'number') { //若是是數字 ret = new Array(val); //變成數組 獲取長度 for (i = 0; i < val; i++) { //循環數字 ret[i] = render(i + 1, i); } } else if (isObject(val)) { //若是是對象 keys = Object.keys(val); //獲取對象的key ret = new Array(keys.length); //獲取數組長度 for (i = 0, l = keys.length; i < l; i++) { key = keys[i]; ret[i] = render(val[key], key, i); } } if (isDef(ret)) { //判斷是否認義有ret (ret)._isVList = true; //標誌是否認義有ret } //返回一個空數組對象 return ret } /* */ /** * Runtime helper for rendering <slot> * 用於呈現<slot>的運行時幫助程序 */ function renderSlot(name, //子組件中slot的name,匿名default fallback, //子組件插槽中默認內容VNode數組,若是沒有插槽內容,則顯示該內容 props, //子組件傳遞到插槽的props bindObject // 針對<slot v-bind="obj"></slot> obj必須是一個對象 ) { var scopedSlotFn = this.$scopedSlots[name]; // 判斷父組件是否傳遞做用域插槽 var nodes; //虛擬dom if (scopedSlotFn) { // scoped slot props = props || {}; if (bindObject) { //bindObject 必須是一個對象 if ("development" !== 'production' && !isObject(bindObject)) { warn( 'slot v-bind without argument expects an Object', this ); } //合併對象和props屬性 props = extend(extend({}, bindObject), props); } // 傳入props生成相應的VNode nodes = scopedSlotFn(props) || fallback; } else { // 若是父組件沒有傳遞做用域插槽 var slotNodes = this.$slots[name]; //因此在插槽 嵌入引入插槽時候不能命名同樣 // warn duplicate slot usage 警告重複槽的使用 if (slotNodes) { if ("development" !== 'production' && slotNodes._rendered) { warn( "Duplicate presence of slot \"" + name + "\" found in the same render tree " + "- this will likely cause render errors.", this ); } // 設置父組件傳遞插槽的VNode._rendered,用於後面判斷是否有重名slot slotNodes._rendered = true; } nodes = slotNodes || fallback; } // 若是還須要向子組件的子組件傳遞slot /*舉個栗子: * Bar組件: <p class="bar"><slot name="foo"/></p> * Foo組件:<p class="foo"><bar><slot slot="foo"/></bar></p> * main組件:<p><foo>hello</foo></p> * * 最終渲染:<p class="foo"><p class="bar">hello</p></p> */ var target = props && props.slot; //若是props屬性存在而且屬性的插槽存在props.slot if (target) { //建立模板 建立dom節點 虛擬dom須要渲染的數據結構 return this.$createElement('template', {slot: target}, nodes) } else { return nodes } } /* */ /** * Runtime helper for resolving filters * 用於解析過濾器的運行時助手 * 返回註冊指令或者組建的對象 * 檢測指令是否在 組件對象上面 包括 * */ function resolveFilter(id) { return resolveAsset(this.$options, 'filters', id, true) || identity } /* * 檢查key是否匹配 * 若是沒有匹配上的就返回true */ function isKeyNotMatch(expect, actual) { if (Array.isArray(expect)) { //檢查expect 是不是數組 return expect.indexOf(actual) === -1 //檢查數組中是否含有actual } else { return expect !== actual } } /** * Runtime helper for checking keyCodes from config. 用於從配置中檢查密鑰代碼的運行時幫助程序。 * exposed as Vue.prototype._k 暴露爲Vue.prototype._k * passing in eventKeyName as last argument separately for backwards compat 爲向後compat分別傳入eventKeyName做爲最後一個參數 檢查兩個key是否相等,若是不想等返回true 若是相等返回false */ function checkKeyCodes(eventKeyCode, //事件key key, //鍵 builtInKeyCode, //內建鍵碼 eventKeyName, //事件鍵名 builtInKeyName //內建鍵名 ) { var mappedKeyCode = config.keyCodes[key] || builtInKeyCode; //映射的關鍵代碼 if ( builtInKeyName && eventKeyName && !config.keyCodes[key] ) { //比較兩個key是否相等 return isKeyNotMatch(builtInKeyName, eventKeyName) } else if (mappedKeyCode) { //比較兩個key是否相等 return isKeyNotMatch(mappedKeyCode, eventKeyCode) } else if (eventKeyName) { //把駝峯的key 轉換成 -連接 判斷 key 不同 return hyphenate(eventKeyName) !== key } } /* */ /** * Runtime helper for merging v-bind="object" into a VNode's data. * 用於將v-bind="object"合併到VNode的數據中的運行時助手。 * 檢查value 是不是對象,而且爲value 添加update 事件 */ function bindObjectProps(data, //數據 tag, //vonde 節點 value, //value值 asProp, //prosp屬性 isSync) { //是否 同步 if (value) { if (!isObject(value)) { //判斷綁定值若是不是對象 "development" !== 'production' && warn( 'v-bind without argument expects an Object or Array value', this ); } else { if (Array.isArray(value)) { //判斷值若是是數組 value = toObject(value); //轉成對象 } var hash; var loop = function (key) { if ( key === 'class' || //若是key 是class key === 'style' || //獲取是style isReservedAttribute(key) //或者是'key,ref,slot,slot-scope,is' ) { hash = data; } else { var type = data.attrs && data.attrs.type; //若是含有其餘屬性 或者 tyep /* mustUseProp * 1. attr === 'value', tag 必須是 'input,textarea,option,select,progress' 其中一個 type !== 'button' * 2. attr === 'selected' && tag === 'option' * 3. attr === 'checked' && tag === 'input' * 4. attr === 'muted' && tag === 'video' * 的狀況下爲真 * */ hash = asProp || config.mustUseProp(tag, type, key) ? data.domProps || (data.domProps = {}) : data.attrs || (data.attrs = {}); } if (!(key in hash)) { //若是數據和屬性都沒有這個key的時候,判斷他應該是事件 hash[key] = value[key]; if (isSync) { //判斷是不是同步 var on = data.on || (data.on = {}); on[("update:" + key)] = function ($event) { //更新數據事件 value[key] = $event; }; } } }; //循環 value中的全部key for (var key in value) { loop(key) } ; } } return data } /* */ /** * Runtime helper for rendering static trees. * 用於呈現靜態樹的運行時助手。 */ function renderStatic(index, //索引 isInFor //是不是for指令 ) { var cached = this._staticTrees || (this._staticTrees = []); //靜態數 var tree = cached[index]; //獲取單個數 // if has already-rendered static tree and not inside v-for, 若是已經渲染的靜態樹不在v-for中, // we can reuse the same tree. 咱們能夠重用相同的樹。 if (tree && !isInFor) { return tree } // otherwise, render a fresh tree. 不然,渲染一個新的樹。 tree = cached[index] = this.$options.staticRenderFns[index].call( this._renderProxy, null, this // for render fns generated for functional component templates 用於爲功能組件模板生成的呈現fns ); //循環標誌靜態的vonde 虛擬dom markStatic(tree, ("__static__" + index), false); return tree } /** * Runtime helper for v-once. v的運行時助手。 * Effectively it means marking the node as static with a unique key. * 實際上,這意味着使用惟一鍵將節點標記爲靜態。 * 標誌 v-once. 指令 */ function markOnce(tree, index, key) { //循環標誌靜態的vonde 虛擬dom markStatic(tree, ("__once__" + index + (key ? ("_" + key) : "")), true); return tree } //循環標誌靜態的vonde 虛擬dom function markStatic(tree, //樹 key, //key isOnce //是不是v-once指令 ) { if (Array.isArray(tree)) { //判斷是不是數組 for (var i = 0; i < tree.length; i++) { if (tree[i] && typeof tree[i] !== 'string') { //標誌靜態的vonde 虛擬dom markStaticNode(tree[i], (key + "_" + i), isOnce); } } } else { //標誌靜態的vonde 虛擬dom markStaticNode(tree, key, isOnce); } } //標誌靜態的vonde 虛擬dom function markStaticNode(node, key, isOnce) { node.isStatic = true; node.key = key; node.isOnce = isOnce; } /* *綁定對象監聽器 * 判斷value 是不是對象,而且爲數據 data.on 合併data和value 的on * */ function bindObjectListeners(data, value) { if (value) { if (!isPlainObject(value)) { //value 若是不是對象則發出警告日誌 "development" !== 'production' && warn( 'v-on without argument expects an Object value', this ); } else { var on = data.on = data.on ? extend({}, data.on) : {}; //獲取事件 for (var key in value) { //遍歷循環value 值 var existing = on[key]; // 合併他們兩事件 var ours = value[key]; on[key] = existing ? [].concat(existing, ours) : ours; } } } //返回合併過的數據 return data } /* * * 安裝渲染助手 * */ function installRenderHelpers(target) { target._o = markOnce; //實際上,這意味着使用惟一鍵將節點標記爲靜態。* 標誌 v-once. 指令 target._n = toNumber; //字符串轉數字,若是失敗則返回字符串 target._s = toString; // 將對象或者其餘基本數據 變成一個 字符串 target._l = renderList; //根據value 判斷是數字,數組,對象,字符串,循環渲染 target._t = renderSlot; //用於呈現<slot>的運行時幫助程序 建立虛擬slot vonde target._q = looseEqual; //檢測a和b的數據類型,是不是不是數組或者對象,對象的key長度同樣便可,數組長度同樣便可 target._i = looseIndexOf; //或者 arr數組中的對象,或者對象數組 是否和val 相等 target._m = renderStatic;//用於呈現靜態樹的運行時助手。 建立靜態虛擬vnode target._f = resolveFilter; // 用於解析過濾器的運行時助手 target._k = checkKeyCodes; // 檢查兩個key是否相等,若是不想等返回true 若是相等返回false target._b = bindObjectProps; //用於將v-bind="object"合併到VNode的數據中的運行時助手。 檢查value 是不是對象,而且爲value 添加update 事件 target._v = createTextVNode; //建立一個文本節點 vonde target._e = createEmptyVNode; // 建立一個節點 爲註釋節點 空的vnode target._u = resolveScopedSlots; // 解決範圍槽 把對象數組事件分解成 對象 target._g = bindObjectListeners; //判斷value 是不是對象,而且爲數據 data.on 合併data和value 的on 事件 } /* * * 添加虛擬dom 屬性data,添加事件,添加props屬性,添加parent 屬性 添加injections屬性 * 添加slots插槽渲染方法 重寫 this._c createElement 函數 渲染vonde * 安渲染函數到FunctionalRenderContext.prototype原型中,這樣該對象和 Vue有着一樣的渲染功能 * installRenderHelpers(FunctionalRenderContext.prototype) * * */ function FunctionalRenderContext( data, // vonde 虛擬dom的屬性數據 props, //props 屬性 包含值和key children, //子節點 parent, //vm vue實例化,若是parent也組件 也多是VueComponent 構造函數 實例化的對象 Ctor //VueComponent 構造函數 ) { console.log([ data, // vonde 虛擬dom的屬性數據 props, //props 屬性 children, //子節點 parent, //vm Ctor //VueComponent 構造函數 ]) var options = Ctor.options; // ensure the createElement function in functional components // gets a unique context - this is necessary for correct named slot check //確保函數組件中的createElement函數 // 獲取惟一上下文——這對於正確的命名槽檢查是必要的 var contextVm; console.log(hasOwn(parent, '_uid')) if (hasOwn(parent, '_uid')) { //判斷這個組件是不是 new _init 過 contextVm = Object.create(parent); //建立一個對象 // $flow-disable-line 流禁用線 contextVm._original = parent; } else { // the context vm passed in is a functional context as well. // in this case we want to make sure we are able to get a hold to the // real context instance. //傳入的上下文vm也是一個功能上下文。 //在這種狀況下,咱們想肯定一下咱們可否獲得 //真實的上下文實例。 contextVm = parent; // $flow-disable-line parent = parent._original; } var isCompiled = isTrue(options._compiled); // 判斷是不是模板編譯 var needNormalization = !isCompiled; //若是不是模板編譯 // data, // vonde 虛擬dom的數據 // props, //props 屬性 // children, //子節點 // parent, //vm // Ctor //VueComponent 構造函數 this.data = data; // vonde 虛擬dom的數據 this.props = props; // props 屬性 this.children = children; //子節點 this.parent = parent; //vm this.listeners = data.on || emptyObject; // 事件 // inject 選項應該是一個字符串數組或一個對象,該對象的 key 表明了本地綁定的名稱,value 爲其 key (字符串或 Symbol) 以在可用的注入中搜索。 this.injections = resolveInject(options.inject, parent); this.slots = function () { //插槽 // 判斷children 有沒有分發式插槽 而且過濾掉空的插槽 return resolveSlots(children, parent); }; // support for compiled functional template //支持編譯的函數模板 if (isCompiled) { // exposing $options for renderStatic() 爲renderStatic()公開$options this.$options = options; // pre-resolve slots for renderSlot() renderSlot()的預解析槽() this.$slots = this.slots(); //收集插槽 // data.scopedSlots = {default: children[0]}; //獲取插槽 this.$scopedSlots = data.scopedSlots || emptyObject; } if (options._scopeId) { //範圍id this._c = function (a, b, c, d) { // //建立子節點 vonde var vnode = createElement(contextVm, a, b, c, d, needNormalization); if (vnode && !Array.isArray(vnode)) { vnode.fnScopeId = options._scopeId; vnode.fnContext = parent; } return vnode }; } else { this._c = function (a, b, c, d) { //建立子節點 vonde return createElement(contextVm, a, b, c, d, needNormalization); }; } } //安裝渲染助手 installRenderHelpers(FunctionalRenderContext.prototype); //建立功能組件 經過檢測 props 屬性 而後合併props 以後建立 vond 虛擬dom function createFunctionalComponent( Ctor, //組件構造函數VueComponent propsData, //組件props數據 data, // 組件屬性 數據 contextVm, //vm vue實例化對象 children //組件子節點 ) { console.log('==Ctor==') console.log(Ctor) console.log('==propsData==') console.log(propsData) console.log('==data==') console.log(data) console.log('==contextVm==') console.log(contextVm) console.log('==children==') console.log(children) var options = Ctor.options; //獲取拓展參數 var props = {}; var propOptions = options.props; //獲取props 參數 就是組建 定義的props 類型數據 console.log('==options.props==') console.log(options.props) if (isDef(propOptions)) { //若是定義了props 參數 for (var key in propOptions) { //循環 propOptions 參數 /* 驗證支柱 驗證 prosp 是不是規範數據 而且爲props 添加 value.__ob__ 屬性,把prosp添加到觀察者中 * 校驗 props 參數 就是組建 定義的props 類型數據,校驗類型 * * 判斷prop.type的類型是否是Boolean或者String,若是不是他們兩類型,調用getPropDefaultValue獲取默認值而且把value添加到觀察者模式中 */ props[key] = validateProp( key, //key propOptions, //原始props 參數 propsData || emptyObject // 轉義過的組件props數據 ); } } else { if (isDef(data.attrs)) { //若是定義有屬性 // 前拷貝合併 props屬性 而且把 from 的key 由 - 寫法變成 駝峯的寫法。 mergeProps(props, data.attrs); //合併props 和 屬性 } if (isDef(data.props)) { //若是data定義有props 合併props mergeProps(props, data.props); } } // Ctor, // propsData, //組件props數據 // data, // vonde 虛擬dom的數據 // contextVm, //上下文this Vm // children //子節點 console.log(Ctor) // Ctor = function VueComponent(options) { // this._init(options); // } // //返回 var renderContext = new FunctionalRenderContext( //實例化一個對象 data,// vonde 虛擬dom的數據 props, //props 屬性 children, //子節點 contextVm, //vm Ctor //VueComponent 構造函數 ); // children : undefined // data : Object // injections : undefined // listeners : Object // parent : Vue // props : Object // slots : function () // _c: function (a, b, c, d) // __proto__: Object console.log('==renderContext==') console.log(renderContext) //建立 vnode var vnode = options.render.call(null, renderContext._c, renderContext); if (vnode instanceof VNode) { //若是 vnode 的構造函數是VNode //克隆並標記函數結果 return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options) } else if (Array.isArray(vnode)) { //若是vnode 是數組 //normalizeArrayChildren 建立一個規範的子節點 vonde var vnodes = normalizeChildren(vnode) || []; var res = new Array(vnodes.length); // 建立一個空數組 for (var i = 0; i < vnodes.length; i++) { //克隆並標記函數結果 靜態 節點 res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options); } return res } } //克隆並標記函數結果 靜態 節點 function cloneAndMarkFunctionalResult(vnode, //vnode 虛擬dom data, //虛擬dom 數據 contextVm, //vm this options // options 拓展函數 ) { // #7817 clone node before setting fnContext, otherwise if the node is reused // (e.g. it was from a cached normal slot) the fnContext causes named slots // that should not be matched to match. // #7817在設置fnContext以前克隆節點,不然若是節點被重用 //(例如,它來自一個緩存的正常槽)fnContext致使命名槽 //這是不該該匹配的。 //克隆節點 把節點變成靜態節點 var clone = cloneVNode(vnode); clone.fnContext = contextVm; clone.fnOptions = options; if (data.slot) { //判斷是否有插槽 (clone.data || (clone.data = {})).slot = data.slot; } return clone } // 前拷貝合併 props屬性 而且把 from 的key 由 - 寫法變成 駝峯的寫法。 function mergeProps(to, from) { for (var key in from) { to[camelize(key)] = from[key]; } } /* */ // Register the component hook to weex native render engine. // The hook will be triggered by native, not javascript. // Updates the state of the component to weex native render engine. /* */ // https://github.com/Hanks10100/weex-native-directive/tree/master/component // listening on native callback /* */ /* */ // inline hooks to be invoked on component VNodes during patch //補丁期間在組件vnode上調用的內聯鉤子 var componentVNodeHooks = { //組件鉤子函數 init: function init( //初始化組件函數 vnode, //vonde虛擬dom hydrating, //新的虛擬dom vonde parentElm, //父親 dom refElm ) { //當前elm dom //根據Vnode生成VueComponent實例 if ( vnode.componentInstance && //已經實例過的組件就只更新 !vnode.componentInstance._isDestroyed && //而且沒有銷燬 vnode.data.keepAlive //而且是keepAlive 組件 ) { // kept-alive components, treat as a patch // kept-alive組件,看成補丁 // work around flow 圍繞流程工做 var mountedNode = vnode; //觸發更新虛擬比較 componentVNodeHooks.prepatch(mountedNode, mountedNode); } else { // 調用VueComponent構造函數去實例化組件對象 var child = vnode.componentInstance = createComponentInstanceForVnode( vnode, //虛擬dom vonde activeInstance, //活動實例 vue 實例化的對象 parentElm, //父dom el refElm //當前dom el ); //實例方法掛載 vm child.$mount( hydrating ? vnode.elm : undefined, hydrating //新的虛擬dom vonde ); } }, prepatch: function prepatch( oldVnode, //舊的 vnode) { //比較新舊的虛擬dom 更新組件數據 var options = vnode.componentOptions; //組件的參數 var child = vnode.componentInstance = oldVnode.componentInstance; //組件實例 updateChildComponent( //更新子組建 child, //子節點 options.propsData, // updated props 組件屬性。屬性數據 options.listeners, // updated listeners 屬性事件 vnode, // new parent vnode 新的vond 虛擬dom options.children // new children 新的子節點 虛擬dom ); }, insert: function insert(vnode) { //安裝插入 var context = vnode.context; //vm vue 實例化對象或者是VueComponent 構造函數實例化對象 var componentInstance = vnode.componentInstance; //組件實例化對象 if (!componentInstance._isMounted) { // componentInstance._isMounted = true; callHook( componentInstance, 'mounted' //觸發mounted鉤子函數 ); // } //若是有keepAlive 組件才觸發下面 if (vnode.data.keepAlive) { if (context._isMounted) { // vue-router#1212 // During updates, a kept-alive component's child components may // change, so directly walking the tree here may call activated hooks // on incorrect children. Instead we push them into a queue which will // be processed after the whole patch process ended. // vue-router # 1212 //在更新期間,kept-alive組件的子組件能夠 //改變,因此直接在樹中行走可能會調用激活鉤子 //關於不正確的孩子。相反,咱們把它們推到一個隊列中 //在整個補丁過程結束後處理。 //添加活躍的組件函數 把活躍的vm添加到activatedChildren 中 queueActivatedComponent(componentInstance); } else { //判斷是否有不活躍的組件 禁用他 若是有活躍組件則觸發鉤子函數activated activateChildComponent(componentInstance, true /* direct */); } } }, //銷燬鉤子函數 destroy: function destroy(vnode) { var componentInstance = vnode.componentInstance; //組件實例化 if (!componentInstance._isDestroyed) { if (!vnode.data.keepAlive) { //若是組件不是keepAlive 則銷燬掉 // 銷燬不是keepAlive 的組件 改組件是虛擬組件 用於 緩存單頁 返回上一頁數據 componentInstance.$destroy(); } else { //keepAlive組件則走這裏 // 循環子組件 和父組件 判斷是否有禁止的組件 若是有活躍組件則執行生命後期函數deactivated deactivateChildComponent(componentInstance, true /* direct */); } } } }; //獲取對象的key值而且以數組形式封裝 var hooksToMerge = Object.keys(componentVNodeHooks); //建立組件 function createComponent( Ctor, //VueComponen函數 data, // 組件標籤上面的屬性數據 context, //vm Vue 實例化以後的對象上下文 children, //子節點 tag) { //標籤 if (isUndef(Ctor)) { return } //vue //用來標識擴展全部普通對象的「基」構造函數 // Weex的多實例場景中的組件。 var baseCtor = context.$options._base; //基本的Vue 靜態類 // plain options object: turn it into a constructor //普通選項對象:將其轉換爲構造函數 _base vue 的 構造函數 if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor); } // if at this stage it's not a constructor or an async component factory, //若是在這個階段它不是構造函數或異步組件工廠, // reject. if (typeof Ctor !== 'function') { //若是不是函數則發出警告 { warn(("Invalid Component definition: " + (String(Ctor))), context); } return } console.log(Ctor) console.log(baseCtor) console.log(context) // async component //異步組件 var asyncFactory; // Vue.cid = 0; if (isUndef(Ctor.cid)) { //組件的id 惟一標識符 asyncFactory = Ctor; // // 解決異步組件 更新組建數據 Ctor = resolveAsyncComponent( //返回組件如今的狀態 asyncFactory, //新的 baseCtor, //基本的Vue 靜態類 context //當前已經實例化的vm對象 ); if (Ctor === undefined) { // return a placeholder node for async component, which is rendered // as a comment node but preserves all the raw information for the node. // the information will be used for async server-rendering and hydration. //爲已呈現的異步組件返回佔位符節點 //做爲註釋節點,但保留該節點的全部原始信息。 //這些信息將用於異步服務器呈現和水合做用。 // factory, //工廠 // data, //數據 // context, //語境 // children, //子節點 // tag) { //標籤 return createAsyncPlaceholder( asyncFactory, //VueComponent 構造函數 data, //組件tag的屬性數據 context, //Vue 實例化對象 children, //子節點 tag //組件標籤 ) } } data = data || {}; console.log(Ctor) // resolve constructor options in case global mixins are applied after // component constructor creation //解析構造函數選項,以防在後面應用全局mixin //組件構造函數建立 //解決構造函數的選擇 options 參數,合併,過濾重複 options參數 resolveConstructorOptions(Ctor); // transform component v-model data into props & events //將組件轉換 v-model data into props & events //轉換v-model 而且 綁定事件 if (isDef(data.model)) { //若是定義有 model 轉義 model 而且綁定 v-model transformModel(Ctor.options, data); } console.log(data) console.log(Ctor) console.log(tag) // extract props 從…提取,文件的摘錄 extractPropsFromVNodeData 從 props屬性中獲取vnode數據 var propsData = extractPropsFromVNodeData( data, //tag標籤屬性數據 Ctor, //組件構造函數VueComponent tag //tag標籤名稱 ); // functional component 功能組成部分,功能部件 if (isTrue(Ctor.options.functional)) { //爲真 return createFunctionalComponent( Ctor, //組件構造函數VueComponent propsData, //組件props 數據 data, //組件屬性 數據 context, //vm vue實例化對象 children //組件子節點 ) } // extract listeners, since these needs to be treated as // child component listeners instead of DOM listeners //提取監聽器,由於這些監聽器須要被看成 //子組件監聽器而不是DOM監聽器 var listeners = data.on; //事件 // replace with listeners with .native modifier // so it gets processed during parent component patch. //用.native修飾符替換監聽器 //所以它在父組件補丁中被處理 data.on = data.nativeOn; //你可能有不少次想要在一個組件的根元素上直接監聽一個原生事件。這時,你可使用 v-on 的 .native 修飾符: // <base-input v-on:focus.native="onFocus"></base-input> if (isTrue(Ctor.options.abstract)) { //靜態 // abstract components do not keep anything // other than props & listeners & slot // work around flow //抽象組件不保存任何東西 //除了道具、監聽器和插槽 //圍繞流程工做 var slot = data.slot; //插槽 data = {}; if (slot) { data.slot = slot; } } // install component management hooks onto the placeholder node //將組件管理鉤子安裝到佔位符節點上 console.log(data); installComponentHooks(data); // return a placeholder vnode var name = Ctor.options.name || tag; console.log( ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : ''))) console.log( data) console.log( context) console.log( {Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children}) console.log(asyncFactory) var vnode = new VNode( ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')), data, // 標籤 屬性數據 undefined,//子節點 undefined,//文本 undefined,/*當前節點的dom */ context, //vm vue實例化對象或者父組件。 {Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children}, //當前組件 構造函數propsData屬性 事件,tag標籤, 子節點 asyncFactory ); // Weex specific: invoke recycle-list optimized @render function for // extracting cell-slot template. // https://github.com/Hanks10100/weex-native-directive/tree/master/component /* istanbul ignore if */ console.log('===vnode===') console.log(vnode) return vnode } //調用VueComponent構造函數去實例化組件對象 function createComponentInstanceForVnode( vnode, // we know it's MountedComponentVNode but flow doesn't //咱們知道它是MountedComponentVNode,但flow不是 parent, // activeInstance in lifecycle state 處於生命週期狀態的activeInstance parentElm, // 父親dom refElm //當前的dom ) { var options = { _isComponent: true, //是不是組件 parent: parent, //組件的父節點 _parentVnode: vnode, //組件的 虛擬vonde 父節點 _parentElm: parentElm || null, //父節點的dom el _refElm: refElm || null //當前節點 el }; // check inline-template render functions 檢查內聯模板渲染函數 var inlineTemplate = vnode.data.inlineTemplate; //內聯模板 if (isDef(inlineTemplate)) { //是否有內聯模板 options.render = inlineTemplate.render; //若是有內聯模板 獲取內聯模板的渲染函數 options.staticRenderFns = inlineTemplate.staticRenderFns; //獲取靜態渲染函數 } return new vnode.componentOptions.Ctor(options) //實例化 VueComponent 構造函數 } //安裝組件鉤子函數 function installComponentHooks( data //vonde 虛擬dom ) { //安裝組件鉤子函數 var hooks = data.hook || (data.hook = {}); for (var i = 0; i < hooksToMerge.length; i++) { var key = hooksToMerge[i]; hooks[key] = componentVNodeHooks[key]; //組建鉤子函數 } console.log('==hooks==') console.log(hooks) } // transform component v-model info (value and callback) into // prop and event handler respectively. //將組件v-model信息(值和回調)轉換爲 //分別是prop和event handler。 //將標籤含有v-model 信息屬性轉換爲 //獲取options.model.prop屬性 獲取options.model.event 事件類型, // 把data.model.value 數據賦值到data.props.value中 若是value的key沒有定義 則是input // 把事件 data.model.callback 添加到 data.on[event] 中 若是沒有定義是input function transformModel(options, data) { //獲取prop 若是獲取不到 則取值 value var prop = (options.model && options.model.prop) || 'value'; //獲取event若是獲取不到 則取值 input var event = (options.model && options.model.event) || 'input'; //把data.model.value的值賦值到data.props.value 中 (data.props || (data.props = {}))[prop] = data.model.value; var on = data.on || (data.on = {}); if (isDef(on[event])) { //若是model 事件已經定義了則是和鉤子函數合併 on[event] = [data.model.callback].concat(on[event]); } else { on[event] = data.model.callback; //只賦值鉤子函數 } } /* */ var SIMPLE_NORMALIZE = 1; var ALWAYS_NORMALIZE = 2; // wrapper function for providing a more flexible interface 包裝器功能,提供更靈活的接口 // without getting yelled at by flow 而不是被心流狂吼 //建立dom節點 function createElement( context, //vm new Vue 實例化的對象 tag, //標籤標籤名稱 data, //標籤數據,包括屬性,class style 指令等 children, //子節點 normalizationType,//應該設置爲常量ALWAYS_NORMALIZE的值 alwaysNormalize //布爾值 是不是真的是true ) { console.log(data) //若是數據是數組 或者是 //判斷數據類型是不是string,number,symbol,boolean if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children; children = data; data = undefined; } //若是是真的是 true if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE; //type等於2 } //建立節點 return _createElement( context, //vm new Vue 實例化的對象 tag,//節點標籤 data, //標籤數據,包括屬性,class style 指令等 children, //子節點 normalizationType ) } //建立虛擬dom節點 function _createElement(context, //vm vue實例化的對象 tag, //節點 data, //標籤數據,包括屬性,class style 指令等 children, //子節點 normalizationType // 1或者2 ) { /** * 若是存在data.__ob__, * 說明data是被Observer觀察的數據 * 不能用做虛擬節點的data * 須要拋出警告, * 並返回一個空節點 * 被監控的data不能被用做vnode渲染的數據的緣由是:data在vnode渲染過程當中可能會被改變, * 這樣會觸發監控, * 致使不符合預期的操做 * */ if (isDef(data) && isDef((data).__ob__)) { "development" !== 'production' && warn( "Avoid using observed data object as vnode data: " + (JSON.stringify(data)) + "\n" + 'Always create fresh vnode data objects in each render!', context ); //建立一個空的節點 return createEmptyVNode() } // object syntax in v-bind // v-bind中的對象語法 //若是定義有數據而且數據中的is也定義了 if (isDef(data) && isDef(data.is)) { tag = data.is; //tag等於is } //若是tag不存在 // 當組件的is屬性被設置爲一個falsy的值 // Vue將不會知道要把這個組件渲染成什麼 // 因此渲染一個空節點 if (!tag) { // in case of component :is set to falsy value //組件的狀況:設置爲falsy值 建立一個空節點 return createEmptyVNode() } // warn against non-primitive key //警告非原始鍵 if ("development" !== 'production' && isDef(data) && isDef(data.key) && !isPrimitive(data.key) ) { { warn( 'Avoid using non-primitive value as key, ' + 'use string/number value instead.', context ); } } // support single function children as default scoped slot //支持做爲默認做用域插槽的單函數子函數 if ( Array.isArray(children) && //若是子節點是數組 typeof children[0] === 'function' //而且第一個子節點類型是函數 ) { data = data || {}; data.scopedSlots = {default: children[0]}; //獲取插槽 children.length = 0; } // 根據normalizationType的值,選擇不一樣的處理方法 if (normalizationType === ALWAYS_NORMALIZE) { //2 //建立一個規範的子節點 children = normalizeChildren(children); } else if (normalizationType === SIMPLE_NORMALIZE) { //1 //把全部子節點的數組 子孫鏈接在一個數組。 children = simpleNormalizeChildren(children); } var vnode, ns; if (typeof tag === 'string') { //類型是string var Ctor; //getTagNamespace 判斷 tag 是不是svg或者math 標籤 // 獲取標籤名的命名空間 ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag); //判斷 tag 是不是svg或者math 標籤 //判斷標籤是否是html 原有的標籤 if (config.isReservedTag(tag)) { // platform built-in elements //平臺內置的元素 //建立一個vnode // tag, /*當前節點的標籤名*/ // data, /*當前節點對應的對象,包含了具體的一些數據信息,是一個VNodeData類型,能夠參考VNodeData類型中的數據信息*/ // children, //子節點 // text, //文本 // elm, /*當前節點的dom */ // context, /*編譯做用域*/ // componentOptions, /*組件的option選項*/ // asyncFactory/*異步工廠*/ vnode = new VNode( config.parsePlatformTagName(tag), //返回相同的值 。當前tag的標籤名稱 data, //tag標籤的屬性數據 children, //子節點 undefined, //文本 undefined, //*當前節點的dom */ context // vm vue實例化的對象 ); // 若是不是保留標籤,那麼咱們將嘗試從vm的components上查找是否有這個標籤的定義 } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // component 若是有則建立一個組件 console.log('===Ctor===') console.log(Ctor) //獲取到 VueComponent 構造函數 sup類 //Ctor是VueComponent 組件構造函數 //建立一個組件 調用6000多行的createComponent vnode = createComponent( Ctor, //組件構造函數 data, //組件虛擬dom數據 context, //this上下文 children, //子節點 tag //組件標籤 ); } else { // unknown or unlisted namespaced elements // check at runtime because it may get assigned a namespace when its // parent normalizes children //建立標準的vue vnode // 兜底方案,正常建立一個vnode vnode = new VNode( tag, //虛擬dom的標籤 data, //虛擬dom的數據 children, //虛擬dom的子節點 undefined, undefined, context ); } } else { // 當tag不是字符串的時候,咱們認爲tag是組件的構造類 // 因此直接建立 // direct component options / constructor 直接組件選項/構造函數 //建立組件 vnode = createComponent(tag, data, context, children); } if (Array.isArray(vnode)) { //若是vnode 是數組 return vnode } else if (isDef(vnode)) { //若是vnode 有定義 if (isDef(ns)) {//若是ns 有定義 標籤名 // 若是有namespace,就應用下namespace,而後返回vnode //檢測 vnode中的tag === 'foreignObject' 是否相等。而且修改ns值與force 標誌 applyNS(vnode, ns); } if (isDef(data)) { //註冊深綁定 registerDeepBindings(data); } return vnode } else { // 不然,返回一個空節點 return createEmptyVNode() } } //檢測 vnode中的tag === 'foreignObject' 是否相等。而且修改ns值與force 標誌 function applyNS( vnode, //虛擬dom ns, // namespace 標籤 應該是svg標籤吧 不是很清楚 force ) { vnode.ns = ns; // if (vnode.tag === 'foreignObject') { //svg標籤 // use default namespace inside foreignObject //使用foreignObject中的默認名稱空間 ns = undefined; force = true; } if (isDef(vnode.children)) { //虛擬dom是否後子節點 遞歸循環 for (var i = 0, l = vnode.children.length; i < l; i++) { var child = vnode.children[i]; if (isDef(child.tag) && ( isUndef(child.ns) || //子節點沒有定義ns (isTrue(force) && child.tag !== 'svg') //force爲真,子節點不爲svg ) ) { applyNS(child, ns, force); //遞歸 } } } } // ref #5318 // necessary to ensure parent re-render when deep bindings like :style and // :class are used on slot nodes //裁判# 5318 //必須確保父元素在深度綁定時從新呈現,好比:style和 //類在槽節點上使 function registerDeepBindings(data) { if (isObject(data.style)) { // //爲 seenObjects 深度收集val 中的key traverse(data.style); } if (isObject(data.class)) { //爲 seenObjects 深度收集val 中的key traverse(data.class); } } /* * 初始化渲染 * */ function initRender(vm) { //vm 是Vue 對象 vm._vnode = null; // the root of the child tree 上一個 vonde vm._staticTrees = null; // v-once cached trees v-once緩存的樹 var options = vm.$options; //獲取參數 var parentVnode = vm.$vnode = options._parentVnode; // the placeholder node in parent tree 父樹中的佔位符節點 var renderContext = parentVnode && parentVnode.context; // this 上下文 //判斷children 有沒有分發式插槽 而且過濾掉空的插槽,而且收集插槽 vm.$slots = resolveSlots(options._renderChildren, renderContext); vm.$scopedSlots = emptyObject; // bind the createElement fn to this instance // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize // internal version is used by render functions compiled from templates //將createElement fn綁定到這個實例 //這樣咱們就獲得了合適的渲染上下文。 // args order: tag, data, children, normalizationType, alwaysNormalize //內部版本由模板編譯的呈現函數使用 //建立虛擬dom的數據結構 vm._c = function (a, b, c, d) { console.log(a) console.log(b) console.log(c) console.log(d) return createElement( vm, //vm new Vue 實例化的對象 a, //有多是vonde或者指令 b, c, d, false ); }; // normalization is always applied for the public version, used in //的公共版本老是應用規範化 // user-written render functions. //用戶編寫的渲染功能。 vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); }; // $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated // $attrs和$listener將被公開,以便更容易地進行臨時建立。 //它們須要是反應性的,以便使用它們的HOCs老是更新的 var parentData = parentVnode && parentVnode.data; //獲取父vnode /* istanbul ignore else */ { // 經過defineProperty的set方法去通知notify()訂閱者subscribers有新的值修改 defineReactive( vm, '$attrs', parentData && parentData.attrs || emptyObject, function () { !isUpdatingChildComponent && warn("$attrs is readonly.", vm); }, true ); // 經過defineProperty的set方法去通知notify()訂閱者subscribers有新的值修改 defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () { !isUpdatingChildComponent && warn("$listeners is readonly.", vm); }, true); } } function renderMixin(Vue) { // install runtime convenience helpers 安裝運行時方便助手 // 安裝渲染助手 installRenderHelpers(Vue.prototype); Vue.prototype.$nextTick = function (fn) { //爲callbacks 收集隊列cb 函數 而且根據 pending 狀態是否要觸發callbacks 隊列函數 return nextTick(fn, this) }; //渲染函數 Vue.prototype._render = function () { var vm = this; //獲取vm參數 var ref = vm.$options; /* render 是 虛擬dom,須要執行的編譯函數 相似於這樣的函數 (function anonymous( ) { with(this){return _c('div',{attrs:{"id":"app"}},[_c('input',{directives:[{name:"info",rawName:"v-info"},{name:"data",rawName:"v-data"}],attrs:{"type":"text"}}),_v(" "),_m(0)])} }) */ var render = ref.render; var _parentVnode = ref._parentVnode; // reset _rendered flag on slots for duplicate slot check //重置槽上的_render標記,以檢查重複槽 { for (var key in vm.$slots) { // $flow-disable-line //標誌位 vm.$slots[key]._rendered = false; } } if (_parentVnode) { //判斷是否有parentVnode // data.scopedSlots = {default: children[0]}; //獲取插槽 vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject; } // set parent vnode. this allows render functions to have access //設置父vnode。這容許呈現函數具備訪問權限 // to the data on the placeholder node. //到佔位符節點上的數據。 //把父層的Vnode 賦值的到$vnode vm.$vnode = _parentVnode; // render self var vnode; try { //建立一個空的組件 // vm.$options.render = createEmptyVNode; //_renderProxy 代理攔截 /* render 是 虛擬dom,須要執行的編譯函數 相似於這樣的函數 (function anonymous( ) { with(this){return _c('div',{attrs:{"id":"app"}},[_c('input',{directives:[{name:"info",rawName:"v-info"},{name:"data",rawName:"v-data"}],attrs:{"type":"text"}}),_v(" "),_m(0),_v(" "),_c('div',[_v("\n "+_s(message)+"\n ")])])} }) */ vnode = render.call( vm._renderProxy, //this指向 其實就是vm vm.$createElement //這裏雖然傳參進去可是沒有接收參數 ); console.log(vnode) } catch (e) { //收集錯誤信息 並拋出 handleError(e, vm, "render"); // return error render result, // or previous vnode to prevent render error causing blank component //返回錯誤渲染結果, //或之前的vnode,以防止渲染錯誤致使空白組件 /* istanbul ignore else */ { if (vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e); } catch (e) { handleError(e, vm, "renderError"); vnode = vm._vnode; } } else { vnode = vm._vnode; } } } // return empty vnode in case the render function errored out 若是呈現函數出錯,返回空的vnode if (!(vnode instanceof VNode)) { if ("development" !== 'production' && Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ); } //建立一個節點 爲註釋節點 空的vnode vnode = createEmptyVNode(); } // set parent vnode.parent = _parentVnode; //設置父vnode return vnode }; } /* */ var uid$3 = 0; //初始化vue function initMixin(Vue) { Vue.prototype._init = function (options) { //初始化函數 var vm = this; // a uid vm._uid = uid$3++; //id var startTag, //開始標籤 endTag; //結束標籤 /* istanbul ignore if */ //瀏覽器性能監控 if ("development" !== 'production' && config.performance && mark) { startTag = "vue-perf-start:" + (vm._uid); endTag = "vue-perf-end:" + (vm._uid); mark(startTag); } // a flag to avoid this being observed 一個避免被觀察到的標誌 vm._isVue = true; // merge options 合併選項 參數 if (options && options._isComponent) { //判斷是不是組件 // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. //優化內部組件實例化 //由於動態選項合併不是常慢,沒有一個是內部組件選項須要特殊處理。 //初始化內部組件 initInternalComponent(vm, options); } else { //合併參數 將兩個對象合成一個對象 將父值對象和子值對象合併在一塊兒,而且優先取值子值,若是沒有則取子值 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), // //解析constructor上的options屬性的 options || {}, vm ); } /* istanbul ignore else */ { //初始化 代理 監聽 initProxy(vm); } // expose real self 暴露真實的self vm._self = vm; initLifecycle(vm); //初始化生命週期 標誌 initEvents(vm); //初始化事件 initRender(vm); // 初始化渲染 callHook(vm, 'beforeCreate'); //觸發beforeCreate鉤子函數 initInjections(vm); // resolve injections before data/props 在數據/道具以前解決注入問題 //初始化 inject initState(vm); // //初始化狀態 initProvide(vm); // resolve provide after data/props 解決後提供數據/道具 provide 選項應該是一個對象或返回一個對象的函數。該對象包含可注入其子孫的屬性,用於組件之間通訊。 callHook(vm, 'created'); //觸發created鉤子函數 /* istanbul ignore if */ //瀏覽器 性能監聽 if ("development" !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false); mark(endTag); measure(("vue " + (vm._name) + " init"), startTag, endTag); } if (vm.$options.el) { // Vue 的$mount()爲手動掛載, // 在項目中可用於延時掛載(例如在掛載以前要進行一些其餘操做、判斷等),以後要手動掛載上。 // new Vue時,el和$mount並無本質上的不一樣。 vm.$mount(vm.$options.el); } }; } //初始化內部組件 function initInternalComponent(vm, //vue實例 options //選項參數 ) { var opts = vm.$options = Object.create(vm.constructor.options); //vm的參數 // doing this because it's faster than dynamic enumeration. 這樣作是由於它比動態枚舉快。 // var options = { // _isComponent: true, //是不是組件 // parent: parent, //組件的父節點 // _parentVnode: vnode, //組件的 虛擬vonde 父節點 // _parentElm: parentElm || null, //父節點的dom el // _refElm: refElm || null //當前節點 el // } var parentVnode = options._parentVnode; opts.parent = options.parent; //組件的父節點 opts._parentVnode = parentVnode; //組件的 虛擬vonde 父節點 opts._parentElm = options._parentElm; //父節點的dom el opts._refElm = options._refElm; //當前節點 el var vnodeComponentOptions = parentVnode.componentOptions; //組件參數 opts.propsData = vnodeComponentOptions.propsData; //組件數據 opts._parentListeners = vnodeComponentOptions.listeners;//組件 事件 opts._renderChildren = vnodeComponentOptions.children; //組件子節點 opts._componentTag = vnodeComponentOptions.tag; //組件的標籤 if (options.render) { //渲染函數 opts.render = options.render; //渲染函數 opts.staticRenderFns = options.staticRenderFns; //靜態渲染函數 } } //解析new Vue constructor上的options拓展參數屬性的 合併 過濾去重數據 function resolveConstructorOptions(Ctor) { var options = Ctor.options; // 有super屬性,說明Ctor是Vue.extend構建的子類 繼承的子類 if (Ctor.super) { //超類 var superOptions = resolveConstructorOptions(Ctor.super); //回調超類 表示繼承父類 var cachedSuperOptions = Ctor.superOptions; // Vue構造函數上的options,如directives,filters,.... if (superOptions !== cachedSuperOptions) { //判斷若是 超類的options不等於子類的options 的時候 // super option changed, // need to resolve new options. //超級選項改變, //須要解決新的選項。 Ctor.superOptions = superOptions; //讓他的超類選項賦值Ctor.superOptions // check if there are any late-modified/attached options (#4976) 檢查是否有任何後期修改/附加選項(#4976) // 解決修改選項 轉義數據 合併 數據 var modifiedOptions = resolveModifiedOptions(Ctor); // update base extend options 更新基本擴展選項 if (modifiedOptions) { //extendOptions合併拓展參數 extend(Ctor.extendOptions, modifiedOptions); } // 優先取Ctor.extendOptions 將兩個對象合成一個對象 將父值對象和子值對象合併在一塊兒,而且優先取值子值,若是沒有則取子值 options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions); if (options.name) { //若是參數含有name 組件name options.components[options.name] = Ctor; } } } return options //返回參數 } //解決修改options 轉義數據 合併 數據 function resolveModifiedOptions(Ctor) { var modified; var latest = Ctor.options; //獲取選項 var extended = Ctor.extendOptions; //獲取拓展的選項 var sealed = Ctor.sealedOptions; //獲取子類選項 for (var key in latest) { //遍歷最新選項 if (latest[key] !== sealed[key]) { //若是選項不等於子類選項 if (!modified) { modified = {}; } //合併參數 modified[key] = dedupe(latest[key], extended[key], sealed[key]); } } //返回合併後的參數 return modified } //轉換判斷最新的選項是不是數組,若是是數組則將他們拓展和最新還有自選項 合併數組。若是是對象直接返回最新的對象 function dedupe( latest,//最新的選項 extended, //拓展的選項 sealed //獲取子類選項 ) { // compare latest and sealed to ensure lifecycle hooks won't be duplicated // between merges //比較最新的和密封的,確保生命週期鉤子不會重複 //之間的合併 if (Array.isArray(latest)) { //若是是數組 var res = []; sealed = Array.isArray(sealed) ? sealed : [sealed]; //對象轉義數組 extended = Array.isArray(extended) ? extended : [extended];//對象轉義數組 for (var i = 0; i < latest.length; i++) { // push original options and not sealed options to exclude duplicated options //推進原始選項和非密封選項排除重複的選項 過濾重複選項 if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) { res.push(latest[i]); } } //返回數組 return res } else { //返回對象 return latest } } //vue 構造函數 function Vue(options) { if ("development" !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword'); } this._init(options); } initMixin(Vue); //初始化vue stateMixin(Vue); //數據綁定,$watch方法 eventsMixin(Vue); // 初始化事件綁定方法 lifecycleMixin(Vue); // 初始化vue 更新 銷燬 函數 renderMixin(Vue); //初始化vue 須要渲染的函數 /* */ // 初始化vue 安裝插件函數 function initUse(Vue) { //安裝 Vue.js 插件。 Vue.use = function (plugin) { var installedPlugins = (this._installedPlugins || (this._installedPlugins = [])); // if (installedPlugins.indexOf(plugin) > -1) { //判斷是否已經安裝過插件了 return this } // additional parameters//額外的參數 var args = toArray(arguments, 1); //變成真的數組 args.unshift(this); //在前面添加 if (typeof plugin.install === 'function') { //若是plugin.install 是個函數 則執行安裝 plugin.install.apply(plugin, args); } else if (typeof plugin === 'function') { //若是plugin 是個函數則安裝 plugin.apply(null, args); } installedPlugins.push(plugin); // 將已經安裝過的插件添加到隊列去 return this }; } /* */ //初始化vue mixin 函數 function initMixin$1(Vue) { Vue.mixin = function (mixin) { // 合併 對象 this.options = mergeOptions(this.options, mixin); return this }; } /* */ //初始化 vue extend 函數 function initExtend(Vue) { /** * Each instance constructor, including Vue, has a unique * cid. This enables us to create wrapped "child * constructors" for prototypal inheritance and cache them. */ Vue.cid = 0; var cid = 1; /** Vue.extend//使用基礎 Vue 構造器,建立一個「子類」。參數是一個包含組件選項的對象。合併繼承new 實例化中的拓展參數或者是用戶直接使用Vue.extend 的拓展參數。把對象轉義成組件構造函數。建立一個sub類 構造函數是VueComponent,合併options參數,把props屬性和計算屬性添加到觀察者中。//若是組件含有名稱 則 把這個對象存到 組件名稱中, 在options拓展參數的原型中能獲取到該數據Sub.options.components[name] = Sub 簡稱Ctor,返回該構造函數 */ Vue.extend = function (extendOptions) { //使用基礎 Vue 構造器,建立一個「子類」。參數是一個包含組件選項的對象。 extendOptions = extendOptions || {}; var Super = this; var SuperId = Super.cid; var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}); //組件構造函數 if (cachedCtors[SuperId]) { //父類 超類id return cachedCtors[SuperId] //獲取 超類 } var name = extendOptions.name || Super.options.name; //獲取組件的name if ("development" !== 'production' && name) { // 驗證組件名稱 必須是大小寫,而且是-橫杆 validateComponentName(name); } //實例化 組件 對象 var Sub = function VueComponent(options) { console.log('==this._init') console.log(this._init) // vue中的_init 函數 Vue.prototype._init this._init(options); }; //建立一個對象 繼承 超類的原型 Sub.prototype = Object.create(Super.prototype); //讓他的構造函數指向回來,防止繼承擾亂。 Sub.prototype.constructor = Sub; //id 加加。標誌 不一樣的組件 Sub.cid = cid++; //合併參數 Sub.options = mergeOptions( Super.options, extendOptions ); //記錄超類 Sub['super'] = Super; // For props and computed properties, we define the proxy getters on // the Vue instances at extension time, on the extended prototype. This // avoids Object.defineProperty calls for each instance created. //對於道具和計算屬性,咱們定義代理getter //在擴展原型上的擴展時的Vue實例。這避免爲建立的每一個實例調用Object.defineProperty。 if (Sub.options.props) { //獲取props屬性 若是有 //初始化屬性 而且把組件的屬性 加入 觀察者中 initProps$1(Sub); } if (Sub.options.computed) { //組件計算屬性 //定義計算屬性 而且 把屬性的數據 添加到對象監聽中 initComputed$1(Sub); } // allow further extension/mixin/plugin usage 容許進一步的擴展/混合/插件使用 Sub.extend = Super.extend; Sub.mixin = Super.mixin; Sub.use = Super.use; // create asset registers, so extended classes // can have their private assets too. //建立資產註冊,因此擴展類 //也能夠擁有他們的私人資產。 // var ASSET_TYPES = [ // 'component', //組建指令 // 'directive', //定義指令 指令 // 'filter' //過濾器指令 // ]; ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type]; }); // enable recursive self-lookup 使遞歸self-lookup if (name) { //若是組件含有名稱 則 把這個對象存到 組件名稱中, 在options拓展參數的原型中能獲取到該數據 console.log(name) Sub.options.components[name] = Sub; } // keep a reference to the super options at extension time. // later at instantiation we can check if Super's options have // been updated. //在擴展時保留對超級選項的引用。 //稍後在實例化時,咱們能夠檢查Super的選項是否具備 //更新。 Sub.superOptions = Super.options; //超類 父類的拓展參數 Sub.extendOptions = extendOptions; //子類拓參數 Sub.sealedOptions = extend({}, Sub.options); //合併 // cache constructor cachedCtors[SuperId] = Sub; // 當前緩存的構造函數 console.log(cachedCtors) return Sub }; } //初始化屬性 而且把組件的屬性 加入 觀察者中 function initProps$1(Comp) { var props = Comp.options.props; //組件屬性 for (var key in props) { // proxy(Comp.prototype, "_props", key); } } //初始化 組件計算屬性 function initComputed$1(Comp) { var computed = Comp.options.computed; for (var key in computed) { //定義計算屬性 而且 把屬性的數據 添加到對象監聽中 defineComputed(Comp.prototype, key, computed[key]); } } /* * 爲vue 添加 靜態方法component,directive,,filter * */ function initAssetRegisters(Vue) { /** * Create asset registration methods. * * * // var ASSET_TYPES = [ // 'component', //組建指令 // 'directive', //定義指令 指令 // 'filter' //過濾器指令 // ]; *爲vue 添加 靜態方法component,directive,filter * */ ASSET_TYPES.forEach(function (type) { Vue[type] = function ( id, //id definition //new Vue拓展參數對象 ) { console.log(definition) if (!definition) { //若是definition不存在 return this.options[type + 's'][id] //返回 } else { /* istanbul ignore if */ if ("development" !== 'production' && type === 'component') { // 驗證組件名稱 必須是大小寫,而且是-橫杆 validateComponentName(id); } if (type === 'component' && isPlainObject(definition)) { //若是類型是組件 definition.name = definition.name || id; //名稱若是有定義就獲取 若是沒有 就按照id的來 definition = this.options._base.extend(definition); // Class inheritance 類繼承 用於vue多個組件中的合併拓展參數 } if (type === 'directive' && typeof definition === 'function') { //若是類型是指令 definition = {bind: definition, update: definition}; } this.options[type + 's'][id] = definition; //返回集合 return definition } }; }); } /* * 獲取組件的名稱 */ function getComponentName(opts) { return opts && (opts.Ctor.options.name || opts.tag) } // 判斷pattern 中是否還有 name function matches(pattern, name) { if (Array.isArray(pattern)) { //若是是數組 return pattern.indexOf(name) > -1 // 是否存在 } else if (typeof pattern === 'string') { //若是是字符串 return pattern.split(',').indexOf(name) > -1 //判斷是否存在 } else if (isRegExp(pattern)) { // 若是是正則 則用正則表示 return pattern.test(name) } /* istanbul ignore next */ return false } function pruneCache(keepAliveInstance, //當前保持活着的實例 filter //函數過濾器 ) { var cache = keepAliveInstance.cache; // 控對象 var keys = keepAliveInstance.keys; //獲取key var _vnode = keepAliveInstance._vnode; for (var key in cache) { // 循環 var cachedNode = cache[key]; //獲取值 if (cachedNode) { //值存在 var name = getComponentName(cachedNode.componentOptions); // 獲取組件的名稱 if (name && !filter(name)) { //若是name已經被銷燬掉 pruneCacheEntry( //檢測緩存中的組件,若是不是當前激活的組件則銷燬 cache, key, keys, _vnode ); } } } } //檢測緩存中的組件,若是不是當前激活的組件則銷燬 function pruneCacheEntry(cache, //緩存對象 key, //單個key keys, //多個key current //當前虛擬dom ) { var cached$$1 = cache[key]; //獲取值遍歷中的值 if (cached$$1 && (!current || cached$$1.tag !== current.tag)) { //判斷遍歷中的值 若是不等於當前活躍的組件則讓他銷燬 cached$$1.componentInstance.$destroy(); } cache[key] = null; remove(keys, key); } var patternTypes = [String, RegExp, Array]; //類型 var KeepAlive = { // <keep-alive> 包裹動態組件時,會緩存不活動的組件實例,而不是銷燬它們。 name: 'keep-alive', abstract: true, //標準是靜態組件 props: { include: patternTypes, // 設置include類型 容許[String, RegExp, Array] 緩存尚未銷燬的組件 exclude: patternTypes, // 設置include類型 容許[String, RegExp, Array] 緩存已經被銷燬的組件 max: [String, Number] // 設置include類型 容許 [String, Number] }, created: function created() { //created生命週期 this.cache = Object.create(null); //建立一個緩存的空對象 this.keys = []; //緩存key }, destroyed: function destroyed() { //銷燬 生命週期 var this$1 = this; for (var key in this$1.cache) { //銷燬全部組件 pruneCacheEntry( this$1.cache, key, this$1.keys ); } }, mounted: function mounted() { //組件初始化 生命週期 var this$1 = this; this.$watch( 'include', //監聽 include 數據是否有變化 function (val) { //監聽爲完後更新的值 pruneCache( this$1, function (name) { // 判斷include 中是否還有 name 就證實組件還在 return matches(val, name); //判斷include 對象中 name 不存在了 就 調用 檢測緩存中的組件,若是不是當前激活的組件則銷燬 }); }); this.$watch( 'exclude', //監聽 exclude 數據是否有變化 function (val) { pruneCache(this$1, function (name) { //若是exclude 對象中存在name 不存在了 就 調用 檢測緩存中的組件,若是不是當前激活的組件則銷燬 return !matches(val, name); }); }); }, // 渲染 keepAlive 組件 render: function render() { var slot = this.$slots.default; //獲取插槽 var vnode = getFirstComponentChild(slot); // 獲取插槽子組件 var componentOptions = vnode && vnode.componentOptions; //獲取組件參數 if (componentOptions) { // check pattern var name = getComponentName(componentOptions); //獲取組件名稱 var ref = this; var include = ref.include; //獲取include var exclude = ref.exclude; //獲取exclude if ( // not included 沒有包括在內 (include && (!name || !matches(include, name))) || //若是include存在,而且name不存在,或者name不存在include中則進if // excluded (exclude && name && matches(exclude, name)) //若是exclude存在 而且name存在 而且name存在exclude對象中 ) { return vnode //返回虛擬dom } var ref$1 = this; //獲取當前this vm var cache = ref$1.cache; //緩存的對象 var keys = ref$1.keys; //獲取keys 全部的key var key = vnode.key == null // 判斷當前虛擬dom得key 是否爲空 // same constructor may get registered as different local components // so cid alone is not enough (#3269) //同一個構造函數能夠註冊爲不一樣的本地組件 //單靠cid是不夠的(#3269) //這裏三木是 判斷組件是否有cid 若是有 則 判斷 是否有組件標籤,若是有組件標籤則返回 '::'+組件標籤,若是沒有組件標籤則返回空。若是沒有 判斷組件是否有cid 則返回 vnode.key ? componentOptions.Ctor.cid + (componentOptions.tag ? ("::" + (componentOptions.tag)) : '') : vnode.key; if (cache[key]) { //獲取值 若是key存在 vnode.componentInstance = cache[key].componentInstance; //直接獲取組件實例化 // make current key freshest remove(keys, key); //把key添加到末端 keys.push(key); } else { //將虛擬dom緩存起來 cache[key] = vnode; keys.push(key); //key緩存起來 // prune oldest entry //刪除最老的條目 //設定最大的緩存值 if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry( cache, keys[0], //第一個key keys, //keys[] this._vnode //當前活躍的組件 ); } } vnode.data.keepAlive = true; } return vnode || (slot && slot[0]) } } // var builtInComponents = { KeepAlive: KeepAlive } /* * 初始化全局api 而且暴露 一些靜態方法 */ function initGlobalAPI(Vue) { // config var configDef = {}; configDef.get = function () { return config; }; { configDef.set = function () { warn( 'Do not replace the Vue.config object, set individual fields instead.' ); }; } Object.defineProperty(Vue, 'config', configDef); // exposed util methods. // NOTE: these are not considered part of the public API - avoid relying on // them unless you are aware of the risk. //暴露的util方法。 //注意:這些不是公共API的一部分——避免依賴 //除非你意識到其中的風險。 Vue.util = { warn: warn, //警告函數 extend: extend, //繼承方式 mergeOptions: mergeOptions, //合併參數 defineReactive: defineReactive // 經過defineProperty的set方法去通知notify()訂閱者subscribers有新的值修改 添加觀察者 get set方法 }; Vue.set = set; //暴露接口靜態方法 set Vue.delete = del; //暴露接口靜態方法 delete 方法 Vue.nextTick = nextTick; // 暴露接口靜態方法 nextTick 方法 Vue.options = Object.create(null); //建立一個空的參數 // var ASSET_TYPES = [ // 'component', //組建指令 // 'directive', //定義指令 指令 // 'filter' //過濾器指令 // ]; // //添加components ,directives, filters 指令組件 控對象 ASSET_TYPES.forEach(function (type) { Vue.options[type + 's'] = Object.create(null); }); // this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. //用來標識擴展全部普通對象的「基」構造函數 // Weex的多實例場景中的組件。 Vue.options._base = Vue; extend(Vue.options.components, builtInComponents); //合併 KeepAlive參數中的組件對象 initUse(Vue); // 初始化vue 安裝插件函數 initMixin$1(Vue); //初始化vue mixin 函數 initExtend(Vue); //初始化 vue extend 函數 initAssetRegisters(Vue); //爲vue 添加 靜態方法component,directive,,filter } //初始化全局api 而且暴露 一些靜態方法 initGlobalAPI(Vue); //監聽是不是服務器環境 Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering }); // 獲取$ssrContext Object.defineProperty(Vue.prototype, '$ssrContext', { get: function get() { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext } }); // expose FunctionalRenderContext for ssr runtime helper installation //爲ssr運行時幫助程序安裝公開FunctionalRenderContext 建立 虛擬dom vonde 渲染 slots插槽 Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext }); Vue.version = '2.5.16'; //版本號 // these are reserved for web because they are directly compiled away // during template compilation //這些是爲web保留的,由於它們是直接編譯掉的 //在模板編譯期間 // isReservedAttr是一個函數判斷 傳入字符串style或者class的是否返回真 var isReservedAttr = makeMap('style,class'); // attributes that should be using props for binding //用於綁定props的屬性 acceptValue是一個函數判斷傳入字符串'input,textarea,option,select,progress'的是否返回真 var acceptValue = makeMap('input,textarea,option,select,progress'); //校驗屬性 var mustUseProp = function (tag, type, attr) { /* * 1. attr === 'value', tag 必須是 'input,textarea,option,select,progress' 其中一個 type !== 'button' * 2. attr === 'selected' && tag === 'option' * 3. attr === 'checked' && tag === 'input' * 4. attr === 'muted' && tag === 'video' * 的狀況下爲真 * */ return ( (attr === 'value' && acceptValue(tag)) && type !== 'button' || // (attr === 'selected' && tag === 'option') || (attr === 'checked' && tag === 'input') || (attr === 'muted' && tag === 'video') ) }; //contenteditable 是否能夠編輯屬性 //draggable html5設置是否能夠拖動 //spellcheck 進行拼寫檢查的可編輯段落: var isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck'); //檢查是不是html中的布爾值屬性 就是該屬性只有 true 和 false // HTML5的boolean值得屬性: // checkd,checked="",checked="checked",checked=true,checke=false 只要有checked屬性,其屬性值有沒有都認爲選中狀態 var isBooleanAttr = makeMap( 'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' + 'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' + 'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' + 'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' + 'required,reversed,scoped,seamless,selected,sortable,translate,' + 'truespeed,typemustmatch,visible' ); var xlinkNS = 'http://www.w3.org/1999/xlink'; //判斷是不是xmlns 屬性 例子 <bookstore xmlns:xlink="http://www.w3.org/1999/xlink"> var isXlink = function (name) { return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink' }; //獲取xml link的屬性 var getXlinkProp = function (name) { return isXlink(name) ? name.slice(6, name.length) : '' }; //判斷val 是不是 null 或者 false var isFalsyAttrValue = function (val) { return val == null || val === false }; /* * class 轉碼獲取vonde 中的staticClass 靜態class 和class動態class轉義成真實dom須要的class格式。而後返回class字符串 * */ function genClassForVnode(vnode) { var data = vnode.data; //獲取vnode.data 數據 標籤屬性數據 var parentNode = vnode; //獲取 父節點 var childNode = vnode; //獲取子節點 // this.componentInstance = undefined; /*當前節點對應的組件的實例*/ while (isDef(childNode.componentInstance)) { //若是定義了componentInstance 組件實例 遞歸合併子組件的class childNode = childNode.componentInstance._vnode; //上一個vnode if (childNode && childNode.data) { data = mergeClassData(childNode.data, data); } } while (isDef(parentNode = parentNode.parent)) { //遞歸父組件parent 合併父組件class if (parentNode && parentNode.data) { //合併calss數據 data = mergeClassData(data, parentNode.data); } } return renderClass(data.staticClass, data.class) //渲染calss } //合併calss數據 function mergeClassData(child, parent) { return { staticClass: concat(child.staticClass, parent.staticClass), //靜態calss class: isDef(child.class) //data中動態calss ? [child.class, parent.class] : parent.class } } //渲染calss 這裏獲取到已經轉碼的calss function renderClass( staticClass, //靜態class dynamicClass //動態calss ) { if (isDef(staticClass) || isDef(dynamicClass)) { //鏈接class return concat( staticClass, //靜態的class 就是class 屬性的class stringifyClass(dynamicClass) //動態class就是vonde對象中的class 須要stringifyClass轉義成 真實dom須要的class ) } /* istanbul ignore next */ return '' } //class 鏈接 function concat(a, b) { return a ? ( b ? (a + ' ' + b) : a ): (b || '') } //轉碼 class,把數組格式,對象格式的calss 所有轉化成 字符串格式 function stringifyClass(value) { if (Array.isArray(value)) { //若是是數組 //數組變成字符串,而後用空格 隔開 拼接 起來變成字符串 return stringifyArray(value) } if (isObject(value)) { return stringifyObject(value) } //直到所有轉成 字符串才結束遞歸 if (typeof value === 'string') { return value } /* istanbul ignore next */ return '' } //數組字符串變成字符串,而後用空格 隔開 拼接 起來變成字符串 function stringifyArray(value) { var res = ''; var stringified; for (var i = 0, l = value.length; i < l; i++) { if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') { if (res) { res += ' '; } res += stringified; } } return res } //對象字符串變成字符串,而後用空格 隔開 拼接 起來變成字符串 function stringifyObject(value) { var res = ''; for (var key in value) { if (value[key]) { if (res) { res += ' '; } res += key; } } return res } /* * * */ var namespaceMap = { svg: 'http://www.w3.org/2000/svg', //svg標籤命名xmlns屬性 math: 'http://www.w3.org/1998/Math/MathML' //math 中的xmlns屬性聲明 XHTML 文件 }; //isHTMLTag 函數,驗證是不是html中的原始標籤 var isHTMLTag = makeMap( 'html,body,base,head,link,meta,style,title,' + 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' + 'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' + 'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' + 's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' + 'embed,object,param,source,canvas,script,noscript,del,ins,' + 'caption,col,colgroup,table,thead,tbody,td,th,tr,' + 'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' + 'output,progress,select,textarea,' + 'details,dialog,menu,menuitem,summary,' + 'content,element,shadow,template,blockquote,iframe,tfoot' ); // this map is intentionally selective, only covering SVG elements that may // contain child elements. //此映射是有意選擇的,只覆蓋可能的SVG元素 //包含子元素。 //isSVG函數 判斷svg 標籤,包括svg子元素標籤 var isSVG = makeMap( 'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' + 'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' + 'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view', true ); //判斷標籤是不是pre 若是是則返回真 var isPreTag = function (tag) { return tag === 'pre'; }; //保留標籤 判斷是否是真的是 html 原有的標籤 或者svg標籤 var isReservedTag = function (tag) { return isHTMLTag(tag) || isSVG(tag) }; //判斷 tag 是不是svg或者math 標籤 function getTagNamespace(tag) { //若是是svg if (isSVG(tag)) { return 'svg' } // basic support for MathML // note it doesn't support other MathML elements being component roots // MathML的基本支持 //注意,它不支持做爲組件根的其餘MathML元素 if (tag === 'math') { return 'math' } } var unknownElementCache = Object.create(null); //判斷是否是真的是 html 原有的標籤,判斷是不是瀏覽器標準標籤 包括標準html和svg標籤 //若是不是則返回真,這樣就是用戶自定義標籤 function isUnknownElement(tag) { /* istanbul ignore if */ if (!inBrowser) { //判斷是不是瀏覽器 return true } //保留標籤 判斷是否是真的是 html 原有的標籤 if (isReservedTag(tag)) { return false } //把標籤轉化成小寫 tag = tag.toLowerCase(); /* istanbul ignore if */ //緩存未知標籤 if (unknownElementCache[tag] != null) { //若是緩存有則返回出去 return unknownElementCache[tag] } //建立該標籤 var el = document.createElement(tag); //判斷是不是含有 - 的組件標籤 if (tag.indexOf('-') > -1) { // http://stackoverflow.com/a/28210364/1070244 return (unknownElementCache[tag] = ( el.constructor === window.HTMLUnknownElement || el.constructor === window.HTMLElement )) } else { //正則判斷標籤是不是HTMLUnknownElement return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString())) } } //map 對象中的[name1,name2,name3,name4] 變成這樣的map{name1:true,name2:true,name3:true,name4:true} //匹配'text,number,password,search,email,tel,url' var isTextInputType = makeMap('text,number,password,search,email,tel,url'); /* */ /** * Query an element selector if it's not an element already. * html5 獲取dom */ function query(el) { if (typeof el === 'string') { var selected = document.querySelector(el); if (!selected) { "development" !== 'production' && warn( 'Cannot find element: ' + el ); return document.createElement('div') } return selected } else { return el } } /* 建立一個真實的dom */ function createElement$1(tagName, vnode) { //建立一個真實的dom var elm = document.createElement(tagName); if (tagName !== 'select') { //若是不是select標籤則返回dom出去 return elm } // false or null will remove the attribute but undefined will not // false或null將刪除屬性,但undefined不會 if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) { //若是是select標籤 判斷是否設置了multiple屬性。若是設置了則加上去 elm.setAttribute('multiple', 'multiple'); } return elm } //XML createElementNS() 方法可建立帶有指定命名空間的元素節點。 //createElement差很少 建立一個dom節點 // document.createElementNS('http://www.w3.org/2000/svg','svg'); //建立一個真實的dom svg方式 function createElementNS(namespace, tagName) { // var namespaceMap = { // svg: 'http://www.w3.org/2000/svg', // math: 'http://www.w3.org/1998/Math/MathML' // }; return document.createElementNS(namespaceMap[namespace], tagName) } //建立文本節點真是dom節點 function createTextNode(text) { return document.createTextNode(text) } //建立一個註釋節點 function createComment(text) { return document.createComment(text) } //插入節點 在referenceNode dom 前面插入一個節點 function insertBefore(parentNode, newNode, referenceNode) { parentNode.insertBefore(newNode, referenceNode); } //刪除子節點 function removeChild(node, child) { node.removeChild(child); } //添加子節點 尾部 function appendChild(node, child) { node.appendChild(child); } //獲取父親子節點dom function parentNode(node) { return node.parentNode } //獲取下一個兄弟節點 function nextSibling(node) { return node.nextSibling } //獲取dom標籤名稱 function tagName(node) { return node.tagName } //設置dom 文本 function setTextContent(node, text) { node.textContent = text; } //設置組建樣式的做用域 function setStyleScope(node, scopeId) { node.setAttribute(scopeId, ''); } //Object.freeze()阻止修改現有屬性的特性和值,並阻止添加新屬性。 var nodeOps = Object.freeze({ createElement: createElement$1, //建立一個真實的dom createElementNS: createElementNS, //建立一個真實的dom svg方式 createTextNode: createTextNode, // 建立文本節點 createComment: createComment, // 建立一個註釋節點 insertBefore: insertBefore, //插入節點 在xxx dom 前面插入一個節點 removeChild: removeChild, //刪除子節點 appendChild: appendChild, //添加子節點 尾部 parentNode: parentNode, //獲取父親子節點dom nextSibling: nextSibling, //獲取下一個兄弟節點 tagName: tagName, //獲取dom標籤名稱 setTextContent: setTextContent, // //設置dom 文本 setStyleScope: setStyleScope //設置組建樣式的做用域 }); /* * ref 建立 更新 和 銷燬 事件 * */ var ref = { create: function create(_, vnode) { //建立註冊一個ref registerRef(vnode); }, update: function update(oldVnode, vnode) { //更新ref if (oldVnode.data.ref !== vnode.data.ref) { registerRef(oldVnode, true); //先刪除 registerRef(vnode); //在添加 } }, destroy: function destroy(vnode) { registerRef(vnode, true); //刪除銷燬ref } } //註冊ref或者刪除ref。好比標籤上面設置了ref='abc' 那麼該函數就是爲this.$refs.abc 註冊ref 把真實的dom存進去 function registerRef(vnode, isRemoval) { var key = vnode.data.ref; //獲取vond ref的字符串 if (!isDef(key)) { //若是沒有定義則不執行下面的代碼了 return } var vm = vnode.context; //vm 上下文 var ref = vnode.componentInstance || vnode.elm; //組件實例 或者 elm DOM 節點 var refs = vm.$refs; //獲取vm總共的refs if (isRemoval) { //標誌是否刪除ref if (Array.isArray(refs[key])) { //若是定義有多個同名的ref 則會定義爲一個數組,刪除refs 這個key 定義的數組 remove(refs[key], ref); //刪除ref } else if (refs[key] === ref) { //若是是單個的時候 refs[key] = undefined; //直接置空 } } else { if (vnode.data.refInFor) { //若是ref和for一塊兒使用的時候 if (!Array.isArray(refs[key])) { //refs[key] 不是數組 則變成一個數組 refs[key] = [ref]; } else if (refs[key].indexOf(ref) < 0) { //若是ref 不存在 refs的時候則添加進去 // $flow-disable-line refs[key].push(ref); } } else { refs[key] = ref; //若是是單個直接賦值 } } } /** * Virtual DOM patching algorithm based on Snabbdom by * Simon Friis Vindum (@paldepind) * Licensed under the MIT License * https://github.com/paldepind/snabbdom/blob/master/LICENSE * * modified by Evan You (@yyx990803) * * Not type-checking this because this file is perf-critical and the cost * of making flow understand it is not worth it. */ //建立一個空的vnode var emptyNode = new VNode('', {}, []); var hooks = ['create', 'activate', 'update', 'remove', 'destroy']; //sameVnode(oldVnode, vnode)2個節點的基本屬性相同,那麼就進入了2個節點的diff過程。 function sameVnode(a, b) { return ( a.key === b.key && ( //若是a的key 等於b的key ( a.tag === b.tag && // 若是a的tag 等於b的tag a.isComment === b.isComment && // 若是a和b 都是註釋節點 isDef(a.data) === isDef(b.data) && //若是a.data 和 b.data 都定義後,是組件,或者是都含有tag屬性 sameInputType(a, b) //相同的輸入類型。判斷a和b的屬性是否相同 ) || ( isTrue(a.isAsyncPlaceholder) && //判斷是不是異步的 a.asyncFactory === b.asyncFactory && isUndef(b.asyncFactory.error) ) ) ) } //相同的輸入類型。判斷a和b的屬性是否相同 function sameInputType(a, b) { if (a.tag !== 'input') { //若是a標籤不是input return true } var i; var typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type; //獲取a的tag標籤屬性 var typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type;//獲取b的tag標籤屬性 return typeA === typeB || //typeA和typeB 都相同 //匹配'text,number,password,search,email,tel,url' isTextInputType(typeA) && isTextInputType(typeB) } function createKeyToOldIdx(children, beginIdx, endIdx) { var i, key; var map = {}; for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key; if (isDef(key)) { map[key] = i; } } return map } //建立虛擬dom function createPatchFunction(backend) { /* var nodeOps = Object.freeze({ createElement: createElement$1, //建立一個真實的dom createElementNS: createElementNS, //建立一個真實的dom svg方式 createTextNode: createTextNode, // 建立文本節點 createComment: createComment, // 建立一個註釋節點 insertBefore: insertBefore, //插入節點 在xxx dom 前面插入一個節點 removeChild: removeChild, //刪除子節點 appendChild: appendChild, //添加子節點 尾部 parentNode: parentNode, //獲取父親子節點dom nextSibling: nextSibling, //獲取下一個兄弟節點 tagName: tagName, //獲取dom標籤名稱 setTextContent: setTextContent, // //設置dom 文本 setStyleScope: setStyleScope //設置組建樣式的做用域 }); modules=[ attrs, // attrs包含兩個方法create和update都是更新設置真實dom屬性值 {create: updateAttrs, update: updateAttrs } klass, //klass包含類包含兩個方法create和update都是更新calss。其實就是updateClass方法。 設置真實dom的class events, //更新真實dom的事件 domProps, //更新真實dom的props 屬性值 style, // 更新真實dom的style屬性。有兩個方法create 和update 不過函數都是updateStyle更新真實dom的style屬性值.將vonde虛擬dom的css 轉義成而且渲染到真實dom的css中 transition // 過分動畫 ref, //ref建立,更新 , 銷燬 函數 directives //自定義指令 建立 ,更新,銷燬函數 ] */ console.log(backend) var i, j; var cbs = {}; console.log('==backend==') console.log(backend) var modules = backend.modules; var nodeOps = backend.nodeOps; // 把鉤子函數添加到cbs隊列中 循環數字 var hooks = ['create', 'activate', 'update', 'remove', 'destroy']; for (i = 0; i < hooks.length; ++i) { cbs[hooks[i]] = []; //循環modules 數組 for (j = 0; j < modules.length; ++j) { //判斷modules上面是否有定義有 'create', 'activate', 'update', 'remove', 'destroy' if (isDef(modules[j][hooks[i]])) { //若是有則把他添加到cbs 對象數組中 cbs[hooks[i]].push(modules[j][hooks[i]]); //把鉤子函數添加到cbs隊列中 } } } /* cbs={ 'create':[], 'activate':[], 'update':[], 'remove':[], 'destroy:[] } */ //建立一個vnode節點 function emptyNodeAt(elm) { // tag, /*當前節點的標籤名*/ // data, /*當前節點對應的對象,包含了具體的一些數據信息,是一個VNodeData類型,能夠參考VNodeData類型中的數據信息*/ // children, //子節點 // text, //文本 // elm, /*當前節點*/ // context, /*編譯做用域*/ // componentOptions, /*組件的option選項*/ // asyncFactory return new VNode( nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm ) } //建立一個RmCb function createRmCb( childElm, //子節點 listeners //事件數組 ) { function remove() { //若是listeners === 0 的時候就刪除掉該子節點 if (--remove.listeners === 0) { removeNode(childElm); } } remove.listeners = listeners; return remove } //刪除真實的dom 參數el 是dom function removeNode(el) { // function parentNode(node) { // return node.parentNode // } //獲取父親dom var parent = nodeOps.parentNode(el); // element may have already been removed due to v-html / v-text // 元素可能已經因爲v-html / v-text而被刪除 //判斷父親dom是否存在 若是存在則 // function removeChild(node, child) { // node.removeChild(child); // } //刪除子節點 if (isDef(parent)) { nodeOps.removeChild(parent, el); } } // 檢查dom 節點的tag標籤 類型 是不是VPre 標籤 或者是判斷是不是瀏覽器自帶原有的標籤 function isUnknownElement$$1( vnode, //vnode inVPre //標記 標籤是否還有 v-pre 指令,若是沒有則是false ) { return ( !inVPre && // 標記 標籤是否還有 v-pre 指令,若是沒有則是false !vnode.ns && !( config.ignoredElements.length && config.ignoredElements.some(function (ignore) { //some() 方法測試是否至少有一個元素經過由提供的函數實現的測試。 return isRegExp(ignore) //判斷是不是正則對象 ? ignore.test(vnode.tag) : ignore === vnode.tag }) ) && //判斷是否是真的是 html 原有的標籤,判斷是不是瀏覽器標準標籤 config.isUnknownElement(vnode.tag) ) } var creatingElmInVPre = 0; //建立dom 節點 function createElm( vnode, //vnode 節點, insertedVnodeQueue, //插入Vnode隊列 parentElm, //父親節點 refElm, //當前的節點的兄弟節點 nested, //嵌套 ownerArray, //主數組 節點 index //索引 ) { console.log(vnode) //判斷是否認義有vnode.elm 和 定義有ownerArray if (isDef(vnode.elm) && isDef(ownerArray)) { // This vnode was used in a previous render! // now it's used as a new node, overwriting its elm would cause // potential patch errors down the road when it's used as an insertion // reference node. Instead, we clone the node on-demand before creating // associated DOM element for it. //這個vnode在以前的渲染中使用過! //如今它被用做一個新節點,覆蓋它的elm將致使 //當它被用做插入時,未來可能會出現補丁錯誤 //引用節點。相反,咱們在建立以前按需克隆節點 //關聯的DOM元素。 //克隆一個新的節點 vnode = ownerArray[index] = cloneVNode(vnode); } vnode.isRootInsert = !nested; // for transition enter check //對於過渡輸入檢查 //建立組件,而且判斷它是否實例化過 if (createComponent( vnode, //虛擬dom vonde insertedVnodeQueue, //插入Vnode隊列 parentElm,//父親節點 refElm //當前節點 )) { return } var data = vnode.data; //vnode 數據 如 屬性等 var children = vnode.children; //vonde 子節點 var tag = vnode.tag; //vonde 標籤 if (isDef(tag)) { //若是組件標籤訂義了 console.log(vnode) { if (data && data.pre) { //標記是不是pre 標籤吧 creatingElmInVPre++; } // 檢查dom 節點的tag標籤 類型 是不是VPre 標籤 或者是判斷是不是瀏覽器自帶原有的標籤 if (isUnknownElement$$1(vnode, creatingElmInVPre)) { warn( 'Unknown custom element: <' + tag + '> - did you ' + 'register the component correctly? For recursive components, ' + 'make sure to provide the "name" option.', vnode.context ); } } vnode.elm = vnode.ns // 字符串值,可爲此元素節點規定命名空間的名稱。 多是svg 或者 math 節點 ? nodeOps.createElementNS(vnode.ns, tag) // 字符串值,可爲此元素節點規定命名空間的名稱。 多是svg 或者 math 節點 : nodeOps.createElement(tag, vnode); //html建立一個dom 節點 setScope(vnode); //設置樣式的做用域 console.log('====tag====' + tag) /* istanbul ignore if */ { //建立子節點 createChildren( vnode, //虛擬dom children, // vonde 子節點 insertedVnodeQueue //已經安裝好的vonde ); if (isDef(data)) { // invokeCreateHooks,循環cbs.create 鉤子函數,而且執行調用,其實cbs.create 鉤子函數就是platformModules中的attrs中 updateAttrs更新屬性函數。若是是組件則調用componentVNodeHooks中的 create invokeCreateHooks(vnode, insertedVnodeQueue); } //插入一個真實的dom,若是ref$$1.parentNode等於parent是。ref$$1和elm他們是兄弟節點則插入ref$$1前面 //若是ref$$1的ref$$1.parentNode不等於parent。那麼elm就直接append到parent中 insert( parentElm, vnode.elm, refElm ); } if ("development" !== 'production' && data && data.pre) { creatingElmInVPre--; } } else if (isTrue(vnode.isComment)) { vnode.elm = nodeOps.createComment(vnode.text); //插入一個真實的dom,若是ref$$1.parentNode等於parent是。ref$$1和elm他們是兄弟節點則插入ref$$1前面 //若是ref$$1的ref$$1.parentNode不等於parent。那麼elm就直接append到parent中 insert(parentElm, vnode.elm, refElm); } else { vnode.elm = nodeOps.createTextNode(vnode.text); //插入一個真實的dom,若是ref$$1.parentNode等於parent是。ref$$1和elm他們是兄弟節點則插入ref$$1前面 //若是ref$$1的ref$$1.parentNode不等於parent。那麼elm就直接append到parent中 insert(parentElm, vnode.elm, refElm); } } //若是組件已經實例化過了纔會初始化組件,纔會返回值爲真 function createComponent( //建立組件 vnode, insertedVnodeQueue,// insertedVnodeQueue 插入vnode隊列 parentElm, //父節點 dom refElm //當前節點 dom ) { var i = vnode.data; //標籤 dom 中的屬性 或者是組件 console.log(i) if (isDef(i)) { //若是i有定義 var isReactivated = isDef(vnode.componentInstance) && i.keepAlive; //若是已經實例化過,而且是keepAlive組件 if (isDef(i = i.hook) && isDef(i = i.init)) { //觸發鉤子函數。或者init, console.log(i) i( vnode, false /* hydrating */, parentElm, refElm ); } // after calling the init hook, if the vnode is a child component // it should've created a child instance and mounted it. the child // component also has set the placeholder vnode's elm. // in that case we can just return the element and be done. //調用init鉤子後,若是vnode是一個子組件 //它應該建立一個子實例並掛載它。這個孩子 //組件還設置了佔位符vnode的elm。 //在這種狀況下,咱們只須要返回元素就能夠了。 if (isDef(vnode.componentInstance)) { //組件已經實例過 //initComponent 初始化組建,若是沒有tag標籤則去更新真實dom的屬性,若是有tag標籤,則註冊或者刪除ref 而後爲insertedVnodeQueue.push(vnode);確保調用插入鉤子若是vnode.data.pendingInsert爲反正則也爲insertedVnodeQueue插入緩存 vnode.data.pendingInsert initComponent( vnode, insertedVnodeQueue ); //判斷是不是真的true if (isTrue(isReactivated)) { //激活組件 reactivateComponent( vnode, //新的vonde insertedVnodeQueue, // parentElm, refElm ); } return true } } } // 初始化組建,若是沒有tag標籤則去更新真實dom的屬性,若是有tag標籤,則註冊或者刪除ref 而後爲insertedVnodeQueue.push(vnode);確保調用插入鉤子若是vnode.data.pendingInsert爲反正則也爲insertedVnodeQueue插入緩存 vnode.data.pendingInsert function initComponent( vnode, //node 虛擬dom insertedVnodeQueue //插入Vnode隊列 記錄已經實例化過的組件 ) { if (isDef(vnode.data.pendingInsert)) { //模板緩存 待插入 insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert); vnode.data.pendingInsert = null; } vnode.elm = vnode.componentInstance.$el; //組件實例 if (isPatchable(vnode)) { // 判斷組件是否認義有 tag標籤 //invokeCreateHooks,循環cbs.create 鉤子函數,而且執行調用,其實cbs.create 鉤子函數就是platformModules中的attrs中 updateAttrs更新屬性函數。若是是組件則調用componentVNodeHooks中的 create invokeCreateHooks(vnode, insertedVnodeQueue); //爲有做用域的CSS設置做用域id屬性。 //這是做爲一種特殊狀況來實現的,以免開銷 //經過常規屬性修補過程。 setScope(vnode); } else { // empty component root. // skip all element-related modules except for ref (#3455) //空組件根。 //跳過除ref(#3455)以外的全部與元素相關的模塊 //註冊ref registerRef(vnode); // make sure to invoke the insert hook //確保調用插入鉤子 insertedVnodeQueue.push(vnode); } } //激活組建。把vonde添加到parentElm中。若是是transition組件則 調用 transition中的activate就是_enter function reactivateComponent( vnode, //新的vonde insertedVnodeQueue,// parentElm, refElm ) { var i; // hack for #4339: a reactivated component with inner transition // does not trigger because the inner node's created hooks are not called // again. It's not ideal to involve module-specific logic in here but // there doesn't seem to be a better way to do it. //破解#4339:一個內部轉換的從新激活的組件 //不觸發,由於沒有調用內部節點建立的鉤子 //一次。在這裏使用特定於模塊的邏輯並不理想,可是 //彷佛沒有比這更好的方法了。 var innerNode = vnode; while (innerNode.componentInstance) { //若是已經實例過的 innerNode = innerNode.componentInstance._vnode; // 標誌上一個 vonde 就是舊的 vonde if (isDef(i = innerNode.data) && isDef(i = i.transition)) { //若是是transition 組件 _enter for (i = 0; i < cbs.activate.length; ++i) { cbs.activate[i](emptyNode, innerNode); //調用 transition中的activate就是_enter } insertedVnodeQueue.push(innerNode); break } } // unlike a newly created component, // a reactivated keep-alive component doesn't insert itself //與新建立的組件不一樣, //從新激活的keep-alive組件不會插入 insert( parentElm, //父真實dom vnode.elm, //當前vonde的真實dom refElm //當前vonde的真實dom的兄弟節點或者不是 ); } //插入一個真實的dom,若是ref$$1.parentNode等於parent是。ref$$1和elm他們是兄弟節點則插入ref$$1前面 //若是ref$$1的ref$$1.parentNode不等於parent。那麼elm就直接append到parent中 function insert( parent,//父真實dom elm,//當前vonde的真實dom ref$$1 // 當前vonde的真實dom的兄弟節點或者不是 ) { if (isDef(parent)) { if (isDef(ref$$1)) { if (ref$$1.parentNode === parent) { nodeOps.insertBefore(parent, elm, ref$$1); } } else { nodeOps.appendChild(parent, elm); } } } //建立子節點 function createChildren( vnode, //虛擬dom children, //子節點 insertedVnodeQueue //插入Vnode隊列 ) { console.log('==children==') console.log(children) if (Array.isArray(children)) { //若是children 是數組 { //檢測key是否有重複 checkDuplicateKeys(children); } //創造節點 for (var i = 0; i < children.length; ++i) { //創造節點 createElm( children[i], //vnode 節點 insertedVnodeQueue, //插入Vnode隊列 vnode.elm, //父親節點 null, //當前節點 true, //嵌套 children, //主數組 節點 i //索引 ); } //判斷數據類型是不是string,number,symbol,boolean } else if (isPrimitive(vnode.text)) { //添加子節點 建立一個文本節點 nodeOps.appendChild( vnode.elm, nodeOps.createTextNode(String(vnode.text)) //建立文本節點真是dom節點 ); } } //循環組件實例 是否認義有 tag標籤 function isPatchable(vnode) { while (vnode.componentInstance) { //組件實例 循環n層組件實例 vnode = vnode.componentInstance._vnode; } //判斷組件是否認義有 tag標籤 return isDef(vnode.tag) } // invokeCreateHooks,循環cbs.create 鉤子函數,而且執行調用,其實cbs.create 鉤子函數就是platformModules中的attrs中 updateAttrs更新屬性函數。若是是組件則調用componentVNodeHooks中的 create function invokeCreateHooks(vnode, insertedVnodeQueue) { // 這裏的cbs以下: /* cbs={ 'create':[], 'activate':[], 'update':[], 'remove':[], 'destroy:[] } */ // activate:Array(1) // create:Array(8) // destroy:Array(2) // remove:Array(1) // update:Array(7) // __proto__:Object console.log('==cbs.create==') console.log(cbs) for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { cbs.create[i$1](emptyNode, vnode); } i = vnode.data.hook; // Reuse variable 若是他是組件 console.log(i) // 若是是組件則調用componentVNodeHooks中的 create if (isDef(i)) { if (isDef(i.create)) { //可是componentVNodeHooks 中沒有create 因此下面可能不會執行 i.create(emptyNode, vnode); } if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); } } } // set scope id attribute for scoped CSS. // this is implemented as a special case to avoid the overhead // of going through the normal attribute patching process. //爲有做用域的CSS設置做用域id屬性。 //這是做爲一種特殊狀況來實現的,以免開銷 //經過常規屬性修補過程。 function setScope(vnode) { var i; //fnScopeId 判斷css做用 有沒有設置Scope 若是有則設置 css做用域 if (isDef(i = vnode.fnScopeId)) { nodeOps.setStyleScope(vnode.elm, i); } else { var ancestor = vnode; while (ancestor) { // context, /*編譯做用域*/ 上下文 判斷vnode 是否設置有做用於 與css是否設置有做用域 _scopeId 是放在dom屬性上面作標記 if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) { //設置css做用域 nodeOps.setStyleScope(vnode.elm, i); } //循環父節點 ancestor = ancestor.parent; } } // for slot content they should also get the scopeId from the host instance. // 對於插槽內容,它們還應該從主機實例得到scopeId // activeInstance 多是 vm if (isDef(i = activeInstance) && i !== vnode.context && i !== vnode.fnContext && isDef(i = i.$options._scopeId) ) { nodeOps.setStyleScope(vnode.elm, i); } } function addVnodes(parentElm, //父親節點 refElm, //當前點 vnodes, //虛擬dom startIdx, // 開始index endIdx, // 結束index insertedVnodeQueue //插入Vnode隊列 ) { for (; startIdx <= endIdx; ++startIdx) { //創造dom節點 createElm( vnodes[startIdx], //vnode 節點 insertedVnodeQueue, //插入Vnode隊列 parentElm, //父親節點 refElm, //當前節點 false, //嵌套 vnodes, //vnodes 數組 startIdx //索引 ); } } //組件銷燬,觸發銷燬鉤子函數 function invokeDestroyHook(vnode) { var i, j; var data = vnode.data; //若是vonde有標籤屬性 if (isDef(data)) { //若是vonde有標籤屬性 if (isDef(i = data.hook) && isDef(i = i.destroy)) { //若是有鉤子函數,或者銷燬的鉤子函數destroy 就調用destroy或者鉤子函數 i(vnode); } for (i = 0; i < cbs.destroy.length; ++i) { //而且判斷有幾個銷燬的鉤子函數,循環調用 cbs.destroy[i](vnode); // } } if (isDef(i = vnode.children)) { //若是有子節點則遞歸 for (j = 0; j < vnode.children.length; ++j) { invokeDestroyHook(vnode.children[j]); } } } function removeVnodes(parentElm, vnodes, startIdx, endIdx) { for (; startIdx <= endIdx; ++startIdx) { var ch = vnodes[startIdx]; if (isDef(ch)) { if (isDef(ch.tag)) { removeAndInvokeRemoveHook(ch); invokeDestroyHook(ch); } else { // Text node removeNode(ch.elm); } } } } function removeAndInvokeRemoveHook(vnode, rm) { if (isDef(rm) || isDef(vnode.data)) { var i; var listeners = cbs.remove.length + 1; if (isDef(rm)) { // we have a recursively passed down rm callback // increase the listeners count rm.listeners += listeners; } else { // directly removing rm = createRmCb(vnode.elm, listeners); } // recursively invoke hooks on child component root node if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) { removeAndInvokeRemoveHook(i, rm); } for (i = 0; i < cbs.remove.length; ++i) { cbs.remove[i](vnode, rm); } if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) { i(vnode, rm); } else { rm(); } } else { removeNode(vnode.elm); } } function updateChildren( parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly ) { var oldStartIdx = 0; var newStartIdx = 0; var oldEndIdx = oldCh.length - 1; var oldStartVnode = oldCh[0]; var oldEndVnode = oldCh[oldEndIdx]; var newEndIdx = newCh.length - 1; var newStartVnode = newCh[0]; var newEndVnode = newCh[newEndIdx]; var oldKeyToIdx, idxInOld, vnodeToMove, refElm; // removeOnly is a special flag used only by <transition-group> // to ensure removed elements stay in correct relative positions // during leaving transitions var canMove = !removeOnly; { checkDuplicateKeys(newCh); } while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left } else if (isUndef(oldEndVnode)) { oldEndVnode = oldCh[--oldEndIdx]; } else if (sameVnode(oldStartVnode, newStartVnode)) { patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue); oldStartVnode = oldCh[++oldStartIdx]; newStartVnode = newCh[++newStartIdx]; } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue); oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue); canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)); oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue); canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); oldEndVnode = oldCh[--oldEndIdx]; newStartVnode = newCh[++newStartIdx]; } else { if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); } idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx); if (isUndef(idxInOld)) { // New element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx); } else { vnodeToMove = oldCh[idxInOld]; if (sameVnode(vnodeToMove, newStartVnode)) { patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue); oldCh[idxInOld] = undefined; canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm); } else { // same key but different element. treat as new element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx); } } newStartVnode = newCh[++newStartIdx]; } } if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); } else if (newStartIdx > newEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); } } //檢測key是否有重複 function checkDuplicateKeys(children) { var seenKeys = {}; for (var i = 0; i < children.length; i++) { //循環子節點 var vnode = children[i]; //獲取子節點 var key = vnode.key; //獲取子節點的key if (isDef(key)) { //判斷key是否有定義過 if (seenKeys[key]) { //若是定義過則發出警告 warn( //檢測到重複鍵:「+ key +」。這可能會致使更新錯誤。 ("Duplicate keys detected: '" + key + "'. This may cause an update error."), vnode.context ); } else { //標誌key 狀態是 true seenKeys[key] = true; } } } } function findIdxInOld(node, oldCh, start, end) { for (var i = start; i < end; i++) { var c = oldCh[i]; if (isDef(c) && sameVnode(node, c)) { return i } } } function patchVnode( oldVnode, vnode, insertedVnodeQueue, removeOnly ) { if (oldVnode === vnode) { //若是他們相等 return } var elm = vnode.elm = oldVnode.elm; //獲取真實的dom if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue); } else { vnode.isAsyncPlaceholder = true; } return } // reuse element for static trees. // note we only do this if the vnode is cloned - // if the new node is not cloned it means the render functions have been // reset by the hot-reload-api and we need to do a proper re-render. if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance; return } var i; var data = vnode.data; if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode); } var oldCh = oldVnode.children; var ch = vnode.children; if (isDef(data) && isPatchable(vnode)) { for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); } if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); } } if (isUndef(vnode.text)) { if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); } } else if (isDef(ch)) { if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); } else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1); } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } } else if (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm, vnode.text); } if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); } } } function invokeInsertHook(vnode, queue, initial) { // delay insert hooks for component root nodes, invoke them after the // element is really inserted if (isTrue(initial) && isDef(vnode.parent)) { vnode.parent.data.pendingInsert = queue; } else { for (var i = 0; i < queue.length; ++i) { queue[i].data.hook.insert(queue[i]); } } } var hydrationBailed = false; // list of modules that can skip create hook during hydration because they // are already rendered on the client or has no need for initialization // Note: style is excluded because it relies on initial clone for future // deep updates (#7063). var isRenderedModule = makeMap('attrs,class,staticClass,staticStyle,key'); // Note: this is a browser-only function so we can assume elms are DOM nodes. function hydrate(elm, vnode, insertedVnodeQueue, inVPre) { var i; var tag = vnode.tag; var data = vnode.data; var children = vnode.children; inVPre = inVPre || (data && data.pre); vnode.elm = elm; if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) { vnode.isAsyncPlaceholder = true; return true } // assert node match { if (!assertNodeMatch(elm, vnode, inVPre)) { return false } } if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.init)) { i(vnode, true /* hydrating */); } if (isDef(i = vnode.componentInstance)) { // child component. it should have hydrated its own tree. // 初始化組建,若是沒有tag標籤則去更新真實dom的屬性,若是有tag標籤,則註冊或者刪除ref 而後爲insertedVnodeQueue.push(vnode);確保調用插入鉤子若是vnode.data.pendingInsert爲反正則也爲insertedVnodeQueue插入緩存 vnode.data.pendingInsert initComponent(vnode, insertedVnodeQueue); return true } } if (isDef(tag)) { if (isDef(children)) { // empty element, allow client to pick up and populate children if (!elm.hasChildNodes()) { createChildren(vnode, children, insertedVnodeQueue); } else { // v-html and domProps: innerHTML if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) { if (i !== elm.innerHTML) { /* istanbul ignore if */ if ("development" !== 'production' && typeof console !== 'undefined' && !hydrationBailed ) { hydrationBailed = true; console.warn('Parent: ', elm); console.warn('server innerHTML: ', i); console.warn('client innerHTML: ', elm.innerHTML); } return false } } else { // iterate and compare children lists var childrenMatch = true; var childNode = elm.firstChild; for (var i$1 = 0; i$1 < children.length; i$1++) { if (!childNode || !hydrate(childNode, children[i$1], insertedVnodeQueue, inVPre)) { childrenMatch = false; break } childNode = childNode.nextSibling; } // if childNode is not null, it means the actual childNodes list is // longer than the virtual children list. if (!childrenMatch || childNode) { /* istanbul ignore if */ if ("development" !== 'production' && typeof console !== 'undefined' && !hydrationBailed ) { hydrationBailed = true; console.warn('Parent: ', elm); console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children); } return false } } } } if (isDef(data)) { var fullInvoke = false; for (var key in data) { if (!isRenderedModule(key)) { fullInvoke = true; // invokeCreateHooks,循環cbs.create 鉤子函數,而且執行調用,其實cbs.create 鉤子函數就是platformModules中的attrs中 updateAttrs更新屬性函數。若是是組件則調用componentVNodeHooks中的 create invokeCreateHooks(vnode, insertedVnodeQueue); break } } if (!fullInvoke && data['class']) { // ensure collecting deps for deep class bindings for future updates traverse(data['class']); } } } else if (elm.data !== vnode.text) { elm.data = vnode.text; } return true } function assertNodeMatch(node, vnode, inVPre) { if (isDef(vnode.tag)) { return vnode.tag.indexOf('vue-component') === 0 || ( !isUnknownElement$$1(vnode, inVPre) && vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase()) ) } else { return node.nodeType === (vnode.isComment ? 8 : 3) } } // patch入口是這裏 // vm.$el, //真正的dom // vnode, //vnode /* __patch__( vm.$el, //真正的dom vnode, //vnode hydrating, // 空 false // removeOnly , vm.$options._parentElm, //父節點 空 vm.$options._refElm //當前節點 空 ); */ return function patch( oldVnode, //舊的vonde或者是真實的dom. 或者是沒有 vnode, //新的vode hydrating, removeOnly, //是否要所有刪除標誌 parentElm, //父節點 真實的dom refElm//當前節點 真實的dom ) { console.log('===oldVnode===') console.log(oldVnode) debugger; if (isUndef(vnode)) { //若是沒有定義新的vonde if (isDef(oldVnode)) { //若是沒有定義舊的vonde invokeDestroyHook(oldVnode); //若是vnode不存在可是oldVnode存在,說明意圖是要銷燬老節點,那麼就調用invokeDestroyHook(oldVnode)來進行銷燬 } return } var isInitialPatch = false; var insertedVnodeQueue = []; //vonde隊列 若是vnode上有insert鉤子,那麼就將這個vnode放入insertedVnodeQueue中做記錄,到時再在全局批量調用insert鉤子回調 if (isUndef(oldVnode)) { //若是沒有定義舊的vonde // empty mount (likely as component), create new root element 空掛載(可能做爲組件),建立新的根元素 isInitialPatch = true; createElm( //建立節點 vnode, //虛擬dom insertedVnodeQueue, //vonde隊列空數組 parentElm, //真實的 父節點 refElm //當前節點 ); } else { var isRealElement = isDef(oldVnode.nodeType); //獲取 真實的dom 類型 if (!isRealElement && //若是獲取不到真實的dom 類型 sameVnode(oldVnode, vnode) //sameVnode(oldVnode, vnode)2個節點的基本屬性相同,那麼就進入了2個節點的diff過程。 ) { // patch existing root node //修補現有根節點 patchVnode( oldVnode, vnode, insertedVnodeQueue, //vonde隊列 removeOnly //是否要所有刪除標誌 ); } else { if (isRealElement) { // mounting to a real element // check if this is server-rendered content and if we can perform // a successful hydration. if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { oldVnode.removeAttribute(SSR_ATTR); hydrating = true; } if (isTrue(hydrating)) { if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { invokeInsertHook(vnode, insertedVnodeQueue, true); return oldVnode } else { warn( 'The client-side rendered virtual DOM tree is not matching ' + 'server-rendered content. This is likely caused by incorrect ' + 'HTML markup, for example nesting block-level elements inside ' + '<p>, or missing <tbody>. Bailing hydration and performing ' + 'full client-side render.' ); } } // either not server-rendered, or hydration failed. // create an empty node and replace it oldVnode = emptyNodeAt(oldVnode); } // replacing existing element var oldElm = oldVnode.elm; var parentElm$1 = nodeOps.parentNode(oldElm); // create new node createElm( vnode, insertedVnodeQueue, // extremely rare edge case: do not insert if old element is in a // leaving transition. Only happens when combining transition + // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm$1, nodeOps.nextSibling(oldElm) ); // update parent placeholder node element, recursively if (isDef(vnode.parent)) { var ancestor = vnode.parent; var patchable = isPatchable(vnode); while (ancestor) { for (var i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor); } ancestor.elm = vnode.elm; if (patchable) { for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { cbs.create[i$1](emptyNode, ancestor); } // #6513 // invoke insert hooks that may have been merged by create hooks. // e.g. for directives that uses the "inserted" hook. var insert = ancestor.data.hook.insert; if (insert.merged) { // start at index 1 to avoid re-invoking component mounted hook for (var i$2 = 1; i$2 < insert.fns.length; i$2++) { insert.fns[i$2](); } } } else { registerRef(ancestor); } ancestor = ancestor.parent; } } // destroy old node if (isDef(parentElm$1)) { removeVnodes(parentElm$1, [oldVnode], 0, 0); } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode); } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); return vnode.elm } } //建立虛擬dom-end /* * vue 指令 * */ var directives = { create: updateDirectives, //建立指令 update: updateDirectives, //更新指令 destroy: function unbindDirectives(vnode) { //銷燬指令 updateDirectives(vnode, emptyNode); } } //更新數據 //oldVnode 老數據 //vnode 新數據 //更新指令 function updateDirectives(oldVnode, vnode) { //判斷舊的指令 或者如今指令存在麼 if (oldVnode.data.directives || vnode.data.directives) { _update(oldVnode, vnode); } } //更新指令 比較oldVnode和vnode,根據oldVnode和vnode的狀況 觸發指令鉤子函數bind,update,inserted,insert,componentUpdated,unbind鉤子函數 function _update(oldVnode, vnode) { var isCreate = oldVnode === emptyNode; // 判斷舊的指令是否等於一個空的指令 var isDestroy = vnode === emptyNode;// 判斷如今指令是否等於一個空的指令 //指令字符串 vm this上下文 console.log(oldVnode) console.log(vnode) //規範化的指令,爲指令屬性修正變成規範的指令數據。返回指令數據集合 var oldDirs = normalizeDirectives$1( oldVnode.data.directives, //vonde指令對象集合 oldVnode.context //vm vne實例化對象,或者是組件實例化的對象 ); //規範化的指令,爲指令屬性修正變成規範的指令數據。返回指令數據集合 var newDirs = normalizeDirectives$1( vnode.data.directives, //vonde指令對象集合 vnode.context //vm vne實例化對象,或者是組件實例化的對象 ); var dirsWithInsert = []; var dirsWithPostpatch = []; var key, oldDir, dir; for (key in newDirs) { //循環新的指令集合 console.log(newDirs[key]) oldDir = oldDirs[key]; //獲取舊的單個指令值 dir = newDirs[key];//獲取新的單個指令值 if (!oldDir) { //若是舊的不存在了 // new directive, bind 新指令,綁定 callHook$1( dir, //新的指令值 'bind', //觸發bind鉤子函數 vnode,//新的vonde oldVnode //舊的vonde ); if (dir.def && dir.def.inserted) { //獲取指令的屬性。 插入標記,指令 dirsWithInsert.push(dir); //記錄插入指令 } } else { // existing directive, update 現有的指令,更新 dir.oldValue = oldDir.value; // 若有指令 <div v-hello='123'></div> value=123. 若是更新了123 就是更新值 callHook$1(dir, 'update', //觸發更新鉤子函數 vnode, oldVnode ); if (dir.def && dir.def.componentUpdated) { // 組件更新 dirsWithPostpatch.push(dir); //記錄更新 } } } if (dirsWithInsert.length) { var callInsert = function () { for (var i = 0; i < dirsWithInsert.length; i++) { callHook$1( dirsWithInsert[i], //新的指令值 'inserted', //觸發inserted鉤子函數 vnode, //新的vonde oldVnode //舊的vonde ); } }; if (isCreate) { //是不是第一次建立的指令 mergeVNodeHook( vnode, 'insert',//合併鉤子函數 callInsert ); } else { callInsert(); } } if (dirsWithPostpatch.length) { mergeVNodeHook(vnode, 'postpatch', function () { for (var i = 0; i < dirsWithPostpatch.length; i++) { callHook$1( dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode); } }); } if (!isCreate) { for (key in oldDirs) { if (!newDirs[key]) { //新的vonde 中沒有了指令 // no longer present, unbind 再也不存在,解除束縛 callHook$1( oldDirs[key], 'unbind', //觸發unbind 鉤子 oldVnode, oldVnode, isDestroy ); } } } } var emptyModifiers = Object.create(null); //規範化的指令,爲指令屬性修正變成規範的指令數據。返回指令數據集合 function normalizeDirectives$1( dirs, //vonde 指令集合 vm //vm vne實例化對象,或者是組件實例化的對象 ) { //建立一個空的對象 var res = Object.create(null); //若是 指令 名稱dirs 不存在 則返回一個空的對象 if (!dirs) { // $flow-disable-line return res } var i, dir; for (i = 0; i < dirs.length; i++) { //循環遍歷指令集合 dir = dirs[i]; if (!dir.modifiers) { //判斷是否有修飾符 // $flow-disable-line dir.modifiers = emptyModifiers; //空對象 } //返回指令名稱 或者屬性name名稱+修飾符 res[getRawDirName(dir)] = dir; //指令屬性,該屬性由用戶自定義如 bind,inserted,update,componentUpdated,unbind這些 dir.def = resolveAsset(vm.$options, 'directives', dir.name, true); } // $flow-disable-line return res } //返回指令名稱 或者屬性name名稱+修飾符 function getRawDirName(dir) { //rawName 視圖中的 指令如 <div v-hello></div> 就是v-hello //name 視圖中的 指令如 <div v-hello></div> 就是hello //modifiers 修飾符 console.log(dir) return dir.rawName || ((dir.name) + "." + (Object.keys(dir.modifiers || {}).join('.'))) } //觸發指令鉤子函數 function callHook$1( dir, //新的指令值 hook, //鉤子函數 vnode, //新的vnode oldVnode, //舊的vnode isDestroy ) { var fn = dir.def && dir.def[hook]; //獲取屬性上面的鉤子函數 if (fn) { try { fn( vnode.elm, //真實dom dir, //指令的參數 vnode, //新的vond oldVnode, //舊的vonde isDestroy //是否要銷燬標記 ); } catch (e) { handleError(e, vnode.context, ("directive " + (dir.name) + " " + hook + " hook")); } } } var baseModules = [ ref, //ref建立,更新 , 銷燬 函數 directives //自定義指令 建立 ,更新,銷燬函數 ] /* * * 更新屬性,比較新的vnode和舊的oldVnode中的屬性值,若是不相等則設置屬性,就更新屬性值,若是新的vnode 屬性中沒有了則刪除該屬性 * * */ function updateAttrs(oldVnode, vnode) { debugger var opts = vnode.componentOptions; //獲取組件的拓展參數 if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) { // 判斷是否認義有拓展參數,而且須要Ctor.options.inheritAttrs 不等於 false的 時候才執行下面的代碼 return } //若是 oldVnode和vnode 沒有定義有attrs 屬性 也不會執行下面的代碼 if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) { return } var key, cur, old; var elm = vnode.elm; var oldAttrs = oldVnode.data.attrs || {}; //獲取舊的vonde attrs var attrs = vnode.data.attrs || {}; //獲取新的vonde attrs // clone observed objects, as the user probably wants to mutate it ////克隆觀察到的對象,由於用戶可能但願對其進行變異 if (isDef(attrs.__ob__)) { //判斷attrs.__ob__ 若是定義了 就執行 重新克隆一個 attrs = vnode.data.attrs = extend({}, attrs); } for (key in attrs) { //循環attrs cur = attrs[key]; //獲取到 attrs 值 old = oldAttrs[key]; //獲取到舊的 attrs 值 if (old !== cur) { //若是他們兩值不相等的時候就設置值 //設置屬性 setAttr(elm, key, cur); } } // #4391: in IE9, setting type can reset value for input[type=radio] 在IE9中,設置類型能夠重置輸入值[type=radio] // #6666: IE/Edge forces progress value down to 1 before setting a max 在設置最大值以前,IE/Edge會將進度值下降到1 /* istanbul ignore if */ if ((isIE || isEdge) && attrs.value !== oldAttrs.value) { //若是是ie瀏覽器,或者是edge瀏覽器 新的值和舊的值不相等的時候 setAttr(elm, 'value', attrs.value); //設置新的value值 } for (key in oldAttrs) { //遍歷循環舊的屬性 if (isUndef(attrs[key])) { // 若是新的屬性中 還含有舊的屬性key 而且有值的時候 if (isXlink(key)) { //判斷是不是xml elm.removeAttributeNS(xlinkNS, getXlinkProp(key)); //設置屬性 } else if (!isEnumeratedAttr(key)) { //若是不是 'contenteditable,draggable,spellcheck' 屬性 elm.removeAttribute(key); //設置屬性 } } } } //設置屬性 function setAttr(el, key, value) { //若是dom點 tag標籤 含有- 則是自定義標籤 if (el.tagName.indexOf('-') > -1) { //設置屬性 baseSetAttr(el, key, value); } else if (isBooleanAttr(key)) { //檢查是不是html中的布爾值屬性 就是該屬性只有 true 和 false // set attribute for blank value 爲空值設置屬性 // e.g. <option disabled>Select one</option> if (isFalsyAttrValue(value)) { //判斷val 是不是 null 或者 false el.removeAttribute(key); //刪除屬性 } else { // technically allowfullscreen is a boolean attribute for <iframe>, 從技術上講,allowfullscreen是一個布爾屬性 // but Flash expects a value of "true" when used on <embed> tag 可是Flash但願在<embed>標籤上使用時,其值爲"true" //判斷標籤是不是EMBED 若是是 true 若是不是則給標籤key就好了 value = key === 'allowfullscreen' && el.tagName === 'EMBED' ? 'true' : key; //設置屬性 el.setAttribute(key, value); } } else if (isEnumeratedAttr(key)) { //判斷是不是contenteditable,draggable,spellcheck 這三個屬性的其中一個 //設置屬性 el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'); } else if (isXlink(key)) { //判斷是不是xmlns 屬性 例子 <bookstore xmlns:xlink="http://www.w3.org/1999/xlink"> if (isFalsyAttrValue(value)) { //value 沒有值 //xml 則用個方法刪除屬性 el.removeAttributeNS(xlinkNS, getXlinkProp(key)); } else { //設置xml 屬性 el.setAttributeNS(xlinkNS, key, value); } } else { //設置基本屬性 baseSetAttr(el, key, value); } } // 設置基本的屬性 //設置屬性,而且判斷是不是ie瀏覽器 若是是 而且不是ie九的時候 更新input事件 function baseSetAttr(el, //dom節點 key, //屬性的 key value //屬性的值 ) { if (isFalsyAttrValue(value)) { // 判斷value 是不是 null 或者 false el.removeAttribute(key); //從dom中刪除屬性 } else { // #7138: IE10 & 11 fires input event when setting placeholder on IE10和11在設置佔位符時觸發輸入事件 // <textarea>... block the first input event and remove the blocker 阻塞第一個輸入事件並刪除該阻塞程序 // immediately. /* istanbul ignore if */ if ( isIE && //若是是is !isIE9 && //若是不是ie9 不支持ie9 el.tagName === 'TEXTAREA' && //若是標籤是TEXTAREA key === 'placeholder' && //若是key等於html5文本提示輸入的placeholder !el.__ieph //__ieph 等於假的 標誌位 ) { var blocker = function (e) { // 若是有多個相同類型事件的事件監聽函數綁定到同一個元素,當該類型的事件觸發時,它們會按照被添加的順序執行。若是其中某個監聽函數執行了 event.stopImmediatePropagation() 方法,則當前元素剩下的監聽函數將不會被執行。 // stopImmediatePropagation 則是阻止事件冒泡 e.stopImmediatePropagation(); //刪除input 事件 el.removeEventListener('input', blocker); }; //添加新的input 事件 el.addEventListener('input', blocker); // $flow-disable-line //標誌已經添加過 或者更新過input事件 el.__ieph = true; /* IE placeholder patched 佔位符打補丁 */ } //設置屬性 el.setAttribute(key, value); } } var attrs = { create: updateAttrs, //建立屬性 update: updateAttrs //更新屬性 } /* * 更新 真實dom的 calss * */ function updateClass(oldVnode, vnode) { var el = vnode.elm; //獲取dom節點 var data = vnode.data; //獲取新的 vnode數據 var oldData = oldVnode.data; //獲取舊的 oldVnode 數據 if ( isUndef(data.staticClass) && //若是沒有定義靜態的 staticClass isUndef(data.class) && //沒有定義calss ( isUndef(oldData) || //若是舊的oldData 沒有定義 ( isUndef(oldData.staticClass) && //舊的oldData staticClass class 沒有定義 isUndef(oldData.class) ) ) ) { //返回去 不執行下面的代碼 return } //class 轉碼獲取vonde 中的staticClass 靜態class 和class動態class轉義成真實dom須要的class格式。而後返回class字符串 var cls = genClassForVnode(vnode); // handle transition classes var transitionClass = el._transitionClasses; if (isDef(transitionClass)) { cls = concat(cls, stringifyClass(transitionClass)); } // set the class _prevClass 上一個css表示是否已經更新過 if (cls !== el._prevClass) { el.setAttribute('class', cls); el._prevClass = cls; } } var klass = { create: updateClass, update: updateClass } /* 匹配 ) 或 . 或 + 或 - 或 _ 或 $ 或 ] */ var validDivisionCharRE = /[\w).+\-_$\]]/; /*處理value 解析成正確的value,把過濾器 轉換成vue 虛擬dom的解析方法函數 好比把過濾器 ' ab | c | d' 轉換成 _f("d")(_f("c")(ab)) * 表達式中的過濾器解析 方法 * @param {*} exp */ console.log(parseFilters(' ab | c | d')) //轉化成 _f("d")(_f("c")(ab)) function parseFilters(exp) { // 是否在 ''中 var inSingle = false; // 是否在 "" 中 var inDouble = false; // 是否在 `` var inTemplateString = false; // 是否在 正則 \\ 中 var inRegex = false; // 是否在 {{ 中發現一個 culy加1 而後發現一個 } culy減1 直到culy爲0 說明 { .. }閉合 var curly = 0; // 跟{{ 同樣 有一個 [ 加1 有一個 ] 減1 var square = 0; // 跟{{ 同樣 有一個 ( 加1 有一個 ) 減1 var paren = 0; var lastFilterIndex = 0; var c, prev, i, expression, filters; for (i = 0; i < exp.length; i++) { prev = c; c = exp.charCodeAt(i); console.log('c =' + exp[i]) console.log('c === 0x7C=' + (c === 0x7C)) console.log('exp.charCodeAt(i + 1) !== 0x7C=' + (exp.charCodeAt(i + 1) !== 0x7C)) console.log('exp.charCodeAt(i - 1) !== 0x7C=' + (exp.charCodeAt(i - 1) !== 0x7C)) console.log('curly=' + (curly)) console.log('!curly=' + (!curly)) console.log('square=' + (square )) console.log('!square=' + (!square )) console.log('!paren=' + (!paren)) console.log('最後一個=' + ( c === 0x7C && // pipe exp.charCodeAt(i + 1) !== 0x7C && exp.charCodeAt(i - 1) !== 0x7C && !curly && !square && !paren)) if (inSingle) { // ' \ if (c === 0x27 && prev !== 0x5C) { inSingle = false; } } else if (inDouble) { // " \ if (c === 0x22 && prev !== 0x5C) { inDouble = false; } } else if (inTemplateString) { // ` if (c === 0x60 && prev !== 0x5C) { inTemplateString = false; } } else if (inRegex) { // 當前在正則表達式中 /開始 // / \ if (c === 0x2f && prev !== 0x5C) { inRegex = false; } } else if ( // 若是在 以前不在 ' " ` / 即字符串 或者正則中 // 那麼就判斷 當前字符是不是 | // 若是當前 字符爲 | // 且 不在 { } 對象中 // 且 不在 [] 數組中 // 且不在 () 中 // 那麼說明此時是過濾器的一個 分界點 c === 0x7C && // | exp.charCodeAt(i + 1) !== 0x7C && exp.charCodeAt(i - 1) !== 0x7C && !curly && !square && !paren ) { /* 若是前面沒有表達式那麼說明這是第一個 管道符號 "|" 再次遇到 | 由於前面 expression = 'message ' 執行 pushFilter() */ if (expression === undefined) { // first filter, end of expression // 過濾器表達式 就是管道符號以後開始 lastFilterIndex = i + 1; // 存儲過濾器的 表達式 expression = exp.slice(0, i).trim(); //這裏匹配若是字符串是 'ab|c' 則把ab匹配出來 console.log(expression) } else { pushFilter(); } } else { switch (c) { case 0x22: inDouble = true; break // 匹配" case 0x27: inSingle = true; break // 匹配' case 0x60: inTemplateString = true; break // 匹配` case 0x28: paren++; break // 匹配( case 0x29: paren--; break // 匹配) case 0x5B: square++; break // 匹配[ case 0x5D: square--; break // 匹配] case 0x7B: curly++; break // 匹配 { case 0x7D: curly--; break // 匹配 } case 0x5C: break // 匹配 \ case 0x2f: break; // 匹配 / case 0x7C: // 匹配 | break; } if (c === 0x2f) { // / var j = i - 1; var p = (void 0); // find first non-whitespace prev char //查找第一個非空白的prev字符 for (; j >= 0; j--) { p = exp.charAt(j); if (p !== ' ') { break } } if (!p || !validDivisionCharRE.test(p)) { inRegex = true; } } } } if (expression === undefined) { expression = exp.slice(0, i).trim(); } else if (lastFilterIndex !== 0) { pushFilter(); } // 獲取當前過濾器的 並將其存儲在filters 數組中 // filters = [ 'filterA' , 'filterB'] function pushFilter() { (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim()); lastFilterIndex = i + 1; } if (filters) { console.log(filters) for (i = 0; i < filters.length; i++) { //把過濾器封裝成函數 虛擬dom須要渲染的函數 expression = wrapFilter(expression, filters[i]); } } console.log(expression) //返回值 return expression } /* 生成過濾器的 表達式字符串 如上面的 exp = message filters = ['filterA','filterB(arg1,arg2)'] 第一步 以exp 爲入參 生成 filterA 的過濾器表達式字符串 _f("filterA")(message) 第二步 以第一步字符串做爲入參 生成第二個過濾器的表達式字符串 _f("filterB")(_f("filterA")(message),arg1,arg2) => _f("filterB")(_f("filterA")(message),arg1,arg2) * @param {string} exp 上一個過濾器的值 沒有就是 表達式的值 * @param {string} filter * @returns {string} */ console.log(wrapFilter('abc', 'defg(hijk)')) //結果 _f("defg")(abc,hijk) function wrapFilter(exp, filter) { var i = filter.indexOf('('); //返回字符串第一次出現索引的位置 console.log('i=' + i) if (i < 0) { // _f: resolveFilter return ("_f(\"" + filter + "\")(" + exp + ")") //閉包 } else { //name 是 從字符串開始到(結束的字符串,不包含( var name = filter.slice(0, i); //截取字符串 arrayObject.slice(start,end) console.log('==name==') console.log(name) //args是從(開始匹配,到字符串末端,不包含( var args = filter.slice(i + 1); //若是 end 未被規定,那麼 slice() 方法會選取從 start 到數組結尾的全部元素。 console.log('==args==') console.log(args) return ( "_f(\"" + name + "\")(" + exp + ( args !== ')' ? ',' + args : args ) ) } } /* */ function baseWarn(msg) { console.error(("[Vue compiler]: " + msg)); } //循環過濾數組或者對象的值,根據key循環 過濾對象或者數組[key]值,若是不存在則丟棄,若是有相同多個的key值,返回多個值的數組 function pluckModuleFunction( modules, //數組或者對象 key //key ) { return modules ? modules.map(function (m) { return m[key]; // 獲取modules[key] 值 }).filter(function (_) { return _; //過濾modules[key] 值,若是不存在則丟棄 }):[] } //在虛擬dom中添加prop屬性 function addProp(el, name, value) { (el.props || (el.props = [])).push({name: name, value: value}); el.plain = false; } //添加attrs屬性 function addAttr(el, name, value) { (el.attrs || (el.attrs = [])).push({name: name, value: value}); el.plain = false; } // add a raw attr (use this in preTransforms) //添加原始attr(在預轉換中使用) function addRawAttr(el, name, value) { el.attrsMap[name] = value; el.attrsList.push({name: name, value: value}); } //爲虛擬dom 添加一個 指令directives屬性 對象 function addDirective( el, //虛擬dom name, //獲取 view 原始屬性的名稱 不包含 v- : @的 rawName, // 獲取 view 原始屬性的名稱 包含 v- : @的 value, //屬性view 屬性上的值 arg, // efg:hig 屬性名稱冒號後面多出來的標籤 modifiers ) { (el.directives || (el.directives = [])).push({ name: name, rawName: rawName, value: value, arg: arg, modifiers: modifiers }); el.plain = false; } //爲虛擬dom添加events 事件對象屬性,若是添加@click='clickEvent' 則此時 虛擬dom爲el.events.click.value="clickEvent" //或者虛擬dom添加nativeEvents 事件對象屬性,若是添加@click.native='clickEvent' 則此時 虛擬dom爲el.nativeEvents.click.value="clickEvent" function addHandler( el, //虛擬dom name, //name 事件名稱 事件類型 value, //事件函數 modifiers, //事件類型狀態狀態 important, // 根據important爲true 把事件添加在前面 假就添加在尾部 warn //警告日誌 ) { modifiers = modifiers || emptyObject; // warn prevent and passive modifier /* istanbul ignore if */ if ( "development" !== 'production' && warn && modifiers.prevent && modifiers.passive ) { warn( 'passive and prevent can\'t be used together. ' + 'Passive handler can\'t prevent default event.' ); } // check capture modifier 檢查捕獲修飾符 if (modifiers.capture) { delete modifiers.capture; name = '!' + name; // mark the event as captured 將事件標記爲捕獲 } if (modifiers.once) { //將事件標記爲一次 delete modifiers.once; name = '~' + name; // mark the event as once 將事件標記爲一次 } /* istanbul ignore if */ if (modifiers.passive) { delete modifiers.passive; name = '&' + name; // mark the event as passive 將事件標記爲被動的 } // normalize click.right and click.middle since they don't actually fire // this is technically browser-specific, but at least for now browsers are // the only target envs that have right/middle clicks. //點擊正常化。並點擊。中間,由於它們實際上不會開火 //這在技術上是特定於瀏覽器的,但至少如今瀏覽器是 //惟一有右/中點擊的目標環境。 if (name === 'click') {//判斷是不是點擊事件 if (modifiers.right) { //判斷是不是鼠標右擊 name = 'contextmenu'; delete modifiers.right; } else if (modifiers.middle) {//若是是鼠標左擊 name = 'mouseup'; //變成鼠標擡起事件 } } var events; if (modifiers.native) { // 判斷是有原生事件修飾符 通俗點講:就是在父組件中給子組件綁定一個原生的事件,就將子組件變成了普通的HTML標籤,不加'. native'事件是沒法觸 發的。 /* * 好比<my-component @click="outClick"></my-component> 這樣是不會觸發事件的 * 須要加修飾符<my-component @click.native="outClick"></my-component> 這樣是不會觸發事件的 * */ delete modifiers.native; events = el.nativeEvents || (el.nativeEvents = {}); //獲取修飾符事件,若是虛擬dom沒有nativeEvents 這個屬性則爲他添加 } else { events = el.events || (el.events = {}); //直接獲取事件對象,若是虛擬dom沒有events屬性則爲他添加一個 } //此時下面操做events 就至關於操做 el.nativeEvents 或者 el.events 對象 var newHandler = { value: value.trim() //把事件函數 去除兩邊空格 }; if (modifiers !== emptyObject) { //若是 modifiers 不是一個空的對象 newHandler.modifiers = modifiers; } var handlers = events[name]; //獲取事件的值。 /* istanbul ignore if */ if (Array.isArray(handlers)) { //判斷事件是不是數組 //根據important 判斷在前面添加事件仍是在末端加 important ? handlers.unshift(newHandler) : handlers.push(newHandler); } else if (handlers) { //若是handlers 已經存在,可是不是數組,說明如今是有兩個事件 //將handlers 修改成數組,新的事件和舊的事件一塊兒 events[name] = important ? [newHandler, handlers] : [handlers, newHandler]; } else { //若是handlers 不存在 則直接獲取事件,說明該事件同名的只有一個, events[name] = newHandler; } el.plain = false; } // 獲取 :屬性 或者v-bind:屬性,或者獲取屬性 移除傳進來的屬性name,而且返回獲取到 屬性的值 function getBindingAttr(el, //虛擬dom vonde name, //name getStatic // ) { //獲取 :屬性 或者v-bind:屬性 var dynamicValue = getAndRemoveAttr(el, ':' + name) || getAndRemoveAttr(el, 'v-bind:' + name); console.log(el) console.log(dynamicValue) if (dynamicValue != null) { /* *處理value 解析成正確的value,把過濾器 轉換成vue 虛擬dom的解析方法函數 好比把過濾器 ' ab | c | d' 轉換成 _f("d")(_f("c")(ab)) * 表達式中的過濾器解析 方法 */ let parseFiltersValue = parseFilters(dynamicValue); console.log(parseFiltersValue) return parseFiltersValue } else if (getStatic !== false) { //移除傳進來的屬性name,而且返回獲取到 屬性的值 var staticValue = getAndRemoveAttr(el, name); if (staticValue != null) { //轉換成字符串 return JSON.stringify(staticValue) } } } // note: this only removes the attr from the Array (attrsList) so that it // doesn't get processed by processAttrs. // By default it does NOT remove it from the map (attrsMap) because the map is // needed during codegen. //注意:這只是從數組(attrsList)中移除attr //不會被processAttrs處理。 //默認狀況下,它不會從地圖(attrsMap)中刪除它,由於地圖是 //在codegen期間須要。 //移除傳進來的屬性name,而且返回獲取到 屬性的值 function getAndRemoveAttr(el, //el 虛擬dom name,//屬性名稱 須要刪除的屬性 name,獲取值的name屬性 removeFromMap //是否要刪除屬性的標誌 ) { var val; if ((val = el.attrsMap[name]) != null) { var list = el.attrsList; //按地址引用 for (var i = 0, l = list.length; i < l; i++) { if (list[i].name === name) { list.splice(i, 1); //按地址引用 刪除一個屬性name break } } } if (removeFromMap) { //是否要刪除屬性的標誌 delete el.attrsMap[name]; } return val } /* */ /** * Cross-platform code generation for component v-model * 組件v-model的跨平臺代碼生成 更新$$v 數據 * 爲虛擬dom添加model屬性, */ function genComponentModel( el, //虛擬dom value, //綁定v-model 的值 modifiers ) { var ref = modifiers || {}; var number = ref.number; //數字 var trim = ref.trim; //去除字符串 // 給baseValueExpression賦值一個默認的字符串 var baseValueExpression = '$$v'; var valueExpression = baseValueExpression; if (trim) { // 判斷類型是否爲字符串,若是是使用去空格方法,若是不是返回原值 valueExpression = "(typeof " + baseValueExpression + " === 'string'" + "? " + baseValueExpression + ".trim()" + ": " + baseValueExpression + ")"; } if (number) { //若是是數字 則用數字渲染方法 valueExpression = "_n(" + valueExpression + ")"; } /* *創賦值代碼,轉義字符串對象拆分字符串對象 把後一位key分離出來 * 返回 key"=" value * 或者 $set(object[info],key,valueExpression) */ var assignment = genAssignmentCode( value, //綁定v-model 的屬性值 valueExpression //值 ); console.log(value) console.log(valueExpression) console.log(baseValueExpression) console.log(assignment) //若是 trim不存在,number 不存在 則 valueExpression 默認爲$$v //回調函數是 $set(object[info],key,$$v) 更新$$v的值 el.model = { value: ("(" + value + ")"), // 綁定v-model 的值 expression: ("\"" + value + "\""), //綁定v-model 的值 //函數 $set(object[info],key,$$v) //$set更新值函數 callback: ("function (" + baseValueExpression + ") {" + assignment + "}") }; } /** * Cross-platform codegen helper for generating v-model value assignment code. * 用於生成v-model值賦值代碼的跨平臺codegen助手。 * 創賦值代碼,轉義字符串對象拆分字符串對象 把後一位key分離出來 * * 返回 key"=" value * 或者 $set(object[info],key,value) */ function genAssignmentCode( value, //key assignment //值 ) { //轉義字符串對象拆分字符串對象 把後一位key分離出來 // 兩種狀況分析1 若是數據是object.info.name的狀況下 則返回是 {exp: "object.info",key: "name"} //若是數據是object[info][name]的狀況下 則返回是 {exp: "object[info]",key: "name"} var res = parseModel(value); if (res.key === null) { return (value + "=" + assignment) // 沒有key就是當前值,返回當前值的key } else { return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")") // 返回更新值 '$set(object[info],key,value)' } } /** * Parse a v-model expression into a base path and a final key segment. * Handles both dot-path and possible square brackets. * 將v-model表達式解析爲基路徑和最後一個鍵段。 *處理點路徑和可能的方括號。 * * Possible cases: * 可能的狀況下: * * - test * - test[key] * - test[test1[key]] * - test["a"][key] * - xxx.test[a[a].test1[key]] * - test.xxx.a["asa"][test1[key]] * */ var len; //字符串長度 var str; //字符串 var chr; //字符串的編碼 var index$1; //循環的索引 var expressionPos; //匹配到 符號 [ 的開始索引 var expressionEndPos; // 若是匹配上一對 [] 的時候就跳出循環 則是匹配 console.log(parseModel('object')) console.log(parseModel('object[info][name]')) console.log(parseModel('object.info.name')) console.log(parseModel('test[key]')) console.log(parseModel('test[test1[key]]')) console.log(parseModel('test["a"][key]')) console.log(parseModel('xxx.test[a[a].test1[key]]')) console.log(parseModel('test.xxx.a["asa"][test1[key]]')) //轉義字符串對象拆分字符串對象 把後一位key分離出來 // 兩種狀況分析1 若是數據是object.info.name的狀況下 則返回是 {exp: "object.info",key: "name"} //若是數據是object[info][name]的狀況下 則返回是 {exp: "object[info]",key: "name"} function parseModel(val) { // Fix https://github.com/vuejs/vue/pull/7730 // allow v-model="obj.val " (trailing whitespace) val = val.trim(); //值 len = val.length; //獲取長度 //lastIndexOf 方法可返回一個指定的字符串值最後出現的位置 if ( val.indexOf('[') < 0 || //這個字符串沒有出現過[ val.lastIndexOf(']') < len - 1 //這個字符串 沒有出現過]這個符號 或者是出現位置不是在最後一位的時候 ) { index$1 = val.lastIndexOf('.'); //獲取最後一位出現 . 的位置 if (index$1 > -1) { //說明有點. return { exp: val.slice(0, index$1), //丟棄最後一位 好比data.object.info.age獲取data.object.info key: '"' + val.slice(index$1 + 1) + '"' //獲取最後一位 age } } else { return { exp: val, //若是沒有點 則只有一個值 key: null } } } str = val; index$1 = expressionPos = expressionEndPos = 0; // 索引和字符串長度比較 若是索引大於或者等於字符串的時候返回真 while (!eof()) { //循環獲取字符串的編碼 直到把字符編碼循環完 //獲取字符串的編碼 chr = next(); /* istanbul ignore if */ if (isStringStart(chr)) { //若是是 " 或者 ' 的時候返回真 parseString(chr); //循環匹配一對''或者""符號 } else if (chr === 0x5B) { // 符號 [ //檢測 匹配[] 一對這樣的=括號 parseBracket(chr); } } return { exp: val.slice(0, expressionPos), key: val.slice(expressionPos + 1, expressionEndPos) } } //索引加加 獲取字符串的編碼 function next() { //charCodeAt() 方法可返回指定位置的字符的 Unicode 編碼。這個返回值是 0 - 65535 之間的整數。 return str.charCodeAt(++index$1) } // 索引和字符串長度比較 若是索引大於或者等於字符串的時候返回真 function eof() { //索引和字符串長度比較 return index$1 >= len } //若是是 " 或者 ' 的時候返回真 function isStringStart(chr) { // " ' return chr === 0x22 || chr === 0x27 } //檢測 匹配[] 一對這樣的=括號 function parseBracket(chr) { var inBracket = 1; expressionPos = index$1; while (!eof()) { chr = next(); if (isStringStart(chr)) { //若是是 " 或者 ' 的時候返回真 parseString(chr); //循環匹配一對''或者""符號 continue } if (chr === 0x5B) { // 匹配上 inBracket++; } if (chr === 0x5D) { //匹配上 ] inBracket--; } if (inBracket === 0) { //若是匹配上一對 [] 的時候就跳出循環 expressionEndPos = index$1; break } } } //循環匹配一對''或者""符號 function parseString(chr) { var stringQuote = chr; //記錄當前的'或者" while (!eof()) { chr = next(); if (chr === stringQuote) { //當他們匹配上一對的時候退出循環 break } } } /* */ var warn$1; // in some cases, the event used has to be determined at runtime // so we used some reserved tokens during compile. //在某些狀況下,使用的事件必須在運行時肯定 //所以咱們在編譯期間使用了一些保留的令牌。 var RANGE_TOKEN = '__r'; //虛擬dom渲染函數 var CHECKBOX_RADIO_TOKEN = '__c'; //根據判斷虛擬dom的標籤類型是什麼?給相應的標籤綁定 相應的 v-model 雙數據綁定代碼函數 function model( el, //虛擬dom dir, // v-model 屬性的key和值 _warn //警告日誌函數 ) { console.log(el) console.log(dir) // {name: "model" // rawName: "v-model" // value: "item.url"} warn$1 = _warn; var value = dir.value; // var modifiers = dir.modifiers; var tag = el.tag; var type = el.attrsMap.type; { // inputs with type="file" are read only and setting the input's // value will throw an error. if (tag === 'input' && type === 'file') { warn$1( "<" + (el.tag) + " v-model=\"" + value + "\" type=\"file\">:\n" + "File inputs are read only. Use a v-on:change listener instead." ); } } //根據表單元素的tag標籤以及type屬性的值,調用不一樣的方法也就驗證了官網所說的「隨表單控件類型不一樣而不一樣。」這裏調用的就是genDefaultModel(). if (el.component) { //若是是組件 // 組件v-model的跨平臺代碼生成 更新$$v 數據 // * 爲虛擬dom添加model屬性, genComponentModel(el, value, modifiers); // 組件v-model不須要額外的運行時 // component v-model doesn't need extra runtime return false } else if (tag === 'select') { //爲虛擬dom select添加change 函數 ,change函數調用 set 去更新 select選中數據的值 genSelect(el, value, modifiers); } else if (tag === 'input' && type === 'checkbox') { //爲input type="checkbox" 虛擬dom添加 change 函數 ,根據v-model是不是數組,調用change函數,調用 set 去更新 checked選中數據的值 genCheckboxModel(el, value, modifiers); } else if (tag === 'input' && type === 'radio') { //爲虛擬dom inpu標籤 type === 'radio' 添加change 事件 更新值 genRadioModel(el, value, modifiers); } else if (tag === 'input' || tag === 'textarea') { //爲虛擬dom inpu標籤 事件 更新值 genDefaultModel(el, value, modifiers); } else if (!config.isReservedTag(tag)) { //保留標籤 判斷是否是真的是 html 原有的標籤 或者svg標籤 若是不是則表示是組件 標籤 // 組件v-model的跨平臺代碼生成 更新$$v 數據 // * 爲虛擬dom添加model屬性, genComponentModel(el, value, modifiers); // component v-model doesn't need extra runtime return false } else { warn$1( "<" + (el.tag) + " v-model=\"" + value + "\">: " + "v-model is not supported on this element type. " + 'If you are working with contenteditable, it\'s recommended to ' + 'wrap a library dedicated for that purpose inside a custom component.' ); } // ensure runtime directive metadata return true } //爲input type="checkbox" 虛擬dom添加 change 函數 ,根據v-model是不是數組,調用change函數,調用 set 去更新 checked選中數據的值 function genCheckboxModel( el, //虛擬dom value, //v-model view的屬性值 modifiers ) { console.log(el) var number = modifiers && modifiers.number; var valueBinding = getBindingAttr(el, 'value') || 'null'; //獲取 表單的 value屬性值 若是 view 是 value="1" var trueValueBinding = getBindingAttr(el, 'true-value') || 'true'; var falseValueBinding = getBindingAttr(el, 'false-value') || 'false'; /* view 綁定的 v-model="item.selected" 第二個參數爲 * Array.isArray(item.selected)?_i(item.selected,"index")>-1:(item.selected) * */ console.log( "Array.isArray(" + value + ")" + "?_i(" + value + "," + valueBinding + ")>-1" + ( trueValueBinding === 'true'? (":(" + value + ")") : (":_q(" + value + "," + trueValueBinding + ")") )) //在虛擬dom中添加prop屬性 addProp(el, 'checked', "Array.isArray(" + value + ")" + "?_i(" + value + "," + valueBinding + ")>-1" + ( trueValueBinding === 'true'? (":(" + value + ")") : (":_q(" + value + "," + trueValueBinding + ")") ) ); console.log("var $$a=" + value + "," + '$$el=$event.target,' + "$$c=$$el.checked?(" + trueValueBinding + "):(" + falseValueBinding + ");" + 'if(Array.isArray($$a)){' + "var $$v=" + (number ? '_n(' + valueBinding + ')' : valueBinding) + "," + '$$i=_i($$a,$$v);' + "if($$el.checked){$$i<0&&(" + (genAssignmentCode(value, '$$a.concat([$$v])')) + ")}" + "else{$$i>-1&&(" + (genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')) + ")}" + "}else{" + (genAssignmentCode(value, '$$c')) + "}") /* view 綁定的 v-model="item.selected" 第二個參數爲 var $$a = item.selected, //屬性值 v-model view的屬性值 item.selected是不是數組 $$el = $event.target, //目標dom 真實dom $$c = $$el.checked ? (true) : (false); //是否有選中 if (Array.isArray($$a)) { var $$v = "1", //獲取 表單的 value屬性值 若是 view 是 value="1" $$i = _i($$a, $$v); //獲取到數組的索引,若是沒有匹配上則是新的數據 if ($$el.checked) { //更新數組的值 $$i < 0 && ($set(item, "selected", $$a.concat([$$v]))) } else { //截取數組 更新獲取到索引的數組 從匹配到到最後一位 $$i > -1 && ($set(item, "selected", $$a.slice(0, $$i).concat($$a.slice($$i + 1)))) } } else { $set(item, "selected", $$c) } * */ //更新函數綁定change事件 addHandler( el, //虛擬dom 'change', //事件 "var $$a=" + value + "," + '$$el=$event.target,' + "$$c=$$el.checked?(" + trueValueBinding + "):(" + falseValueBinding + ");" + 'if(Array.isArray($$a)){' + "var $$v=" + (number ? '_n(' + valueBinding + ')' : valueBinding) + "," + '$$i=_i($$a,$$v);' + "if($$el.checked){$$i<0&&(" + (genAssignmentCode(value, '$$a.concat([$$v])')) + ")}" + "else{$$i>-1&&(" + (genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')) + ")}" + "}else{" + (genAssignmentCode(value, '$$c')) + "}", null, true ); } //爲虛擬dom inpu標籤 type === 'radio' 添加change 事件 更新值 function genRadioModel( el, //虛擬dom value, //v-model 在view中的屬性值 modifiers ) { var number = modifiers && modifiers.number; //是不是數字 var valueBinding = getBindingAttr(el, 'value') || 'null'; //獲取虛擬dom view標籤value屬性值 //若是是數字 則調用_n() 轉義 valueBinding = number ? ("_n(" + valueBinding + ")") : valueBinding; addProp( el, 'checked', ("_q(" + value + "," + valueBinding + ")") ); //添加事件 addHandler( el, //虛擬dom 'change', //change事件 // 返回 key"=" valueBinding // * 或者 $set(object[info],key,valueBinding) genAssignmentCode(value, valueBinding), //事件函數 null,// modifiers, //事件類型狀態狀態 true// 根據important爲true 把事件添加在前面 假就添加在尾部 ); } //爲虛擬dom添加change 函數 ,change函數調用 set 去更新 select選中數據的值 function genSelect( el, //虛擬dom value, //v-model屬性值 modifiers ) { var number = modifiers && modifiers.number; var selectedVal = "Array.prototype.filter" + ".call($event.target.options," + "function(o){" + " return o.selected" + "})" + ".map(function(o){" + "var val = \"_value\" in o ? o._value : o.value;" + "return " + (number ? '_n(val)' : 'val') + "" + "})"; console.log(selectedVal) var assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'; var code = "var $$selectedVal = " + selectedVal + ";"; // * 返回 key"=" $$selectedVal // * 或者 $set(object[info],key,$$selectedVal) code = code + " " + ( genAssignmentCode( value, //v-model屬性值 assignment // $$selectedVal是select選中數據的值 ) ); //這裏字符串js意思是。先執行Array.prototype.filter 獲取到值以後 在調用 $set(object[info],key,value) 更新數據 //在把這個事件添加到change事件中 addHandler( el, //虛擬dom 'change', //name 事件名稱 事件類型 code, //事件函數 null, //事件類型狀態 true // 根據important爲true 把事件添加在前面 假就添加在尾部 ); } // 若是虛擬dom標籤是 'input' 類型不是checkbox,radio 或者是'textarea' 標籤的時候,獲取真實的dom的value值調用 change或者input方法執行set方法更新數據 function genDefaultModel( el, //虛擬dom value, //屬性在view 的值 modifiers //標籤類型對象 修飾符 ) { console.log(el) console.log(value) var type = el.attrsMap.type; //獲取類型 // warn if v-bind:value conflicts with v-model 警告若是v-bind:值與v-model衝突 // except for inputs with v-bind:type 除了輸入v-bind:type { var value$1 = el.attrsMap['v-bind:value'] || el.attrsMap[':value']; var typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']; if (value$1 && !typeBinding) { //若是type屬性沒有則發出警告 var binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'; warn$1( binding + "=\"" + value$1 + "\" conflicts with v-model on the same element " + 'because the latter already expands to a value binding internally' ); } } var ref = modifiers || {}; var lazy = ref.lazy; //只有在焦點不集中時,才應該更新帶有lazy的輸入 失去焦點 var number = ref.number; //數字 var trim = ref.trim; //去除兩邊空格 var needCompositionGuard = !lazy && type !== 'range'; //若是不是滑動類型input var event = lazy ? //獲取類型事件 能夠是change或者是input 事件 'change' : type === 'range' ? //判斷是不是滑動塊 RANGE_TOKEN //'__r'虛擬dom渲染函數 : 'input'; var valueExpression = '$event.target.value'; if (trim) { valueExpression = "$event.target.value.trim()"; //獲取真實dom的value } if (number) { valueExpression = "_n(" + valueExpression + ")"; } //更新值 // * 返回 key"=" value // * 或者 $set(object[info],key,value) var code = genAssignmentCode( value, //v-model 的屬性值 valueExpression //真實dom的value ); if (needCompositionGuard) { //若是不是滑動塊 code = "if($event.target.composing)return;" + code; } //添加props 屬性 addProp(el, 'value', ("(" + value + ")")); //添加綁定事件 addHandler( el, //虛擬dom event, //事件類型 code, //事件函數 null, //事件類型狀態狀態 修飾符 true // 根據important爲true 把事件添加在前面 假就添加在尾部 ); if (trim || number) { addHandler(el, 'blur', '$forceUpdate()'); } } /* */ // normalize v-model event tokens that can only be determined at runtime. // it's important to place the event as the first in the array because // the whole point is ensuring the v-model callback gets called before // user-attached handlers. //規範化只能在運行時肯定的v-model事件令牌。 //將事件放在數組的第一個位置很重要,由於 //關鍵是確保v-model回調函數在以前被調用 //user-attached處理程序。 //爲事件 多添加 change 或者input 事件加進去 function normalizeEvents(on) { /* istanbul ignore if */ if (isDef(on[RANGE_TOKEN])) { // IE input[type=range] only supports `change` event // var event = isIE ? 'change' : 'input'; //判斷是不是ie 瀏覽器,若是是則選擇 change 事件,若是不是則選擇input事件 on[event] = [].concat(on[RANGE_TOKEN], on[event] || []); //鏈接事件 把change或者input 事件添加進去 delete on[RANGE_TOKEN]; //刪除舊的事件 } // This was originally intended to fix #4521 but no longer necessary // after 2.5. Keeping it for backwards compat with generated code from < 2.4 /* istanbul ignore if */ //最初的目的是修復#4521,但如今已經沒有必要了 // 2.5以後。保留它以便與< 2.4生成的代碼進行反向比較 //添加change事件 if (isDef(on[CHECKBOX_RADIO_TOKEN])) { on.change = [].concat(on[CHECKBOX_RADIO_TOKEN], on.change || []); delete on[CHECKBOX_RADIO_TOKEN]; } } var target$1; //柯理化函數,返回一個直接調用函數的方法,調用完就刪除事件 function createOnceHandler( handler,//轉義過的事件 event, //事件名稱 capture // 事件俘獲或是冒泡行爲 ) { var _target = target$1; // save current target element in closure return function onceHandler() { var res = handler.apply(null, arguments); if (res !== null) { remove$2( event, //事件名稱 onceHandler, //綁定的事件 capture, //事件俘獲或是冒泡行爲 _target //真實的dom ); } } } //withMacroTask,爲事件添加一個靜態屬性_withTask爲紅任務,則是執行fn的。 // 判斷once$$1是否存在,若是存在則調用createOnceHandler 返回一個直接調用函數的方法,調用完就刪除事件 // 爲真實的dom添加事件 function add$1( event, //事件名稱 handler, // 轉義過的事件 執行事件靜態類 once$$1, //是否只觸發一次的狀態 capture, // 事件俘獲或是冒泡行爲 passive // 檢測事件修飾符 是不是 '&' ) { //withMacroTask,爲事件添加一個靜態屬性_withTask爲紅任務,則是執行fn的。 handler = withMacroTask(handler); if (once$$1) { //建立一次處理程序 //柯理化函數,返回一個直接調用函數的方法,調用完就刪除事件 handler = createOnceHandler( handler,//轉義過的事件 event, //事件名稱 capture //事件俘獲或是冒泡行爲 ); } //爲真實的dom添加事件 target$1.addEventListener( event, //事件名稱 handler, //事件函數 supportsPassive ? {capture: capture,passive: passive}: capture //事件是俘獲仍是冒泡 ); } //刪除真實dom的事件 function remove$2( event,//事件名稱 handler, //轉義過的事件 dom綁定的事件 capture, //事件俘獲或是冒泡行爲 _target //真實的dom ) { (_target || target$1).removeEventListener( event, handler._withTask || handler, capture //事件俘獲或是冒泡行爲 ); } //更新dom事件 function updateDOMListeners(oldVnode, vnode) { //判斷是否認義了事件on 若是他們兩不定義有則不執行下面代碼 if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) { return } var on = vnode.data.on || {}; var oldOn = oldVnode.data.on || {}; target$1 = vnode.elm; //真實的dom normalizeEvents(on); //爲事件 多添加 change 或者input 事件加進去 //更新數據源 而且爲新的值 添加函數 舊的值刪除函數等功能 updateListeners( on, //新的事件對象 oldOn, //舊的事件對象 add$1, //添加真實dom的事件函數 remove$2, //刪除真實dom的事件函數 vnode.context //vue 實例化的對象 new Vue 或者組件 構造函數實例化的對象 ); target$1 = undefined; } var events = { create: updateDOMListeners, update: updateDOMListeners } /* * 更新真實dom的props屬性 * */ function updateDOMProps(oldVnode, vnode) { if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) { return } var key, cur; var elm = vnode.elm; var oldProps = oldVnode.data.domProps || {}; //獲取舊的props屬性 var props = vnode.data.domProps || {}; //獲取新的props // clone observed objects, as the user probably wants to mutate it //克隆觀察到的對象,由於用戶可能但願對其進行修改 if (isDef(props.__ob__)) { //若是是props添加了觀察者,從新克隆他,這樣就能夠修改了 props = vnode.data.domProps = extend({}, props); } consolelog(props) consolelog(oldProps) for (key in oldProps) { //循環舊的props屬性,若是沒有定義了 就給空 if (isUndef(props[key])) { elm[key] = ''; } } for (key in props) { //循環新的props屬性 cur = props[key]; //獲取props 的值 // ignore children if the node has textContent or innerHTML, // as these will throw away existing DOM nodes and cause removal errors // on subsequent patches (#3360) //忽略子節點,若是節點有textContent或innerHTML, //由於這將丟棄現有的DOM節點並致使刪除錯誤 //其後的修補程式(#3360) if ( key === 'textContent' || key === 'innerHTML' ) { if (vnode.children) { vnode.children.length = 0; } if (cur === oldProps[key]) { continue } // #6601 work around Chrome version <= 55 bug where single textNode // replaced by innerHTML/textContent retains its parentNode property // #6601解決Chrome版本<= 55的bug,其中只有一個textNode //被innerHTML/textContent替換後,保留了它的parentNode屬性 if (elm.childNodes.length === 1) { //文本節點 elm.removeChild(elm.childNodes[0]); } } if (key === 'value') { // store value as _value as well since // non-string values will be stringified //將value存儲爲_value以及since //非字符串值將被字符串化 elm._value = cur; // avoid resetting cursor position when value is the same // 當值相同時,避免重置光標位置 var strCur = isUndef(cur) ? '' : String(cur); //轉義成字符串 if (shouldUpdateValue( elm, //真實的dom strCur //value )) { elm.value = strCur; //賦值 } } else { elm[key] = cur; //直接賦值 } } } // check platforms/web/util/attrs.js acceptValue // 判斷你是否更新value function shouldUpdateValue(elm, checkVal) { return (!elm.composing && ( elm.tagName === 'OPTION' || isNotInFocusAndDirty(elm, checkVal) || isDirtyWithModifiers(elm, checkVal) )) } function isNotInFocusAndDirty(elm, checkVal) { // return true when textbox (.number and .trim) loses focus and its value is // not equal to the updated value var notInFocus = true; // #6157 // work around IE bug when accessing document.activeElement in an iframe try { notInFocus = document.activeElement !== elm; } catch (e) { } return notInFocus && elm.value !== checkVal } function isDirtyWithModifiers(elm, newVal) { var value = elm.value; var modifiers = elm._vModifiers; // injected by v-model runtime if (isDef(modifiers)) { if (modifiers.lazy) { // inputs with lazy should only be updated when not in focus return false } if (modifiers.number) { return toNumber(value) !== toNumber(newVal) } if (modifiers.trim) { return value.trim() !== newVal.trim() } } return value !== newVal } var domProps = { create: updateDOMProps, //更新真實dom的props 屬性值 update: updateDOMProps//更新真實dom的props 屬性值 } /* */ //把style 字符串 轉換成對象 好比'width:100px;height:200px;' 轉化成 {width:100px,height:200px} var parseStyleText = cached(function (cssText) { var res = {}; var listDelimiter = /;(?![^(]*\))/g; //匹配字符串中的 ;符號。可是不屬於 (;)的 符號 若是是括號中的;不能匹配出來 var propertyDelimiter = /:(.+)/; //:+任何字符串 console.log(cssText.split(listDelimiter)) cssText.split(listDelimiter).forEach(function (item) { if (item) { var tmp = item.split(propertyDelimiter); console.log(tmp) tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim()); } }); return res }); console.log(parseStyleText('width:100px;(height:200px);')) // merge static and dynamic style data on the same vnode //在同一個vnode上合併靜態和動態樣式數據 function normalizeStyleData(data) { // //將可能的數組/字符串值規範化爲對象 把style 字符串 轉換成對象 好比'width:100px;height:200px;' 轉化成 {width:100px,height:200px} 返回該字符串。 var style = normalizeStyleBinding(data.style); //獲取到vonde中的style屬性值 // static style is pre-processed into an object during compilation // and is always a fresh object, so it's safe to merge into it //靜態樣式在編譯期間被預處理爲對象 //始終是一個新鮮的對象,因此能夠安全地融入其中 return data.staticStyle ? extend(data.staticStyle, style) : //合併靜態 style } // normalize possible array / string values into Object //將可能的數組/字符串值規範化爲對象 function normalizeStyleBinding(bindingStyle) { if (Array.isArray(bindingStyle)) { return toObject(bindingStyle) } if (typeof bindingStyle === 'string') { //把style 字符串 轉換成對象 好比'width:100px;height:200px;' 轉化成 {width:100px,height:200px} return parseStyleText(bindingStyle) } return bindingStyle } /** * parent component style should be after child's * so that parent component's style could override it * 父組件樣式應該在子組件樣式以後 * 這樣父組件的樣式就能夠覆蓋它 * 循環子組件和組件的樣式,把它所有合併到一個樣式對象中返回 樣式對象 如{width:100px,height:200px} 返回該字符串。 */ function getStyle( vnode, //虛擬dom checkChild //標誌點 布爾值 ) { var res = {}; var styleData; //style data if (checkChild) { // 標誌點 布爾值 var childNode = vnode; //獲取子節點 while (childNode.componentInstance) { //已經實例化過的 就是子節點有vonde childNode = childNode.componentInstance._vnode; if ( childNode && childNode.data && (styleData = normalizeStyleData(childNode.data)) ) { extend(res, styleData); } } } if ((styleData = normalizeStyleData(vnode.data))) { extend(res, styleData); } var parentNode = vnode; while ((parentNode = parentNode.parent)) { if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) { extend(res, styleData); } } return res } /* */ var cssVarRE = /^--/; //開始以 --開始 var importantRE = /\s*!important$/; //以!important 結束 var setProp = function (el, name, val) { //object.setProperty(propertyname, value, priority) // 參數 描述 // propertyname 必需。一個字符串,表示建立或修改的屬性。 // value 可選,新的屬性值。 // priority 可選。字符串,規定是否須要設置屬性的優先級 important。 // 能夠是下面三個值:"important",undefined,"" /* istanbul ignore if */ if (cssVarRE.test(name)) { //開始以 --開始 el.style.setProperty(name, val); //設置真實dom樣式 } else if (importantRE.test(val)) { //以!important 結束 el.style.setProperty( name, val.replace(importantRE, ''), 'important' ); } else { //給css加前綴 var normalizedName = normalize(name); if (Array.isArray(val)) { // Support values array created by autoprefixer, e.g. // {display: ["-webkit-box", "-ms-flexbox", "flex"]} // Set them one by one, and the browser will only set those it can recognize //支持自動修復程序建立的值數組。 //{顯示:[「-webkit-box」、「-ms-flexbox」,「柔化」)} //一個一個設置,瀏覽器只會設置它能識別的 for (var i = 0, len = val.length; i < len; i++) { el.style[normalizedName] = val[i]; //循環一個個設置樣式 } } else { el.style[normalizedName] = val; } } }; var vendorNames = ['Webkit', 'Moz', 'ms']; var emptyStyle; //給css加前綴。解決瀏覽器兼用性問題,加前綴 var normalize = cached(function (prop) { emptyStyle = emptyStyle || document.createElement('div').style; //獲取瀏覽器中的style樣式 prop = camelize(prop); if (prop !== 'filter' && (prop in emptyStyle)) { //若是該屬性已經在樣式中 return prop } var capName = prop.charAt(0).toUpperCase() + prop.slice(1); //首字母變成大寫 for (var i = 0; i < vendorNames.length; i++) { var name = vendorNames[i] + capName; //加前綴 if (name in emptyStyle) { return name } } }); // 將vonde虛擬dom的css 轉義成而且渲染到真實dom的csszhong function updateStyle(oldVnode, vnode) { var data = vnode.data; //獲取新虛擬dom的標籤屬性 var oldData = oldVnode.data; //獲取舊虛擬dom的標籤屬性 if (isUndef(data.staticStyle) && isUndef(data.style) && isUndef(oldData.staticStyle) && isUndef(oldData.style) ) { return } var cur, name; var el = vnode.elm; //獲取真實的dom var oldStaticStyle = oldData.staticStyle; //獲取舊的靜態 staticStyle var oldStyleBinding = oldData.normalizedStyle || oldData.style || {}; //獲取舊的動態style // if static style exists, stylebinding already merged into it when doing normalizeStyleData // 若是存在靜態樣式,則在執行normalizeStyleData時,stylebinding已經合併到其中 var oldStyle = oldStaticStyle || oldStyleBinding; //舊的style樣式 //將可能的數組/字符串值規範化爲對象 //把style 字符串 轉換成對象 好比'width:100px;height:200px;' 轉化成 {width:100px,height:200px} var style = normalizeStyleBinding(vnode.data.style) || {}; // store normalized style under a different key for next diff // make sure to clone it if it's reactive, since the user likely wants // to mutate it. //爲下一個diff在不一樣的鍵下存儲規範化樣式 //若是它是反應性的,請確保克隆它,由於用戶可能但願這樣作 //使之變異 vnode.data.normalizedStyle = isDef(style.__ob__) ? //若是style 加入了觀察者以後 extend({}, style): //從新克隆,能夠修改 style; //直接賦值 //getStyle循環子組件和組件的樣式,把它所有合併到一個樣式對象中返回 樣式對象 如{width:100px,height:200px} 返回該字符串。 var newStyle = getStyle( vnode, true ); for (name in oldStyle) { //獲取舊虛擬dom的樣式 if (isUndef(newStyle[name])) { // 若是新的虛擬dom vonde沒有了 setProp(el, name, ''); //則設置樣式爲空 } } for (name in newStyle) { //循環新的虛擬dom vonde 樣式 cur = newStyle[name]; if (cur !== oldStyle[name]) { //若是舊的和新的不一樣了 就設置新的樣式 // ie9 setting to null has no effect, must use empty string setProp(el, name, cur == null ? '' : cur); } } } var style = { create: updateStyle, update: updateStyle } /* */ /** * Add class with compatibility for SVG since classList is not supported on * SVG elements in IE * *添加與SVG兼容的類,由於不支持類列表 * IE中的SVG元素 *爲真實dom 元素添加class類 */ function addClass(el, cls) { /* istanbul ignore if */ if (!cls || !(cls = cls.trim())) { return } /* istanbul ignore else */ if (el.classList) { //若是瀏覽器支持classList if (cls.indexOf(' ') > -1) { cls.split(/\s+/).forEach(function (c) { return el.classList.add(c); }); } else { el.classList.add(cls); } } else { //不支持classList 直接用字符串拼接 var cur = " " + (el.getAttribute('class') || '') + " "; if (cur.indexOf(' ' + cls + ' ') < 0) { el.setAttribute('class', (cur + cls).trim()); } } } /** * Remove class with compatibility for SVG since classList is not supported on * SVG elements in IE *刪除與SVG兼容的類,由於不支持類列表 * IE中的SVG元素 刪除真實dom的css類名 */ function removeClass(el, cls) { /* istanbul ignore if */ if (!cls || !(cls = cls.trim())) { return } /* istanbul ignore else */ if (el.classList) { if (cls.indexOf(' ') > -1) { cls.split(/\s+/).forEach(function (c) { return el.classList.remove(c); }); } else { el.classList.remove(cls); } if (!el.classList.length) { el.removeAttribute('class'); } } else { var cur = " " + (el.getAttribute('class') || '') + " "; var tar = ' ' + cls + ' '; while (cur.indexOf(tar) >= 0) { cur = cur.replace(tar, ' '); } cur = cur.trim(); if (cur) { el.setAttribute('class', cur); } else { el.removeAttribute('class'); } } } /* * * 解析vonde中的transition的name屬性獲取到一個css過分對象類 * */ function resolveTransition(def) { if (!def) { return } /* istanbul ignore else */ if (typeof def === 'object') { var res = {}; if (def.css !== false) { // 使用 name,默認爲 v // 經過 name 屬性獲取過渡 CSS 類名 好比標籤上面定義name是 fade css就要定義 .fade-enter-active,.fade-leave-active,.fade-enter,.fade-leave-to 這樣的class extend(res, autoCssTransition(def.name || 'v')); } extend(res, def); return res } else if (typeof def === 'string') { return autoCssTransition(def) } } // 經過 name 屬性獲取過渡 CSS 類名 好比標籤上面定義name是 fade css就要定義 .fade-enter-active,.fade-leave-active,.fade-enter,.fade-leave-to 這樣的class var autoCssTransition = cached(function (name) { return { enterClass: (name + "-enter"), // enterToClass: (name + "-enter-to"), // enterActiveClass: (name + "-enter-active"), //進入激活動畫的css類 相似這樣的 v-enter-active {transition: all .3s ease;} leaveClass: (name + "-leave"), //離開動畫的css 動畫過分類 leaveToClass: (name + "-leave-to"), //離開動畫的css 動畫過分類 leaveActiveClass: (name + "-leave-active")//激活離開動畫的css 動畫過分類 } }); var hasTransition = inBrowser && !isIE9; var TRANSITION = 'transition'; var ANIMATION = 'animation'; // Transition property/event sniffing var transitionProp = 'transition'; var transitionEndEvent = 'transitionend'; var animationProp = 'animation'; var animationEndEvent = 'animationend'; if (hasTransition) { /* istanbul ignore if */ if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined ) { transitionProp = 'WebkitTransition'; transitionEndEvent = 'webkitTransitionEnd'; } if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined ) { animationProp = 'WebkitAnimation'; animationEndEvent = 'webkitAnimationEnd'; } } // binding to window is necessary to make hot reload work in IE in strict mode //綁定到窗口是必要的,使熱重載工做在IE嚴格模式 //若是是瀏覽器若是瀏覽器支持requestAnimationFrame就用requestAnimationFrame,不支持就用setTimeout var raf = inBrowser ? (window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : setTimeout) : function (fn) { return fn(); }; //下一幀 function nextFrame(fn) { raf(function () { raf(fn); }); } //獲取 真實dom addTransitionClass 記錄calss類 function addTransitionClass(el, cls) { var transitionClasses = el._transitionClasses || (el._transitionClasses = []); if (transitionClasses.indexOf(cls) < 0) { //若是沒有添加則添加 transitionClasses.push(cls); //爲真實的dom添加class addClass(el, cls); } } //刪除vonde的class類和刪除真實dom的class類 function removeTransitionClass(el, cls) { if (el._transitionClasses) { remove(el._transitionClasses, cls); //刪除數組 } // 刪除真實dom的css類名 removeClass(el, cls); } // 獲取動畫的信息,執行動畫。 function whenTransitionEnds( el, //真實的dom expectedType,//動畫類型 cb //回調方法 ) { //獲取返回transition,或者animation 動畫的類型,動畫個數,動畫執行時間 var ref = getTransitionInfo(el, expectedType); var type = ref.type; //動畫類型 var timeout = ref.timeout;//總動畫執行的時長 var propCount = ref.propCount; //動畫的個數 if (!type) { return cb() } //TRANSITION=transition //判斷是transition動畫仍是animation動畫 var event = type === TRANSITION ? transitionEndEvent : animationEndEvent; var ended = 0; var end = function () { //結束動畫函數 //刪除動畫事件 el.removeEventListener(event, onEnd); cb(); //回調執行動畫 }; var onEnd = function (e) { if (e.target === el) { if (++ended >= propCount) { end(); } } }; setTimeout(function () { //執行動畫 if (ended < propCount) { end(); //時間到了就執行動畫而且刪除事件。 } }, timeout + 1); el.addEventListener(event, onEnd); } var transformRE = /\b(transform|all)(,|$)/; //獲取Transition 過分動畫信息 //獲取transition,或者animation 動畫的類型,動畫個數,動畫執行時間 function getTransitionInfo( el, //真實的dom expectedType //動畫類型 ) { // Window.getComputedStyle()方法返回一個對象, // 該對象在應用活動樣式表並解析這些值可能包含的任何基本計算後報告元素的全部CSS屬性的值 // 私有的CSS屬性值能夠經過對象提供的API或經過簡單地使用CSS屬性名稱進行索引來訪問。 var styles = window.getComputedStyle(el); // console.log('==styles==') console.log(styles) // var transitionProp = 'transition'; var transitionDelays = styles[transitionProp + 'Delay'].split(', '); //獲取動畫時間 var transitionDurations = styles[transitionProp + 'Duration'].split(', '); //獲取動畫時間 //transitionDelays=5s var transitionTimeout = getTimeout(transitionDelays, transitionDurations);//獲取動畫時間 var animationDelays = styles[animationProp + 'Delay'].split(', ');//獲取動畫時間 var animationDurations = styles[animationProp + 'Duration'].split(', ');//獲取動畫時間 var animationTimeout = getTimeout(animationDelays, animationDurations); //獲取動畫時間 console.log('transitionDelays='+transitionDelays) console.log('transitionDurations='+transitionDurations) console.log('transitionTimeout='+transitionTimeout) console.log('animationDelays='+animationDelays) console.log('animationDurations='+animationDurations) console.log('animationTimeout='+animationTimeout) var type; //動畫類型 var timeout = 0; //動畫時長 var propCount = 0; //動畫個數 /* istanbul ignore if */ if (expectedType === TRANSITION) {// 判斷動畫是不是transition if (transitionTimeout > 0) { type = TRANSITION; timeout = transitionTimeout; propCount = transitionDurations.length; } } else if (expectedType === ANIMATION) { //判斷動畫是不是animation if (animationTimeout > 0) { type = ANIMATION; timeout = animationTimeout; propCount = animationDurations.length; } } else { timeout = Math.max(transitionTimeout, animationTimeout); type = timeout > 0 ? transitionTimeout > animationTimeout ? TRANSITION : ANIMATION : null; propCount = type ? type === TRANSITION ? transitionDurations.length : animationDurations.length : 0; } var hasTransform = type === TRANSITION && transformRE.test(styles[transitionProp + 'Property']); console.log(styles[transitionProp + 'Property']) //獲取動畫設置在哪些屬性上面 return { type: type,//過分或者css3動畫類型 timeout: timeout, //執行動畫的時長 propCount: propCount, //動畫個數 執行多個動畫 hasTransform: hasTransform //布爾值 是否是 transition 動畫 } } function getTimeout(delays, durations) { console.log(delays) console.log(durations) debugger /* istanbul ignore next */ while (delays.length < durations.length) { delays = delays.concat(delays); } return Math.max.apply(null, durations.map(function (d, i) { return toMs(d) + toMs(delays[i]) })) } function toMs(s) { return Number(s.slice(0, -1)) * 1000 } //resolveTransition 解析vonde中的transition的name屬性獲取到一個css過分對象類 function enter( vnode, toggleDisplay ) { var el = vnode.elm; //真實的dom // call leave callback now 執行 leave 回調函數 if (isDef(el._leaveCb)) { el._leaveCb.cancelled = true; //標誌已經執行過_leaveCb函數 el._leaveCb(); //執行_leaveCb回調 } //resolveTransition 解析vonde中的transition的name屬性獲取到一個css過分對象類 var data = resolveTransition(vnode.data.transition); console.log(vnode.data.transition) console.log(data) if (isUndef(data)) { return } /* istanbul ignore if */ if (isDef(el._enterCb) || el.nodeType !== 1) { //不是真實的dom return } var css = data.css; //css類 var type = data.type; //dom類型 var enterClass = data.enterClass; //動畫進入中的 css 中的過分類 var enterToClass = data.enterToClass; //動畫退出中的 css 中的過分類 var enterActiveClass = data.enterActiveClass; //動畫進入活躍的類 相似這樣的 enter-active {transition: all .3s ease;} var appearClass = data.appearClass; // 自定義動畫props屬性 過分 var appearToClass = data.appearToClass; //自定義動畫props屬性 離開的過分 css 類名 var appearActiveClass = data.appearActiveClass;//自定義動畫props屬性 激活 css 類名 var beforeEnter = data.beforeEnter; //進入動畫鉤子函數 var enter = data.enter;//進入動畫鉤子函數 var afterEnter = data.afterEnter; //進入動畫鉤子函數 var enterCancelled = data.enterCancelled;//進入動畫鉤子函數 var beforeAppear = data.beforeAppear; //自定義過過分動畫的鉤子函數 var appear = data.appear; //自定義過分動畫的 屬性名稱 var afterAppear = data.afterAppear; //自定義過分動畫的 鉤子函數 var appearCancelled = data.appearCancelled; //自定義過分動畫的 鉤子函數 var duration = data.duration; //定義動畫的時長 // activeInstance will always be the <transition> component managing this // transition. One edge case to check is when the <transition> is placed // as the root node of a child component. In that case we need to check // <transition>'s parent for appear check. //activeInstance始終是管理這個的<transition>組件 //轉換。要檢查的一種邊緣狀況是什麼時候放置<transition> //做爲子組件的根節點。那樣的話,咱們須要檢查一下 // <切換到>的父節點以查看是否出現。 var context = activeInstance; //vue 實例化的對象 var transitionNode = activeInstance.$vnode; // 父層的Vnode while (transitionNode && transitionNode.parent) { //循環父層vonde 一直到頂層的 vonde transitionNode = transitionNode.parent; context = transitionNode.context; } var isAppear = !context._isMounted || //是否已經調用過Mounted 生命週期函數 !vnode.isRootInsert; // /*是否做爲跟節點插入*/ if (isAppear && !appear && appear !== '') { return } //獲取靜態css類, var startClass = isAppear && appearClass ? appearClass : enterClass; /* 獲取激活css類 相似這樣的 .v-leave-active { transition: opacity .5s; } .v-enter-active{ transition: opacity .5s; } */ var activeClass = isAppear && appearActiveClass ? appearActiveClass : enterActiveClass; /* 獲取過分時候的css類,相似這樣的 .fade-enter, .fade-leave-to { opacity: 0; } * */ var toClass = isAppear && appearToClass // 離開的過分 css 類名 ? appearToClass : enterToClass; //鉤子函數 進入動畫的鉤子函數 var beforeEnterHook = isAppear ? (beforeAppear || beforeEnter) : beforeEnter; // var enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : enter; //進入過分動畫的鉤子函數 var afterEnterHook = isAppear ? (afterAppear || afterEnter) : afterEnter; //取消過分動畫的鉤子函數 var enterCancelledHook = isAppear ? (appearCancelled || enterCancelled) : enterCancelled; //動畫時長 var explicitEnterDuration = toNumber( isObject(duration) ? duration.enter : duration ); if ("development" !== 'production' && explicitEnterDuration != null) { checkDuration(explicitEnterDuration, 'enter', vnode); } var expectsCSS = css !== false && !isIE9; //若是不是在ie9的環境下。還有css類 //檢測鉤子函數 fns 的長度 var userWantsControl = getHookArgumentsLength(enterHook); var cb = el._enterCb = once(function () { //只執行一次函數 //這個函數就是給dom添加css class 讓dom執行動畫的 if (expectsCSS) { removeTransitionClass(el, toClass); //刪除了 離開的過分 css 類名 removeTransitionClass(el, activeClass); //刪除了 激活過分 css 類名 } if (cb.cancelled) { //若是執行過了_enterCb函數 if (expectsCSS) { removeTransitionClass(el, startClass); // } enterCancelledHook && enterCancelledHook(el); //回調 取消過分動畫的鉤子函數 } else { afterEnterHook && afterEnterHook(el); //回調進入過分動畫的鉤子函數 } el._enterCb = null; }); if (!vnode.data.show) { // remove pending leave element on enter by injecting an insert hook //經過注入插入鉤子,在進入時刪除掛起的leave元素 mergeVNodeHook( vnode, 'insert', function () { var parent = el.parentNode; //獲取真實dom的父節點 var pendingNode = parent && parent._pending && parent._pending[vnode.key]; if ( pendingNode && pendingNode.tag === vnode.tag && pendingNode.elm._leaveCb ) { //調用離開回調函數 pendingNode.elm._leaveCb(); } //調用的進入過分動畫鉤子函數 enterHook && enterHook(el, cb); }); } // start enter transition //開始進入過渡 動畫 鉤子函數 beforeEnterHook && beforeEnterHook(el); if (expectsCSS) { //若是沒有dom中沒有clss類 //爲真實dom添加class類 addTransitionClass( el, startClass ); //爲真實dom添加class類 addTransitionClass( el, activeClass ); nextFrame(function () { console.log('nextFrame') removeTransitionClass(el, startClass); //執行過了就刪除class類 if (!cb.cancelled) { //若是仍是取消動畫 addTransitionClass(el, toClass); //則添加過分動畫 class if (!userWantsControl) { //檢測鉤子函數 fns 的長度 if (isValidDuration(explicitEnterDuration)) { //若是是 number 類型 setTimeout(cb, explicitEnterDuration); //設置延遲過分事件 } else { whenTransitionEnds( el, //真實的dom type, //動畫類型 cb //_enterCb 回調函數 ); } } } }); } if (vnode.data.show) { toggleDisplay && toggleDisplay(); //執行回調切換顯示或者隱藏函數 enterHook && enterHook(el, cb); } if (!expectsCSS && !userWantsControl) { cb(); } } //執行離開過分動畫效果執行方式 function leave( vnode, //虛擬dom rm //回調函數 ) { var el = vnode.elm; // call enter callback now if (isDef(el._enterCb)) { //標誌是否執行過_enterCb el._enterCb.cancelled = true; //取消 el._enterCb(); } //解析vonde中的transition的name屬性獲取到一個css過分對象類 var data = resolveTransition(vnode.data.transition); if (isUndef(data) || el.nodeType !== 1) { return rm() } /* istanbul ignore if */ if (isDef(el._leaveCb)) { return } var css = data.css; //vonde 的css類 var type = data.type; //vonde 的 類型 如 1,2,3,4.真實dom的類型 var leaveClass = data.leaveClass; //離開動畫的css 動畫過分類 var leaveToClass = data.leaveToClass; //離開動畫的css 動畫過分類 var leaveActiveClass = data.leaveActiveClass;//激活離開動畫的css 動畫過分類 var beforeLeave = data.beforeLeave; //離開動畫的鉤子函數 var leave = data.leave; //離開動畫的鉤子函數 var afterLeave = data.afterLeave;//離開動畫的鉤子函數 var leaveCancelled = data.leaveCancelled;//離開動畫的鉤子函數 var delayLeave = data.delayLeave; //延遲動畫鉤子函數 var duration = data.duration; //動畫時長 var expectsCSS = css !== false && !isIE9; // 檢測鉤子函數 fns 的長度 // 數據必須是這樣才返回真,也能夠是n層fns只要規律是同樣嵌套下去就行 var userWantsControl = getHookArgumentsLength(leave); var explicitLeaveDuration = toNumber( isObject(duration) ? duration.leave : duration ); if ("development" !== 'production' && isDef(explicitLeaveDuration)) { checkDuration(explicitLeaveDuration, 'leave', vnode); } var cb = el._leaveCb = once(function () { if (el.parentNode && el.parentNode._pending) { el.parentNode._pending[vnode.key] = null; } if (expectsCSS) { removeTransitionClass(el, leaveToClass); //離開動畫的css 動畫過分類 removeTransitionClass(el, leaveActiveClass);//激活離開動畫的css 動畫過分類 } if (cb.cancelled) { //取消過分動畫標誌 if (expectsCSS) { removeTransitionClass(el, leaveClass); //離開動畫的css 動畫過分類 } leaveCancelled && leaveCancelled(el); //鉤子函數 } else { rm(); //執行回調函數 afterLeave && afterLeave(el); //執行鉤子函數 } el._leaveCb = null; }); if (delayLeave) { delayLeave(performLeave); //delayLeave 延遲動畫鉤子函數 } else { performLeave(); } function performLeave() { // the delayed leave may have already been cancelled if (cb.cancelled) { //取消過分動畫標誌 return } // record leaving element if (!vnode.data.show) { (el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key)] = vnode; } beforeLeave && beforeLeave(el); //離開動畫的鉤子函數 if (expectsCSS) { addTransitionClass(el, leaveClass);// 爲真實dom添加 css 過分動畫leaveClass類 addTransitionClass(el, leaveActiveClass);//激活離開動畫的css 動畫過分類 nextFrame(function () { removeTransitionClass(el, leaveClass);// 爲真實dom刪除 css 過分動畫leaveClass類 if (!cb.cancelled) { //取消過分動畫標誌 addTransitionClass(el, leaveToClass); //離開動畫的css 動畫過分類 if (!userWantsControl) { if (isValidDuration(explicitLeaveDuration)) { //若是是數字 setTimeout(cb, explicitLeaveDuration); //執行回調函數 _leaveCb } else { // 獲取動畫的信息,執行動畫。 whenTransitionEnds( el, //真實的dom type, //動畫類型 cb//執行回調函數 _leaveCb ); // } } } }); } leave && leave(el, cb); if (!expectsCSS && !userWantsControl) { cb(); } } } // only used in dev mode //檢測 val必需是數字 function checkDuration(val, name, vnode) { if (typeof val !== 'number') { warn( "<transition> explicit " + name + " duration is not a valid number - " + "got " + (JSON.stringify(val)) + ".", vnode.context ); } else if (isNaN(val)) { warn( "<transition> explicit " + name + " duration is NaN - " + 'the duration expression might be incorrect.', vnode.context ); } } //若是是 number 類型 function isValidDuration(val) { return typeof val === 'number' && !isNaN(val) } /** * Normalize a transition hook's argument length. The hook may be: * - a merged hook (invoker) with the original in .fns * - a wrapped component method (check ._length) * - a plain function (.length) *規範化轉換鉤子的參數長度。問題多是: * -一個合併的鉤子(調用程序)與原始的。fns * -封裝的組件方法(檢查._length) * -一個普通函數(.length) 檢測鉤子函數 fns 的長度 數據必須是這樣才返回真,也能夠是n層fns只要規律是同樣嵌套下去就行 var fn1=[1,2,3,4]; var fn={ fns:[ [1,2,3,45,34] ] } var fn2={ fns:[ { fns:[ { fns:[[1,2,3,45,9]] } ] } ] } */ function getHookArgumentsLength(fn) { if (isUndef(fn)) { return false } var invokerFns = fn.fns; if (isDef(invokerFns)) { // invoker return getHookArgumentsLength( Array.isArray(invokerFns) ? invokerFns[0] : invokerFns ) } else { return (fn._length || fn.length) > 1 } } function _enter(_, vnode) { if (vnode.data.show !== true) { //若是不是show的時候 enter(vnode); } } // var transition = inBrowser ? //若是是瀏覽器環境 { create: _enter, //進入時 activate: _enter, //激活 remove: function remove$$1(vnode, rm) { //刪除 /* istanbul ignore else */ if (vnode.data.show !== true) { leave(vnode, rm); } else { rm(); } } }: {} var platformModules = [ attrs, // attrs包含兩個方法create和update都是更新設置真實dom屬性值 {create: updateAttrs, /*建立屬性*/ update: updateAttrs /*更新屬性 */} klass, //klass包含類包含兩個方法create和update都是更新calss。其實就是updateClass方法。 設置真實dom的class events, //更新真實dom的事件 domProps, //更新真實dom的props 屬性值 style, // 更新真實dom的style屬性。有兩個方法create 和update 不過函數都是updateStyle更新真實dom的style屬性值.將vonde虛擬dom的css 轉義成而且渲染到真實dom的css中 transition // 過分動畫 ] /* */ // the directive module should be applied last, after all // built-in modules have been applied. //畢竟,指令模塊應該是最後應用的 //已應用內置模塊。 //baseModules 包括了 ref建立,更新 , 銷燬 函數 和 directives自定義指令 建立 ,更新,銷燬函數 var modules = platformModules.concat(baseModules); //建立補丁函數 建立虛擬dom /* var nodeOps = Object.freeze({ createElement: createElement$1, //建立一個真實的dom createElementNS: createElementNS, //建立一個真實的dom svg方式 createTextNode: createTextNode, // 建立文本節點 createComment: createComment, // 建立一個註釋節點 insertBefore: insertBefore, //插入節點 在xxx dom 前面插入一個節點 removeChild: removeChild, //刪除子節點 appendChild: appendChild, //添加子節點 尾部 parentNode: parentNode, //獲取父親子節點dom nextSibling: nextSibling, //獲取下一個兄弟節點 tagName: tagName, //獲取dom標籤名稱 setTextContent: setTextContent, // //設置dom 文本 setStyleScope: setStyleScope //設置組建樣式的做用域 }); modules=[ attrs, // attrs包含兩個方法create和update都是更新設置真實dom屬性值 {create: updateAttrs, update: updateAttrs } klass, //klass包含類包含兩個方法create和update都是更新calss。其實就是updateClass方法。 設置真實dom的class events, //更新真實dom的事件 domProps, //更新真實dom的props 屬性值 style, // 更新真實dom的style屬性。有兩個方法create 和update 不過函數都是updateStyle更新真實dom的style屬性值.將vonde虛擬dom的css 轉義成而且渲染到真實dom的css中 transition // 過分動畫 ref, //ref建立,更新 , 銷燬 函數 directives //自定義指令 建立 ,更新,銷燬函數 ] */ //path 把vonde 渲染成真實的dom var patch = createPatchFunction( { nodeOps: nodeOps, modules: modules } ); console.log('===patch==') console.log(patch) /** * Not type checking this file because flow doesn't like attaching * properties to Elements. */ /* istanbul ignore if */ if (isIE9) { // http://www.matts411.com/post/internet-explorer-9-oninput/ document.addEventListener('selectionchange', function () { var el = document.activeElement; if (el && el.vmodel) { trigger(el, 'input'); } }); } var directive = { inserted: function inserted(el, binding, vnode, oldVnode) { if (vnode.tag === 'select') { // #6903 if (oldVnode.elm && !oldVnode.elm._vOptions) { mergeVNodeHook(vnode, 'postpatch', function () { directive.componentUpdated(el, binding, vnode); }); } else { setSelected(el, binding, vnode.context); } el._vOptions = [].map.call(el.options, getValue); } else if (vnode.tag === 'textarea' || isTextInputType(el.type)) { el._vModifiers = binding.modifiers; if (!binding.modifiers.lazy) { el.addEventListener('compositionstart', onCompositionStart); el.addEventListener('compositionend', onCompositionEnd); // Safari < 10.2 & UIWebView doesn't fire compositionend when // switching focus before confirming composition choice // this also fixes the issue where some browsers e.g. iOS Chrome // fires "change" instead of "input" on autocomplete. el.addEventListener('change', onCompositionEnd); /* istanbul ignore if */ if (isIE9) { el.vmodel = true; } } } }, componentUpdated: function componentUpdated(el, binding, vnode) { if (vnode.tag === 'select') { setSelected(el, binding, vnode.context); // in case the options rendered by v-for have changed, // it's possible that the value is out-of-sync with the rendered options. // detect such cases and filter out values that no longer has a matching // option in the DOM. var prevOptions = el._vOptions; var curOptions = el._vOptions = [].map.call(el.options, getValue); if (curOptions.some(function (o, i) { return !looseEqual(o, prevOptions[i]); })) { // trigger change event if // no matching option found for at least one value var needReset = el.multiple ? binding.value.some(function (v) { return hasNoMatchingOption(v, curOptions); }) : binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions); if (needReset) { trigger(el, 'change'); } } } } }; function setSelected(el, binding, vm) { actuallySetSelected(el, binding, vm); /* istanbul ignore if */ if (isIE || isEdge) { setTimeout(function () { actuallySetSelected(el, binding, vm); }, 0); } } function actuallySetSelected(el, binding, vm) { var value = binding.value; var isMultiple = el.multiple; if (isMultiple && !Array.isArray(value)) { "development" !== 'production' && warn( "<select multiple v-model=\"" + (binding.expression) + "\"> " + "expects an Array value for its binding, but got " + (Object.prototype.toString.call(value).slice(8, -1)), vm ); return } var selected, option; for (var i = 0, l = el.options.length; i < l; i++) { option = el.options[i]; if (isMultiple) { selected = looseIndexOf(value, getValue(option)) > -1; if (option.selected !== selected) { option.selected = selected; } } else { if (looseEqual(getValue(option), value)) { if (el.selectedIndex !== i) { el.selectedIndex = i; } return } } } if (!isMultiple) { el.selectedIndex = -1; } } function hasNoMatchingOption(value, options) { return options.every(function (o) { return !looseEqual(o, value); }) } function getValue(option) { return '_value' in option ? option._value : option.value } function onCompositionStart(e) { e.target.composing = true; } function onCompositionEnd(e) { // prevent triggering an input event for no reason if (!e.target.composing) { return } e.target.composing = false; trigger(e.target, 'input'); } function trigger(el, type) { var e = document.createEvent('HTMLEvents'); e.initEvent(type, true, true); el.dispatchEvent(e); } /* */ // recursively search for possible transition defined inside the component root function locateNode(vnode) { return vnode.componentInstance && (!vnode.data || !vnode.data.transition) ? locateNode(vnode.componentInstance._vnode) : vnode } var show = { bind: function bind(el, ref, vnode) { var value = ref.value; vnode = locateNode(vnode); var transition$$1 = vnode.data && vnode.data.transition; var originalDisplay = el.__vOriginalDisplay = el.style.display === 'none' ? '' : el.style.display; if (value && transition$$1) { vnode.data.show = true; enter(vnode, function () { el.style.display = originalDisplay; }); } else { el.style.display = value ? originalDisplay : 'none'; } }, update: function update(el, ref, vnode) { var value = ref.value; var oldValue = ref.oldValue; /* istanbul ignore if */ if (!value === !oldValue) { return } vnode = locateNode(vnode); var transition$$1 = vnode.data && vnode.data.transition; if (transition$$1) { vnode.data.show = true; if (value) { enter(vnode, function () { el.style.display = el.__vOriginalDisplay; }); } else { leave(vnode, function () { el.style.display = 'none'; }); } } else { el.style.display = value ? el.__vOriginalDisplay : 'none'; } }, unbind: function unbind(el, binding, vnode, oldVnode, isDestroy) { if (!isDestroy) { el.style.display = el.__vOriginalDisplay; } } } var platformDirectives = { model: directive, show: show } /* */ // Provides transition support for a single element/component. // supports transition mode (out-in / in-out) var transitionProps = { name: String, // appear: Boolean, css: Boolean, mode: String, type: String, enterClass: String, leaveClass: String, //離開動畫的css 動畫過分類 enterToClass: String, // 動畫退出中的 css 中的過分類 leaveToClass: String, //離開動畫的css 動畫過分類 enterActiveClass: String, //激活過分動畫 的css 類 leaveActiveClass: String, //激活離開動畫的css 動畫過分類 appearClass: String, // 自定義動畫props屬性 過分 appearActiveClass: String, //自定義動畫props屬性 激活 css 類名 appearToClass: String, //自定義動畫props屬性 離開的過分 css 類名 duration: [Number, String, Object] //持續的時間 }; // in case the child is also an abstract component, e.g. <keep-alive> // we want to recursively retrieve the real component to be rendered function getRealChild(vnode) { var compOptions = vnode && vnode.componentOptions; if (compOptions && compOptions.Ctor.options.abstract) { return getRealChild(getFirstComponentChild(compOptions.children)) } else { return vnode } } function extractTransitionData(comp) { var data = {}; var options = comp.$options; // props for (var key in options.propsData) { data[key] = comp[key]; } // events. // extract listeners and pass them directly to the transition methods var listeners = options._parentListeners; for (var key$1 in listeners) { data[camelize(key$1)] = listeners[key$1]; } return data } function placeholder(h, rawChild) { if (/\d-keep-alive$/.test(rawChild.tag)) { return h('keep-alive', { props: rawChild.componentOptions.propsData }) } } function hasParentTransition(vnode) { while ((vnode = vnode.parent)) { if (vnode.data.transition) { return true } } } function isSameChild(child, oldChild) { return oldChild.key === child.key && oldChild.tag === child.tag } var Transition = { //動畫組件 name: 'transition', props: transitionProps, //動畫屬性 abstract: true, render: function render(h) { //動畫組件的vonde var this$1 = this; var children = this.$slots.default; if (!children) { return } // filter out text nodes (possible whitespaces) children = children.filter(function (c) { return c.tag || isAsyncPlaceholder(c); }); /* istanbul ignore if */ if (!children.length) { return } // warn multiple elements if ("development" !== 'production' && children.length > 1) { warn( '<transition> can only be used on a single element. Use ' + '<transition-group> for lists.', this.$parent ); } var mode = this.mode; // warn invalid mode if ("development" !== 'production' && mode && mode !== 'in-out' && mode !== 'out-in' ) { warn( 'invalid <transition> mode: ' + mode, this.$parent ); } var rawChild = children[0]; // if this is a component root node and the component's // parent container node also has transition, skip. if (hasParentTransition(this.$vnode)) { return rawChild } // apply transition data to child // use getRealChild() to ignore abstract components e.g. keep-alive var child = getRealChild(rawChild); /* istanbul ignore if */ if (!child) { return rawChild } if (this._leaving) { return placeholder(h, rawChild) } // ensure a key that is unique to the vnode type and to this transition // component instance. This key will be used to remove pending leaving nodes // during entering. var id = "__transition-" + (this._uid) + "-"; child.key = child.key == null ? child.isComment ? id + 'comment' : id + child.tag : isPrimitive(child.key) ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key) : child.key; var data = (child.data || (child.data = {})).transition = extractTransitionData(this); var oldRawChild = this._vnode; var oldChild = getRealChild(oldRawChild); // mark v-show // so that the transition module can hand over the control to the directive if (child.data.directives && child.data.directives.some(function (d) { return d.name === 'show'; })) { child.data.show = true; } if ( oldChild && oldChild.data && !isSameChild(child, oldChild) && !isAsyncPlaceholder(oldChild) && // #6687 component root is a comment node !(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment) ) { // replace old child transition data with fresh one // important for dynamic transitions! var oldData = oldChild.data.transition = extend({}, data); // handle transition mode if (mode === 'out-in') { // return placeholder node and queue update when leave finishes this._leaving = true; mergeVNodeHook(oldData, 'afterLeave', function () { this$1._leaving = false; this$1.$forceUpdate(); }); return placeholder(h, rawChild) } else if (mode === 'in-out') { if (isAsyncPlaceholder(child)) { return oldRawChild } var delayedLeave; var performLeave = function () { delayedLeave(); }; mergeVNodeHook(data, 'afterEnter', performLeave); mergeVNodeHook(data, 'enterCancelled', performLeave); mergeVNodeHook(oldData, 'delayLeave', function (leave) { delayedLeave = leave; }); } } return rawChild } } /* */ // Provides transition support for list items. // supports move transitions using the FLIP technique. // Because the vdom's children update algorithm is "unstable" - i.e. // it doesn't guarantee the relative positioning of removed elements, // we force transition-group to update its children into two passes: // in the first pass, we remove all nodes that need to be removed, // triggering their leaving transition; in the second pass, we insert/move // into the final desired state. This way in the second pass removed // nodes will remain where they should be. var props = extend({ tag: String, moveClass: String }, transitionProps); delete props.mode; var TransitionGroup = { props: props, render: function render(h) { var tag = this.tag || this.$vnode.data.tag || 'span'; var map = Object.create(null); var prevChildren = this.prevChildren = this.children; var rawChildren = this.$slots.default || []; var children = this.children = []; var transitionData = extractTransitionData(this); for (var i = 0; i < rawChildren.length; i++) { var c = rawChildren[i]; if (c.tag) { if (c.key != null && String(c.key).indexOf('__vlist') !== 0) { children.push(c); map[c.key] = c ; (c.data || (c.data = {})).transition = transitionData; } else { var opts = c.componentOptions; var name = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag; warn(("<transition-group> children must be keyed: <" + name + ">")); } } } if (prevChildren) { var kept = []; var removed = []; for (var i$1 = 0; i$1 < prevChildren.length; i$1++) { var c$1 = prevChildren[i$1]; c$1.data.transition = transitionData; c$1.data.pos = c$1.elm.getBoundingClientRect(); if (map[c$1.key]) { kept.push(c$1); } else { removed.push(c$1); } } this.kept = h(tag, null, kept); this.removed = removed; } return h(tag, null, children) }, beforeUpdate: function beforeUpdate() { // force removing pass this.__patch__( this._vnode, this.kept, false, // hydrating true // removeOnly (!important, avoids unnecessary moves) ); this._vnode = this.kept; }, updated: function updated() { var children = this.prevChildren; var moveClass = this.moveClass || ((this.name || 'v') + '-move'); if (!children.length || !this.hasMove(children[0].elm, moveClass)) { return } // we divide the work into three loops to avoid mixing DOM reads and writes // in each iteration - which helps prevent layout thrashing. children.forEach(callPendingCbs); children.forEach(recordPosition); children.forEach(applyTranslation); // force reflow to put everything in position // assign to this to avoid being removed in tree-shaking // $flow-disable-line this._reflow = document.body.offsetHeight; children.forEach(function (c) { if (c.data.moved) { var el = c.elm; var s = el.style; addTransitionClass(el, moveClass); s.transform = s.WebkitTransform = s.transitionDuration = ''; el.addEventListener(transitionEndEvent, el._moveCb = function cb(e) { if (!e || /transform$/.test(e.propertyName)) { el.removeEventListener(transitionEndEvent, cb); el._moveCb = null; removeTransitionClass(el, moveClass); } }); } }); }, methods: { hasMove: function hasMove(el, moveClass) { /* istanbul ignore if */ if (!hasTransition) { return false } /* istanbul ignore if */ if (this._hasMove) { return this._hasMove } // Detect whether an element with the move class applied has // CSS transitions. Since the element may be inside an entering // transition at this very moment, we make a clone of it and remove // all other transition classes applied to ensure only the move class // is applied. var clone = el.cloneNode(); if (el._transitionClasses) { el._transitionClasses.forEach(function (cls) { removeClass(clone, cls); }); } addClass(clone, moveClass); clone.style.display = 'none'; this.$el.appendChild(clone); var info = getTransitionInfo(clone); this.$el.removeChild(clone); return (this._hasMove = info.hasTransform) } } } function callPendingCbs(c) { /* istanbul ignore if */ if (c.elm._moveCb) { c.elm._moveCb(); } /* istanbul ignore if */ if (c.elm._enterCb) { c.elm._enterCb(); } } function recordPosition(c) { c.data.newPos = c.elm.getBoundingClientRect(); } function applyTranslation(c) { var oldPos = c.data.pos; var newPos = c.data.newPos; var dx = oldPos.left - newPos.left; var dy = oldPos.top - newPos.top; if (dx || dy) { c.data.moved = true; var s = c.elm.style; s.transform = s.WebkitTransform = "translate(" + dx + "px," + dy + "px)"; s.transitionDuration = '0s'; } } var platformComponents = { Transition: Transition, TransitionGroup: TransitionGroup } /* */ // install platform specific utils /*校驗屬性 * 1. attr === 'value', tag 必須是 'input,textarea,option,select,progress' 其中一個 type !== 'button' * 2. attr === 'selected' && tag === 'option' * 3. attr === 'checked' && tag === 'input' * 4. attr === 'muted' && tag === 'video' * 的狀況下爲真 * */ Vue.config.mustUseProp = mustUseProp; //校驗屬性 Vue.config.isReservedTag = isReservedTag; Vue.config.isReservedAttr = isReservedAttr; Vue.config.getTagNamespace = getTagNamespace; Vue.config.isUnknownElement = isUnknownElement; // install platform runtime directives & components extend(Vue.options.directives, platformDirectives); extend(Vue.options.components, platformComponents); // install platform patch function 安裝平臺補丁功能 Vue.prototype.__patch__ = inBrowser ? patch : noop; // public mount method 安裝方法 實例方法掛載 vm // 手動地掛載一個未掛載的實例。 Vue.prototype.$mount = function (el, //真實dom 或者是string hydrating //新的虛擬dom vonde ) { debugger el = el && inBrowser ? query(el) : undefined; return mountComponent( this, el, hydrating ) }; // devtools global hook /* istanbul ignore next */ if (inBrowser) { setTimeout(function () { if (config.devtools) { if (devtools) { devtools.emit('init', Vue); } else if ( "development" !== 'production' && "development" !== 'test' && isChrome ) { console[console.info ? 'info' : 'log']( 'Download the Vue Devtools extension for a better development experience:\n' + 'https://github.com/vuejs/vue-devtools' ); } } //若是不是生產環境 if ("development" !== 'production' && "development" !== 'test' && config.productionTip !== false && typeof console !== 'undefined' ) { console[console.info ? 'info' : 'log']( "You are running Vue in development mode.\n" + "Make sure to turn on production mode when deploying for production.\n" + "See more tips at https://vuejs.org/guide/deployment.html" ); } }, 0); } /* */ // var defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g; //匹配viwe 視圖中的{{指令}} var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g; //匹配特殊符號 - 或者. 或者* 或者+ 或者? 或者^ 或者$ 或者{ 或者} 或者( 或者) 或者| 或者[ 或者] 或者/ 或者\ var buildRegex = cached(function (delimiters) { var open = delimiters[0].replace(regexEscapeRE, '\\$&'); //$& 與 regexp 相匹配的子串。 這裏的意思是遇到了特殊符號的時候在正則裏面須要替換加多一個/斜槓 var close = delimiters[1].replace(regexEscapeRE, '\\$&'); return new RegExp(open + '((?:.|\\n)+?)' + close, 'g') // 匹配開始的open +任意字符或者換行符+ close 全局匹配 }); //匹配view 指令,而且把他轉換成 虛擬dom vonde 須要渲染的函數,好比指令{{name}}轉換成 _s(name) //好比字符串 我叫{{name}},今年{{age}},數據{{data.number}}個手機 轉換成 我叫+_s(name)+,今年+_s(age)+,數據+_s(data.number)+個手機 function parseText(text, //字符串 delimiters //被修改默認的標籤匹配 ) { var tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE; // 若是delimiters不存在則 用默認指令 {{}},若是修改爲其餘指令則用其餘指令 if (!tagRE.test(text)) { //判斷字符串是否含有指令 return } var tokens = []; var rawTokens = []; var lastIndex = tagRE.lastIndex = 0; var match, index, tokenValue; while ((match = tagRE.exec(text))) { //循環能匹配上的指令,全局匹配代碼:的時候會有個lastIndex 執行exec方法後,lastIndex就會記錄匹配的字符串在原始字符串中最後一位的索引加一, console.log('match=') console.log(match) console.log('match.index=' + match.index) console.log('lastIndex=' + lastIndex) index = match.index; //當前匹配上的字符串位置,也能夠是上一次匹配出來的位置 // push text token if (index > lastIndex) { // rawTokens.push(tokenValue = text.slice(lastIndex, index)); //截取匹配到字符串指令前面的字符串,並添加到rawTokens tokens.push(JSON.stringify(tokenValue)); //添加匹配到字符串指令前面的字符串 } // tag token //處理value 解析成正確的value,把過濾器 轉換成vue 虛擬dom的解析方法函數 好比把過濾器 ' ab | c | d' 轉換成 _f("d")(_f("c")(ab)) var exp = parseFilters(match[1].trim()); // tokens.push(("_s(" + exp + ")")); //把指令轉義成函數,便於vonde 虛擬dom 渲染 好比指令{{name}} 轉換成 _s(name) rawTokens.push({'@binding': exp}); //綁定指令{{name}} 指令轉換成 [{@binding: "name"}] lastIndex = index + match[0].length; // 上一次匹配出來的字符串的位置+上一次字符串的長度 好比字符串 我叫{{name}},今年{{age}},數據{{data.number}}個手機 這時候lastIndex 等於10 } console.log(lastIndex) console.log(text.length) if (lastIndex < text.length) { //拼接最後一個字符, 數據{{data.number}}個手機 把個手機 的字符串鏈接起來 rawTokens.push(tokenValue = text.slice(lastIndex)); //截取字符串。到最後一位 tokens.push(JSON.stringify(tokenValue)); //拼接最後一位字符串 } return { expression: tokens.join('+'), //把數組變成字符串,用加號連接 好比數組爲 ['我叫','_s(name)',',今年','_s(age)',',數據','_s(data.number)','個手機'] 變成 我叫+_s(name)+,今年+_s(age)+,數據+_s(data.number)+個手機 tokens: rawTokens } } console.log(parseText('我叫{{name}},今年{{age}},數據{{data.number}}個手機')) // console.log(parseText('{{name}}這個')) /* * 獲取 class 屬性和:class或者v-bind的動態屬性值,而且轉化成字符串 添加到staticClass和classBinding 屬性中 * */ function transformNode( el, //虛擬dom vonde options //用戶 new Vue 的參數 ) { var warn = options.warn || baseWarn; //警告日誌 var staticClass = getAndRemoveAttr(el, 'class'); //獲取class if ("development" !== 'production' && staticClass) { //匹配view 指令,而且把他轉換成 虛擬dom vonde 須要渲染的函數,好比指令{{name}}轉換成 _s(name) var res = parseText( staticClass, //class 屬性值 options.delimiters //指令 {{ }} 或者自定義指令['${', '}'] ); //若是在靜態的class中有動態 指令的話 則發出警告 //當用戶設置 class="{ active: isActive }" data={ active:true}, 應該用戶是否是忘記加 : 點了 if (res) { warn( "class=\"" + staticClass + "\": " + 'Interpolation inside attributes has been removed. ' + 'Use v-bind or the colon shorthand instead. For example, ' + 'instead of <div class="{{ val }}">, use <div :class="val">.' ); } } if (staticClass) { //獲取原始class屬性的值 轉化成字符串 el.staticClass = JSON.stringify(staticClass); } //獲取 :class或者v-bind的動態屬性值 var classBinding = getBindingAttr(el, 'class', false /* getStatic */); if (classBinding) { el.classBinding = classBinding; } } //創數據,轉換class function genData(el) { var data = ''; if (el.staticClass) { //el.staticClass 好比咱們設置樣式是這樣 class="classA classB" 此時將數據變成 staticClass:classA classB, data += "staticClass:" + (el.staticClass) + ","; } if (el.classBinding) { //el.staticClass 好比咱們設置樣式是這樣 class="classC classD" 此時將數據變成 class:classC classD, data += "class:" + (el.classBinding) + ","; } return data } var klass$1 = { staticKeys: ['staticClass'], transformNode: transformNode, genData: genData } /* transformNode$1獲取 style屬性和:style或者v-bind的動態屬性值,而且轉化成字符串 添加到staticStyle和styleBinding屬性中 * */ function transformNode$1(el, options) { var warn = options.warn || baseWarn; var staticStyle = getAndRemoveAttr(el, 'style'); if (staticStyle) { /* istanbul ignore if */ { //匹配view 指令,而且把他轉換成 虛擬dom vonde 須要渲染的函數,好比指令{{name}}轉換成 _s(name) var res = parseText(staticStyle, options.delimiters); //若是在靜態的class中有動態 指令的話 則發出警告 //當用戶設置 style="{ width: num }" data={ num:'100px'}, 應該用戶是否是忘記加 : 點了 if (res) { warn( "style=\"" + staticStyle + "\": " + 'Interpolation inside attributes has been removed. ' + 'Use v-bind or the colon shorthand instead. For example, ' + 'instead of <div style="{{ val }}">, use <div :style="val">.' ); } } //把style 字符串 轉換成對象 好比'width:100px;height:200px;' 轉化成 {width:100px,height:200px} // 而後在轉換成字符串 el.staticStyle = JSON.stringify(parseStyleText(staticStyle)); } var styleBinding = getBindingAttr(el, 'style', false /* getStatic */); if (styleBinding) { el.styleBinding = styleBinding; } } //style 數據轉換 function genData$1(el) { var data = ''; if (el.staticStyle) { //好比staticStyle的值是 {width:100px,height:200px} 轉換成 staticStyle:{width:100px,height:200px}, data += "staticStyle:" + (el.staticStyle) + ","; } if (el.styleBinding) { //好比style的值是 {width:100px,height:200px} 轉換成 style:(width:100px,height:200px), data += "style:(" + (el.styleBinding) + "),"; } return data } var style$1 = { staticKeys: ['staticStyle'], transformNode: transformNode$1, genData: genData$1 } /* */ var decoder; //獲取html文本內容 var he = { decode: function decode(html) { decoder = decoder || document.createElement('div'); decoder.innerHTML = html; return decoder.textContent } } /* */ var isUnaryTag = makeMap( 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' + 'link,meta,param,source,track,wbr' ); // Elements that you can, intentionally, leave open // (and which close themselves) var canBeLeftOpenTag = makeMap( 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source' ); // HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3 // Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content /* 判斷標籤是不是 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' + 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' + 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' + 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' + 'title,tr,track' */ var isNonPhrasingTag = makeMap( 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' + 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' + 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' + 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' + 'title,tr,track' ); /** * Not type-checking this file because it's mostly vendor code. */ /*! * HTML Parser By John Resig (ejohn.org) * Modified by Juriy "kangax" Zaytsev * Original code by Erik Arvidsson, Mozilla Public License * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js */ // Regular Expressions for parsing tags and attributes 解析標記和屬性的正則表達式 var attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName // but for Vue templates we can enforce a simple charset var ncname = '[a-zA-Z_][\\w\\-\\.]*'; var qnameCapture = "((?:" + ncname + "\\:)?" + ncname + ")"; // ((?:[a-zA-Z_][\\w\\-\\.]*\\:)?[a-zA-Z_][\\w\\-\\.]*) var startTagOpen = new RegExp(("^<" + qnameCapture)) // 匹配開頭必需是< 後面能夠忽略是任何字符串 ^<((?:[a-zA-Z_][\\w\\-\\.]*\\:)?[a-zA-Z_][\\w\\-\\.]*) var startTagClose = /^\s*(\/?)>/; // 匹配 > 標籤 或者/> 閉合標籤 var endTag = new RegExp(("^<\\/" + qnameCapture + "[^>]*>")); //匹配開頭必需是</ 後面能夠忽略是任何字符串 ^<\\/((?:[a-zA-Z_][\\w\\-\\.]*\\:)?[a-zA-Z_][\\w\\-\\.]*)[^>]*> var doctype = /^<!DOCTYPE [^>]+>/i; //匹配html的頭文件 <!DOCTYPE html> // #7298: escape - to avoid being pased as HTML comment when inlined in page var comment = /^<!\--/; // 匹配 開始字符串爲<!--任何字符串 var conditionalComment = /^<!\[/; //匹配開始爲 <![ 字符串 匹配這樣動態加ie瀏覽器的 字符串 <!--[if IE 8]><link href="ie8only.css" rel="stylesheet"><![endif]--> var IS_REGEX_CAPTURING_BROKEN = false; 'x'.replace(/x(.)?/g, function (m, g) { IS_REGEX_CAPTURING_BROKEN = g === ''; }); // Special Elements (can contain anything) 判斷標籤是是不是script,style,textarea var isPlainTextElement = makeMap('script,style,textarea', true); var reCache = {}; //替換 把 <替換 < , > 替換 > , "替換 ", &替換 & , 替換\n , 替換\t var decodingMap = { '<': '<', '>': '>', '"': '"', '&': '&', ' ': '\n', ' ': '\t' }; var encodedAttr = /&(?:lt|gt|quot|amp);/g; //匹配 <或>或"或& var encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g; //匹配 <或>或"或&或
或	 //判斷標籤是否pre,textarea var isIgnoreNewlineTag = makeMap('pre,textarea', true); //匹配tag標籤是pre,textarea,而且第二個參數的第一個字符是回車鍵 var shouldIgnoreFirstNewline = function (tag, html) { return tag && isIgnoreNewlineTag(tag) && html[0] === '\n'; }; //替換html 中的特殊符號,轉義成js解析的字符串,替換 把 <替換 < , > 替換 > , "替換 ", &替換 & , 替換\n , 替換\t function decodeAttr( value, //標籤中屬性的值 shouldDecodeNewlines //狀態布爾值 標誌。判斷是不是a標籤和是ie瀏覽器仍是谷歌瀏覽器 ) { console.log(value) console.log(shouldDecodeNewlines) var re = shouldDecodeNewlines ? encodedAttrWithNewLines : //匹配 <或>或"或&或
或	 encodedAttr; //匹配 <或>或"或& //替換html 中的特殊符號,轉義成js解析的字符串 return value.replace(re, function (match) { // 替換 把 <替換 < , > 替換 > , "替換 ", &替換 & , 替換\n , 替換\t return decodingMap[match]; }) } function parseHTML( html, //字符串模板 options //參數 ) { var stack = []; // parseHTML 節點標籤堆棧 var expectHTML = options.expectHTML; //true var isUnaryTag$$1 = options.isUnaryTag || no; //函數匹配標籤是不是 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen, link,meta,param,source,track,wbr' var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no; //函數 //判斷標籤是不是 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source' var index = 0; var last, // lastTag; // console.log(html) while (html) { //循環html last = html; // // Make sure we're not in a plaintext content element like script/style 確保咱們不在像腳本/樣式這樣的純文本內容元素中 if ( !lastTag || //lastTag 不存在 !isPlainTextElement(lastTag) // 若是標籤不是script,style,textarea ) { var textEnd = html.indexOf('<'); //匹配開始標籤或者結束標籤的位置 if (textEnd === 0) { //標識是開始標籤 // Comment: if (comment.test(html)) { //匹配 開始字符串爲<!--任何字符串,註釋標籤 若是匹配上 var commentEnd = html.indexOf('-->'); //獲取註釋標籤的結束位置 if (commentEnd >= 0) { //若是註釋標籤結束標籤位置大於0,則有註釋內容 console.log(html.substring(4, commentEnd)) if (options.shouldKeepComment) { //shouldKeepComment爲真時候。獲取註釋標籤內容 //截取註釋標籤的內容 options.comment(html.substring(4, commentEnd)); } //截取字符串從新循環 while 跳出循環就是靠該函數,每次匹配到以後就截取掉字符串,知道最後一個標籤被截取完沒有匹配到則跳出循環 advance(commentEnd + 3); continue } } //這裏思路是先匹配到註釋節點,在匹配到這裏的ie瀏覽器加載樣式節點 // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment if (conditionalComment.test(html)) { //匹配開始爲 <![ 字符串 <![endif]--> 匹配這樣動態加ie瀏覽器的 字符串 <!--[if IE 8]><link href="ie8only.css" rel="stylesheet"><![endif]--> //匹配ie瀏覽器動態加樣式結束符號 var conditionalEnd = html.indexOf(']>'); if (conditionalEnd >= 0) { //截取字符串從新循環 while 跳出循環就是靠該函數,每次匹配到以後就截取掉字符串,知道最後一個標籤被截取完沒有匹配到則跳出循環 advance(conditionalEnd + 2); continue } } // Doctype: //匹配html的頭文件 <!DOCTYPE html> var doctypeMatch = html.match(doctype); if (doctypeMatch) { //截取字符串從新循環 while 跳出循環就是靠該函數,每次匹配到以後就截取掉字符串,知道最後一個標籤被截取完沒有匹配到則跳出循環 advance(doctypeMatch[0].length); continue } // End tag: //匹配開頭必需是</ 後面能夠忽略是任何字符串 ^<\\/((?:[a-zA-Z_][\\w\\-\\.]*\\:)?[a-zA-Z_][\\w\\-\\.]*)[^>]*> var endTagMatch = html.match(endTag); if (endTagMatch) { var curIndex = index; //標籤分隔函數 while 跳出循環就是靠該函數,每次匹配到以後就截取掉字符串,知道最後一個標籤被截取完沒有匹配到則跳出循環 advance(endTagMatch[0].length); console.log(endTagMatch) console.log(curIndex,index) //查找parseHTML的stack棧中與當前tagName標籤名稱相等的標籤, //調用options.end函數,刪除當前節點的子節點中的最後一個若是是空格或者空的文本節點則刪除, //爲stack出棧一個當前標籤,爲currentParent變量獲取到當前節點的父節點 parseEndTag( endTagMatch[1], curIndex, index ); continue } // Start tag: //解析開始標記 標記開始標籤 // 獲取開始標籤的名稱,屬性集合,開始位置和結束位置,而且返回該對象 var startTagMatch = parseStartTag(); if (startTagMatch) { //把數組對象屬性值循環變成對象,這樣能夠過濾相同的屬性 //爲parseHTML 節點標籤堆棧 插入一個桟數據 //調用options.start 爲parse函數 stack標籤堆棧 添加一個標籤 handleStartTag(startTagMatch); //匹配tag標籤是pre,textarea,而且第二個參數的第一個字符是回車鍵 if (shouldIgnoreFirstNewline(lastTag, html)) { //去除回車鍵空格 advance(1); } continue } } var text = (void 0), rest = (void 0), next = (void 0); if (textEnd >= 0) { rest = html.slice(textEnd); //截取字符串 var textEnd = html.indexOf('<'); //匹配開始標籤或者結束標籤的位置 console.log(rest) while ( !endTag.test(rest) && //匹配開頭必需是</ 後面能夠忽略是任何字符串 !startTagOpen.test(rest) && // 匹配開頭必需是< 後面能夠忽略是任何字符串 !comment.test(rest) && // 匹配 開始字符串爲<!--任何字符串 !conditionalComment.test(rest) //匹配開始爲 <![ 字符串 ) { console.log(rest); // < in plain text, be forgiving and treat it as text // <在純文本中,要寬容,把它看成文原本對待 next = rest.indexOf('<', 1); //匹配是否有多個< if (next < 0) { break } textEnd += next; //截取 索引位置 rest = html.slice(textEnd); //獲取 < 字符串 < 獲取他們兩符號< 之間的字符串 } text = html.substring(0, textEnd); //截取字符串 前面字符串到 < //while 跳出循環就是靠該函數,每次匹配到以後就截取掉字符串,知道最後一個標籤被截取完沒有匹配到則跳出循環 advance(textEnd); } if (textEnd < 0) { //都沒有匹配到 < 符號 則表示純文本 text = html; //出來text html = ''; //把html至空 跳槽 while循環 } if (options.chars && text) { options.chars(text); } } else { // 處理是script,style,textarea var endTagLength = 0; var stackedTag = lastTag.toLowerCase(); var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i')); var rest$1 = html.replace(reStackedTag, function (all, text, endTag) { endTagLength = endTag.length; if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') { text = text .replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298 .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1'); } //匹配tag標籤是pre,textarea,而且第二個參數的第一個字符是回車鍵 if (shouldIgnoreFirstNewline(stackedTag, text)) { text = text.slice(1); } if (options.chars) { options.chars(text); } return '' }); index += html.length - rest$1.length; html = rest$1; parseEndTag(stackedTag, index - endTagLength, index); } if (html === last) { options.chars && options.chars(html); if ("development" !== 'production' && !stack.length && options.warn) { options.warn(("Mal-formatted tag at end of template: \"" + html + "\"")); } break } } // Clean up any remaining tags //查找parseHTML的stack棧中與當前tagName標籤名稱相等的標籤, //調用options.end函數,刪除當前節點的子節點中的最後一個若是是空格或者空的文本節點則刪除, //爲stack出棧一個當前標籤,爲currentParent變量獲取到當前節點的父節點 parseEndTag(); //while 跳出循環就是靠該函數,每次匹配到以後就截取掉字符串,知道最後一個標籤被截取完沒有匹配到則跳出循環 function advance(n) { index += n; //讓索引疊加 html = html.substring(n); //截取當前索引 和 後面的字符串。 } //獲取開始標籤的名稱,收集屬性集合,開始位置和結束位置,而且返回該對象 function parseStartTag() { var start = html.match(startTagOpen); //匹配開始標籤 匹配開頭必需是< 後面能夠忽略是任何字符串 ^<((?:[a-zA-Z_][\\w\\-\\.]*\\:)?[a-zA-Z_][\\w\\-\\.]*) console.log(start) console.log(start[0].length) if (start) { var match = { tagName: start[1], //標籤名稱 attrs: [], //標籤屬性集合 start: index //標籤的開始索引 }; //標記開始標籤的位置,截取了開始標籤 advance(start[0].length); var end, attr; while ( !(end = html.match(startTagClose)) //沒有到 關閉標籤 > 標籤 && (attr = html.match(attribute)) //收集屬性 ) { console.log(html) //截取屬性標籤 advance(attr[0].length); match.attrs.push(attr); //把屬性收集到一個集合 } if (end) { match.unarySlash = end[1]; //若是是/>標籤 則unarySlash 是/。 若是是>標籤 則unarySlash 是空 console.log(end) //截取掉開始標籤,而且更新索引 advance(end[0].length); match.end = index; //開始標籤的結束位置 return match } } } //把數組對象屬性值循環變成對象,這樣能夠過濾相同的屬性 //爲parseHTML 節點標籤堆棧 插入一個桟數據 //調用options.start 爲parse函數 stack標籤堆棧 添加一個標籤 function handleStartTag(match) { /* * match = { tagName: start[1], //標籤名稱 attrs: [], //標籤屬性集合 start: index, //開始標籤的開始索引 match:index , //開始標籤的 結束位置 unarySlash:'' //若是是/>標籤 則unarySlash 是/。 若是是>標籤 則unarySlash 是空 }; * */ var tagName = match.tagName; //開始標籤名稱 var unarySlash = match.unarySlash; //若是是/>標籤 則unarySlash 是/。 若是是>標籤 則unarySlash 是空 console.log(expectHTML) console.log('lastTag==') console.log(lastTag) console.log(tagName) if (expectHTML) { //true if ( lastTag === 'p' //上一個標籤是p /* 判斷標籤是不是 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' + 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' + 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' + 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' + 'title,tr,track' */ && isNonPhrasingTag(tagName) ) { //查找parseHTML的stack棧中與當前tagName標籤名稱相等的標籤, //調用options.end函數,刪除當前節點的子節點中的最後一個若是是空格或者空的文本節點則刪除, //爲stack出棧一個當前標籤,爲currentParent變量獲取到當前節點的父節點 parseEndTag(lastTag); } if ( canBeLeftOpenTag$$1(tagName) && //判斷標籤是不是 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source' lastTag === tagName //上一個標籤和如今標籤相同 <li><li> 編譯成 <li></li> 可是這種狀況是不會出現的 由於瀏覽器解析的時候會自動補全若是是<li>我是li標籤<li> 瀏覽器自動解析成 <li>我是li標籤</li><li> </li> ) { //查找parseHTML的stack棧中與當前tagName標籤名稱相等的標籤, //調用options.end函數,刪除當前節點的子節點中的最後一個若是是空格或者空的文本節點則刪除, //爲stack出棧一個當前標籤,爲currentParent變量獲取到當前節點的父節點 parseEndTag(tagName); } } var unary = isUnaryTag$$1(tagName) || //函數匹配標籤是不是 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen, link,meta,param,source,track,wbr' !!unarySlash; //若是是/> 則爲真 var l = match.attrs.length; var attrs = new Array(l); //數組屬性對象轉換正真正的數組對象 for (var i = 0; i < l; i++) { var args = match.attrs[i]; //獲取屬性對象 // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778 //對FF bug進行黑客攻擊:https://bugzilla.mozilla.org/show_bug.cgi?id=369778 if ( IS_REGEX_CAPTURING_BROKEN && //這個應該是 火狐瀏覽器私有 標誌 args[0].indexOf('""') === -1 ) { if (args[3] === '') { delete args[3]; } if (args[4] === '') { delete args[4]; } if (args[5] === '') { delete args[5]; } } var value = args[3] || args[4] || args[5] || ''; var shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' ? options.shouldDecodeNewlinesForHref // true chrome在a[href]中編碼內容 : options.shouldDecodeNewlines; //flase //IE在屬性值中編碼換行,而其餘瀏覽器則不會 attrs[i] = { //把數組對象屬性值循環變成對象,這樣能夠過濾相同的屬性 name: args[1], //屬性名稱 //屬性值 value: decodeAttr(value, shouldDecodeNewlines) //替換html 中的特殊符號,轉義成js解析的字符串,替換 把 <替換 < , > 替換 > , "替換 ", &替換 & , 替換\n , 替換\t }; } console.log('==!unary==') console.log(!unary) if (!unary) { //若是不是單標籤 // 爲parseHTML 節點標籤堆棧 插入一個桟數據 stack.push({ //標籤堆棧 tag: tagName, //開始標籤名稱 lowerCasedTag: tagName.toLowerCase(), //變成小寫記錄標籤 attrs: attrs //獲取屬性 }); //設置結束標籤 lastTag = tagName; console.log('== parseHTML handleStartTag stack==') console.log(stack) } // if (options.start) { //標籤開始函數, 建立一個ast標籤dom, 判斷獲取v-for屬性是否存在若是有則轉義 v-for指令 把for,alias,iterator1,iterator2屬性添加到虛擬dom中 //獲取v-if屬性,爲el虛擬dom添加 v-if,v-eles,v-else-if 屬性 //獲取v-once 指令屬性,若是有有該屬性 爲虛擬dom標籤 標記事件 只觸發一次則銷燬 //校驗屬性的值,爲el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 標誌 屬性 // 標誌當前的currentParent當前的 element //爲parse函數 stack標籤堆棧 添加一個標籤 options.start( tagName, //標籤名稱 attrs, //標籤屬性 unary, // 若是不是單標籤則爲真 match.start, //開始標籤的開始位置 match.end //開始標籤的結束的位置 ); } } //查找parseHTML的stack棧中與當前tagName標籤名稱相等的標籤, //調用options.end函數,刪除當前節點的子節點中的最後一個若是是空格或者空的文本節點則刪除, //爲stack出棧一個當前標籤,爲currentParent變量獲取到當前節點的父節點 function parseEndTag( tagName, //標籤名稱 start, //結束標籤開始位置 end //結束標籤結束位置 ) { var pos, lowerCasedTagName; if (start == null) { //若是沒有傳開始位置 start = index; //就那當前索引 } if (end == null) { //若是沒有傳結束位置 end = index; //就那當前索引 } if (tagName) { //結束標籤名稱 lowerCasedTagName = tagName.toLowerCase(); //將字符串轉化成小寫 } // Find the closest opened tag of the same type 查找最近打開的相同類型的標記 if (tagName) { // 獲取stack堆棧最近的匹配標籤 for (pos = stack.length - 1; pos >= 0; pos--) { //找到最近的標籤相等 if (stack[pos].lowerCasedTag === lowerCasedTagName) { break } } } else { // If no tag name is provided, clean shop //若是沒有提供標籤名稱,請清理商店 pos = 0; } if (pos >= 0) { //這裏就獲取到了stack堆棧的pos索引 // Close all the open elements, up the stack 關閉全部打開的元素,向上堆棧 console.log(pos) for (var i = stack.length - 1; i >= pos; i--) { if ("development" !== 'production' && //若是stack中找不到tagName 標籤的時候就輸出警告日誌,找不到標籤 (i > pos || !tagName) && options.warn ) { options.warn( ("tag <" + (stack[i].tag) + "> has no matching end tag.") ); } if (options.end) { console.log(options.end) //調用options.end函數,刪除當前節點的子節點中的最後一個若是是空格或者空的文本節點則刪除, //爲stack出棧一個當前標籤,爲currentParent變量獲取到當前節點的父節點 options.end( stack[i].tag,//結束標籤名稱 start, //結束標籤開始位置 end //結束標籤結束位置 ); } } // Remove the open elements from the stack //從堆棧中刪除打開的元素 // console.log(stack[pos].tag) // 爲parseHTML 節點標籤堆棧 出桟當前匹配到的標籤 stack.length = pos; //獲取到上一個標籤,就是當前節點的父節點 lastTag = pos && stack[pos - 1].tag; console.log(stack) console.log(lastTag) } else if (lowerCasedTagName === 'br') { if (options.start) { //標籤開始函數, 建立一個ast標籤dom, 判斷獲取v-for屬性是否存在若是有則轉義 v-for指令 把for,alias,iterator1,iterator2屬性添加到虛擬dom中 //獲取v-if屬性,爲el虛擬dom添加 v-if,v-eles,v-else-if 屬性 //獲取v-once 指令屬性,若是有有該屬性 爲虛擬dom標籤 標記事件 只觸發一次則銷燬 //校驗屬性的值,爲el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 標誌 屬性 // 標誌當前的currentParent當前的 element //爲parse函數 stack標籤堆棧 添加一個標籤 options.start( tagName, [], true, start, end ); } } else if (lowerCasedTagName === 'p') { if (options.start) { //標籤開始函數, 建立一個ast標籤dom, 判斷獲取v-for屬性是否存在若是有則轉義 v-for指令 把for,alias,iterator1,iterator2屬性添加到虛擬dom中 //獲取v-if屬性,爲el虛擬dom添加 v-if,v-eles,v-else-if 屬性 //獲取v-once 指令屬性,若是有有該屬性 爲虛擬dom標籤 標記事件 只觸發一次則銷燬 //校驗屬性的值,爲el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 標誌 屬性 // 標誌當前的currentParent當前的 element //爲parse函數 stack標籤堆棧 添加一個標籤 options.start( tagName, [], false, start, end); } if (options.end) { //刪除當前節點的子節點中的最後一個若是是空格或者空的文本節點則刪除, //爲stack出棧一個當前標籤,爲currentParent變量獲取到當前節點的父節點 options.end( tagName, start, end ); } } console.log(lastTag) } } /* */ var onRE = /^@|^v-on:/;//判斷是不是 @或者v-on:屬性開頭的 var dirRE = /^v-|^@|^:/; //判斷是不是 v-或者@或者: 屬性開頭的 var forAliasRE = /([^]*?)\s+(?:in|of)\s+([^]*)/; //匹配 含有 字符串 in 字符串 或者 字符串 of 字符串 var forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/; //匹配上, 可是屬於兩邊是 [{ , 點 , }] 因此匹配上 ,+字符串 var stripParensRE = /^\(|\)$/g; //匹配括號 () var argRE = /:(.*)$/; //匹配字符串是否含有: var bindRE = /^:|^v-bind:/; //開始匹配是 :或者是v-bind var modifierRE = /\.[^.]+/g; // 匹配以點開頭的分組 不屬於點 data.object.info.age 匹配到 ['.object','.info' , '.age'] var decodeHTMLCached = cached(he.decode); //獲取 真是dom的textContent文本 // configurable state var warn$2; //日誌輸出函數 var delimiters; //改變純文本插入分隔符。修改指令的書寫風格,好比默認是{{mgs}} delimiters: ['${', '}']以後變成這樣 ${mgs} var transforms; //transforms 樣式屬性的集合 函數 var preTransforms;//transforms arr屬性的集合 函數 var postTransforms; //空數組 var platformIsPreTag; // 判斷標籤是不是pre 若是是則返回真 var platformMustUseProp; // 校驗特定的屬性方法 var platformGetTagNamespace; //判斷 tag 是不是svg或者math 標籤 //轉換屬性,把數組屬性轉換成對象屬性,返回對象 AST元素 function createASTElement(tag, //標籤名稱 attrs, //屬性 parent //父層 ) { return { type: 1, //dom 類型 tag: tag, //標籤 attrsList: attrs, //數組屬性 attrsMap: makeAttrsMap(attrs), //對象屬性 把數組對象轉換成 對象 例如attrs = [{name:tag1,value:1},{ name:tag2,value:2},{name:tag3,value:3}]轉換成map={tag1:1,tag2:2,tag3:3} parent: parent, //父層 children: [] } } /** * Convert HTML string to AST. * 將HTML字符串轉換爲AST。 */ function parse( template, //html 模板 options ) { warn$2 = options.warn || baseWarn; //警告日誌函數 platformIsPreTag = options.isPreTag || no; // 判斷標籤是不是pre 若是是則返回真 /* mustUseProp 校驗屬性 * 1. attr === 'value', tag 必須是 'input,textarea,option,select,progress' 其中一個 type !== 'button' * 2. attr === 'selected' && tag === 'option' * 3. attr === 'checked' && tag === 'input' * 4. attr === 'muted' && tag === 'video' * 的狀況下爲真 * */ platformMustUseProp = options.mustUseProp || no; platformGetTagNamespace = options.getTagNamespace || no; //判斷 tag 是不是svg或者math 標籤 //baseOptions中的modules參數爲 // modules=modules$1=[ // { // class 轉換函數 // staticKeys: ['staticClass'], // transformNode: transformNode, // genData: genData // }, // { //style 轉換函數 // staticKeys: ['staticStyle'], // transformNode: transformNode$1, // genData: genData$1 // }, // { // preTransformNode: preTransformNode // } // ] //循環過濾數組或者對象的值,根據key循環 過濾對象或者數組[key]值,若是不存在則丟棄,若是有相同多個的key值,返回多個值的數組 transforms = pluckModuleFunction(options.modules, 'transformNode'); //循環過濾數組或者對象的值,根據key循環 過濾對象或者數組[key]值,若是不存在則丟棄,若是有相同多個的key值,返回多個值的數組 preTransforms = pluckModuleFunction(options.modules, 'preTransformNode'); //循環過濾數組或者對象的值,根據key循環 過濾對象或者數組[key]值,若是不存在則丟棄,若是有相同多個的key值,返回多個值的數組 postTransforms = pluckModuleFunction(options.modules, 'postTransformNode'); console.log('==options==') console.log(options) /* 拿到 key transforms值的函數 * transforms=[ transformNode, //函數 獲取 class 屬性和:class或者v-bind的動態屬性值,而且轉化成字符串 添加到staticClass和classBinding 屬性中 transformNode$1 //函數 transformNode$1獲取 style屬性和:style或者v-bind的動態屬性值,而且轉化成字符串 添加到staticStyle和styleBinding屬性中 * ] * */ console.log('==transforms==') console.log(transforms) /* 拿到 key preTransforms值的函數 * preTransforms=[ preTransformNode // preTransformNode把attrsMap與attrsList屬性值轉換添加到el ast虛擬dom中爲虛擬dom添加for,alias,iterator1,iterator2, addRawAttr ,type ,key, ref,slotName或者slotScope或者slot,component或者inlineTemplate , plain,if ,else,elseif 屬性 * ] * */ console.log('==preTransforms==') console.log(preTransforms) /* 拿到 key postTransforms值的函數 * postTransforms=[ 爲空 * ] * */ console.log('==postTransforms==') console.log(postTransforms) //改變純文本插入分隔符。修改指令的書寫風格,好比默認是{{mgs}} delimiters: ['${', '}']以後變成這樣 ${mgs} delimiters = options.delimiters; var stack = []; // parse函數 標籤堆棧 var preserveWhitespace = options.preserveWhitespace !== false; //模板編譯器的選項。當使用默認的 vue-template-compiler 的時候,你可使用這個選項來添加自定義編譯器指令、模塊或經過 { preserveWhitespace: false } 放棄模板標籤之間的空格。 var root; var currentParent; //當前父節點 var inVPre = false; //標記 標籤是否還有 v-pre 指令,若是沒有則是false var inPre = false; // 判斷標籤是不是pre 若是是則返回真 var warned = false; console.log(currentParent) function warnOnce(msg) { if (!warned) { warned = true; warn$2(msg); //警告日誌函數 } } //克隆節點 function closeElement(element) { // check pre state if (element.pre) { //判斷標籤是否還有 v-pre 指令 inVPre = false; //標記 標籤是否還有 v-pre 指令,若是沒有則是false } if (platformIsPreTag(element.tag)) { // 判斷標籤是不是pre 若是是則返回真 inPre = false; // 判斷標籤是不是pre 若是是則返回真 } console.log(postTransforms) // apply post-transforms 應用轉化後 postTransforms數組爲空因此不執行這裏 for (var i = 0; i < postTransforms.length; i++) { postTransforms[i](element, options); } } console.log(currentParent) parseHTML( template, //字符串模板 { warn: warn$2, //警告日誌函數 expectHTML: options.expectHTML, //標誌是html 是true isUnaryTag: options.isUnaryTag, //匹配標籤是不是 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen, link,meta,param,source,track,wbr' canBeLeftOpenTag: options.canBeLeftOpenTag, //判斷標籤是不是 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source' shouldDecodeNewlines: options.shouldDecodeNewlines, //flase //IE在屬性值中編碼換行,而其餘瀏覽器則不會 shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref, // true chrome在a[href]中編碼內容 shouldKeepComment: options.comments, //當設爲 true 時,將會保留且渲染模板中的 HTML 註釋。默認行爲是捨棄它們。 //標籤開始函數, 建立一個ast標籤dom, 判斷獲取v-for屬性是否存在若是有則轉義 v-for指令 把for,alias,iterator1,iterator2屬性添加到虛擬dom中 //獲取v-if屬性,爲el虛擬dom添加 v-if,v-eles,v-else-if 屬性 //獲取v-once 指令屬性,若是有有該屬性 爲虛擬dom標籤 標記事件 只觸發一次則銷燬 //校驗屬性的值,爲el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 標誌 屬性 // 標誌當前的currentParent當前的 element //爲parse函數 stack標籤堆棧 添加一個標籤 start: function start( tag, //標籤名稱 attrs, //標籤屬性 unary // 若是不是單標籤則爲真 ) { // check namespace. 檢查名稱空間。 // inherit parent ns if there is one 若是有,繼承父ns var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);//判斷 tag 是不是svg或者math 標籤 // handle IE svg bug /* istanbul ignore if */ if (isIE && ns === 'svg') { //若是是ie瀏覽器 而且是 svg //防止ie瀏覽器 svu 的 bug 替換屬性含有NS+數字 去除 NS+數字 attrs = guardIESVGBug(attrs); } //轉換屬性,把數組屬性轉換成對象屬性,返回對象 AST元素 console.log(currentParent) //建立一個ast標籤dom var element = createASTElement(tag, attrs, currentParent); if (ns) { //判斷 tag 是不是svg或者math 標籤 element.ns = ns; } if ( isForbiddenTag(element) && //若是是style或者是是script 標籤而且type屬性不存在 或者存在而且是javascript 屬性 的時候返回真 !isServerRendering() //不是在服務器node環境下 ) { element.forbidden = true; "development" !== 'production' && warn$2( 'Templates should only be responsible for mapping the state to the ' + 'UI. Avoid placing tags with side-effects in your templates, such as ' + "<" + tag + ">" + ', as they will not be parsed.' ); } // apply pre-transforms transforms arr屬性的集合 函數 for (var i = 0; i < preTransforms.length; i++) { //transforms arr屬性的集合 函數 // preTransformNode把attrsMap與attrsList屬性值轉換添加到el ast虛擬dom中爲虛擬dom添加for,alias,iterator1,iterator2, addRawAttr ,type ,key, ref,slotName或者slotScope或者slot,component或者inlineTemplate , plain,if ,else,elseif 屬性 element = preTransforms[i](element, options) || element; } console.log(element) if (!inVPre) { //若是 標籤 沒有 v-pre 指令 processPre(element); //檢查標籤是否有v-pre 指令 含有 v-pre 指令的標籤裏面的指令則不會被編譯 if (element.pre) { //標記 標籤是否還有 v-pre 指令 inVPre = true; //若是標籤有v-pre 指令 則標記爲true } } if (platformIsPreTag(element.tag)) { // 判斷標籤是不是pre 若是是則返回真 inPre = true; } if (inVPre) { //若是含有 v-pre 指令 //淺拷貝屬性 把虛擬dom的attrsList拷貝到attrs中,若是沒有pre塊,標記plain爲true processRawAttrs(element); } else if (!element.processed) { // structural directives 指令 //判斷獲取v-for屬性是否存在若是有則轉義 v-for指令 把for,alias,iterator1,iterator2屬性添加到虛擬dom中 processFor(element); //獲取v-if屬性,爲el虛擬dom添加 v-if,v-eles,v-else-if 屬性 processIf(element); //獲取v-once 指令屬性,若是有有該屬性 爲虛擬dom標籤 標記事件 只觸發一次則銷燬 processOnce(element); // element-scope stuff //校驗屬性的值,爲el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 標誌 屬性 processElement(element, options); } console.log(element) //檢查根約束 根節點不能是slot或者template標籤,而且不能含有v-for 屬性 function checkRootConstraints(el) { { if (el.tag === 'slot' || el.tag === 'template') { warnOnce( "Cannot use <" + (el.tag) + "> as component root element because it may " + 'contain multiple nodes.' ); } if (el.attrsMap.hasOwnProperty('v-for')) { warnOnce( 'Cannot use v-for on stateful component root element because ' + 'it renders multiple elements.' ); } } } // tree management if (!root) { root = element; //檢查根約束 根節點不能是slot或者template標籤,而且不能含有v-for 屬性 checkRootConstraints(root); } else if (!stack.length) { // allow root elements with v-if, v-else-if and v-else //容許根元素帶有v-if、v-else-if和v-else if (root.if && (element.elseif || element.else)) { checkRootConstraints(element);//檢查根約束 根節點不能是slot或者template標籤,而且不能含有v-for 屬性 //爲if指令添加標記 addIfCondition( root, //根節點 { exp: element.elseif, //view 試圖中的elseif 屬性 block: element //當前的虛擬dom } ); } else { warnOnce( "Component template should contain exactly one root element. " + "If you are using v-if on multiple elements, " + "use v-else-if to chain them instead." ); } } //若是currentParent父節點存在。而且element.forbidden不存在 if ( currentParent && !element.forbidden //若是是style或者是是script 標籤而且type屬性不存在 或者存在而且是javascript 屬性 的時候返回真 ) { if (element.elseif || element.else) { //若是有elseif或者else屬性的時候 //找到上一個兄弟節點,若是上一個兄弟節點是if,則下一個兄弟節點則是elseif processIfConditions(element, currentParent); } else if (element.slotScope) { // scoped slot 做用域的槽 currentParent.plain = false; //獲取slotTarget做用域標籤,若是獲取不到則定義爲default var name = element.slotTarget || '"default"'; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element; } else { //若是父節點存在currentParent則在父節點添加一個子節點,而且 currentParent.children.push(element); //當前節點上添加parent屬性 element.parent = currentParent; } } // var unary = isUnaryTag$$1(tagName) || //函數匹配標籤是不是 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen, link,meta,param,source,track,wbr' // !!unarySlash; //若是是/> 則爲真 //若是當前標籤不是單標籤,也不是閉合標籤,就標誌當前currentParent 是當前標籤 console.log(stack) if (!unary) { currentParent = element; //爲parse函數 stack標籤堆棧 添加一個標籤 stack.push(element); console.log('== start stack==') console.log(stack) } else { //克隆節點 closeElement(element); } console.log('===start===') console.log(stack) }, //刪除當前節點的子節點中的最後一個若是是空格或者空的文本節點則刪除, //爲stack出棧一個當前標籤,爲currentParent變量獲取到當前節點的父節點 end: function end() { console.log('end') // remove trailing whitespace 刪除尾隨空格 //取到棧中最後一位數據 若是標籤是這樣 <div><span><i></i></span></div> 則這裏會先是i 先進後出 //parse函數 標籤堆棧,出棧一個當前標籤,爲currentParent變量獲取到當前節點的父節點 var element = stack[stack.length - 1]; console.log(element) var lastNode = element.children[element.children.length - 1]; if ( lastNode && //判斷子節點最後一個節點 lastNode.type === 3 //文本節點 Text 空格等 屬性內容#text && lastNode.text === ' ' //空格 && !inPre //標誌須要編譯的狀態 ) { element.children.pop(); //刪除空格文本節點 } // pop stack 獲取上一個標籤節點則是他當前節點的父節點 // parse函數 標籤堆棧,出棧一個當前標籤, stack.length -= 1; //獲取當前節點的父節點標籤 currentParent = stack[stack.length - 1]; console.log(stack) //克隆節點 closeElement(element); }, //把text添加到屬性節點或者添加到註釋節點,ast模板數據 chars: function chars(text) { console.log('chars') console.log(currentParent) //判斷是否有當前的父節點 if (!currentParent) { //警告日誌 { if (text === template) { warnOnce( 'Component template requires a root element, rather than just text.' ); } else if ((text = text.trim())) { warnOnce( ("text \"" + text + "\" outside root element will be ignored.") ); } } return } // IE textarea placeholder bug /* istanbul ignore if */ if ( isIE && //若是是ie currentParent.tag === 'textarea' && //若是上一個節點 父節點是textarea currentParent.attrsMap.placeholder === text //若是他的html5 用戶信息提示和當前的文本同樣 ) { return } var children = currentParent.children; // 獲取到同級的兄弟節點 text = inPre || // 判斷標籤是不是pre 若是是則返回真,則不須要去空格 text.trim() ? //去除text空格 (isTextTag(currentParent) ? //判斷標籤是不是script或者是style text//直接獲取文本 : decodeHTMLCached(text)) //獲取 真是dom的textContent文本 // only preserve whitespace if its not right after a starting tag //只有在開始標記以後沒有空格時才保留空格 : preserveWhitespace && //模板編譯器的選項。當使用默認的 vue-template-compiler 的時候,你可使用這個選項來添加自定義編譯器指令、模塊或經過 { preserveWhitespace: false } 放棄模板標籤之間的空格。 children.length ? ' ' : ''; //若是是children.length存在而且preserveWhitespace爲真則保留空格 if (text) { var res; if ( !inVPre && //標記 標籤是否還有 v-pre 指令,若是沒有則是false text !== ' ' && //你是空節點 (res = parseText(text, delimiters) ) //匹配view 指令,而且把他轉換成 虛擬dom vonde 須要渲染的函數,好比指令{{name}}轉換成 _s(name) //好比字符串 我叫{{name}},今年{{age}},數據{{data.number}}個手機 轉換成 我叫+_s(name)+,今年+_s(age)+,數據+_s(data.number)+個手機 ) { console.log(res) children.push({ //添加爲屬性節點 type: 2, //Attr 表明屬性 expression: res.expression, //好比字符串 我叫{{name}},今年{{age}},數據{{data.number}}個手機 轉換成 我叫+_s(name)+,今年+_s(age)+,數據+_s(data.number)+個手機 tokens: res.tokens, text: text //html文本 }); } else if ( //若是當前text不是空的,子節點也不是空的表示是註釋幾點 text !== ' ' || !children.length || children[children.length - 1].text !== ' ' ) { children.push({ type: 3, //註釋節點 text: text }); } } }, //把text添加到屬性節點或者添加到註釋節點,ast模板數據 comment: function comment(text) { console.log('comment') console.log(currentParent) currentParent.children.push({ type: 3, //註釋節點 text: text, isComment: true }); } } ); return root } //檢查標籤是否有v-pre 指令 含有 v-pre 指令的標籤裏面的指令則不會被編譯 function processPre(el) { if (getAndRemoveAttr(el, 'v-pre') != null) { el.pre = true; //標記 標籤是否還有 v-pre 指令 ,若是有則爲真 含有 v-pre 指令的標籤裏面的指令則不會被編譯 } } //淺拷貝屬性 把虛擬dom的attrsList拷貝到attrs中,若是沒有pre塊,標記plain爲true function processRawAttrs(el) { var l = el.attrsList.length; if (l) { var attrs = el.attrs = new Array(l); for (var i = 0; i < l; i++) { attrs[i] = { name: el.attrsList[i].name, value: JSON.stringify(el.attrsList[i].value) }; } } else if (!el.pre) { //標記 標籤是否還有 v-pre 指令 ,若是有則爲真 // non root node in pre blocks with no attributes //沒有屬性的pre塊中的非根節點 el.plain = true; } } //校驗屬性的值,爲el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 標誌 屬性 function processElement(element, options) { //獲取屬性key值,校驗key 是否放在template 標籤上面 爲el 虛擬dom添加 key屬性 processKey(element); // determine whether this is a plain element after // removing structural attributes //肯定這是不是一個普通元素後 //刪除結構屬性 element.plain = !element.key && !element.attrsList.length; //若是沒有key 也沒有屬性 //獲取ref 屬性,而且判斷ref 是否含有v-for指令 爲el虛擬dom 添加 ref 屬性 processRef(element); //檢查插槽做用域 爲el虛擬dom添加 slotName或者slotScope或者slot processSlot(element); // 判斷虛擬dom 是否有 :is屬性,是否有inline-template 內聯模板屬性 若是有則標記下 爲el 虛擬dom 添加component屬性或者inlineTemplate 標誌 processComponent(element); //轉換數據 for (var i = 0; i < transforms.length; i++) { element = transforms[i](element, options) || element; } //檢查屬性,爲虛擬dom屬性轉換成對應須要的虛擬dom vonde數據 爲el虛擬dom 添加muted, events,nativeEvents,directives processAttrs(element); } //獲取屬性key值,校驗key 是否放在template 標籤上面 function processKey(el) { //校驗key 有沒有放在 var exp = getBindingAttr(el, 'key'); if (exp) { if ("development" !== 'production' && el.tag === 'template') { //不能的。把鍵放在真實的template元素上。 warn$2("<template> cannot be keyed. Place the key on real elements instead."); } el.key = exp; } } //獲取ref 屬性,而且判斷ref 是否含有v-for指令 function processRef(el) { //獲取ref 屬性 var ref = getBindingAttr(el, 'ref'); if (ref) { el.ref = ref; //檢查當前虛擬dom vonde 是否有for指令,或者父組件是否有for指令 el.refInFor = checkInFor(el); } } //判斷獲取v-for屬性是否存在若是有則轉義 v-for指令 把for,alias,iterator1,iterator2屬性添加到虛擬dom中 function processFor(el) { var exp; //獲取v-for指令 屬性 if ((exp = getAndRemoveAttr(el, 'v-for'))) { console.log(exp) //轉換 for指令 獲取 for中的key 返回一個res對象爲{for:data字符串,alias:value字符串,iterator1:key字符串,iterator2:index字符串} var res = parseFor(exp); if (res) { //合併淺拷貝到el中 extend(el, res); } else { warn$2( ("Invalid v-for expression: " + exp) ); } } } //轉換 for指令 獲取 for中的key 返回一個res對象爲{for:data字符串,alias:value字符串,iterator1:key字符串,iterator2:index字符串} function parseFor(exp //字符串 列如 是 (item,index) in data 或 item in data 或item of data 或者(item,index) of data ) { var inMatch = exp.match(forAliasRE); //匹配 含有 字符串 in 字符串 或者 字符串 of 字符串 好比(value, key, index) in data if (!inMatch) { //若是匹配不上則返回出去 return } var res = {}; console.log(inMatch) res.for = inMatch[2].trim(); //獲取到數據 data 字符串 var alias = inMatch[1].trim().replace(stripParensRE, ''); //去除括號 好比(value, key, index) in data 變成 value, key, index var iteratorMatch = alias.match(forIteratorRE); // 匹配出分組 [0: ", key, index", 1: " key" , 2: "index"] if (iteratorMatch) { res.alias = alias.replace(forIteratorRE, ''); // value , key , index 去掉 ,+字符串 得到value 字符串 console.log(res.alias) res.iterator1 = iteratorMatch[1].trim(); //獲取第二個字符串 key if (iteratorMatch[2]) { res.iterator2 = iteratorMatch[2].trim(); //獲取第三個字符串 index } } else { res.alias = alias; //單個字符串的時候 value in data } return res } //獲取v-if屬性,爲el虛擬dom添加 v-if,v-eles,v-else-if 屬性 function processIf(el) { var exp = getAndRemoveAttr(el, 'v-if'); //獲取v-if屬性 if (exp) { el.if = exp; addIfCondition(el, { //爲if指令添加標記 exp: exp, block: el }); } else { if (getAndRemoveAttr(el, 'v-else') != null) { el.else = true; } var elseif = getAndRemoveAttr(el, 'v-else-if'); if (elseif) { el.elseif = elseif; } } } //找到上一個兄弟節點,若是上一個兄弟節點是if,則下一個兄弟節點則是elseif function processIfConditions(el, parent) { //找到兄弟節點,上一個兄弟節點。 var prev = findPrevElement(parent.children); if (prev && prev.if) { //上一個節點若是是有if 這個節點標記則是elseif //爲if指令添加標記 addIfCondition( prev, { exp: el.elseif, block: el } ); } else { warn$2( "v-" + (el.elseif ? ('else-if="' + el.elseif + '"') : 'else') + " " + "used on element <" + (el.tag) + "> without corresponding v-if." ); } } //找到上一個節點 function findPrevElement(children) { var i = children.length; while (i--) { if (children[i].type === 1) { return children[i] } else {//若是是其餘節點則刪除 if ("development" !== 'production' && children[i].text !== ' ') { warn$2( "text \"" + (children[i].text.trim()) + "\" between v-if and v-else(-if) " + "will be ignored." ); } children.pop(); } } } //爲if指令添加標記 function addIfCondition( el, // el當前渲染的虛擬組件 condition // 標記的狀態 對象 {exp: view中的if屬性,block: el當前渲染的虛擬組件} ) { if (!el.ifConditions) { // el.ifConditions = []; //存儲隊列 } el.ifConditions.push(condition); //if 指令標記 } //獲取v-once 指令屬性,若是有有該屬性 爲虛擬dom標籤 標記事件 只觸發一次則銷燬 function processOnce(el) { var once$$1 = getAndRemoveAttr(el, 'v-once'); if (once$$1 != null) { el.once = true; } } //檢查插槽做用域 爲el虛擬dom添加 slotName或者slotScope或者slot function processSlot(el) { if (el.tag === 'slot') { //判斷是不是slot插槽 el.slotName = getBindingAttr(el, 'name'); //獲取插槽的name屬性 //若是設置了key 則警告 if ("development" !== 'production' && el.key) { warn$2( "`key` does not work on <slot> because slots are abstract outlets " + "and can possibly expand into multiple elements. " + "Use the key on a wrapping element instead." ); } } else { var slotScope; if (el.tag === 'template') { //若是是模板標籤 slotScope = getAndRemoveAttr(el, 'scope'); //獲取scope屬性值 /* istanbul ignore if */ if ("development" !== 'production' && slotScope) { warn$2( "the \"scope\" attribute for scoped slots have been deprecated and " + "replaced by \"slot-scope\" since 2.5. The new \"slot-scope\" attribute " + "can also be used on plain elements in addition to <template> to " + "denote scoped slots.", true ); } el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope'); //添加slotScope 的做用域 } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) { //獲取slot-scope 做用域屬性 /* istanbul ignore if */ if ("development" !== 'production' && el.attrsMap['v-for']) { warn$2( "Ambiguous combined usage of slot-scope and v-for on <" + (el.tag) + "> " + "(v-for takes higher priority). Use a wrapper <template> for the " + "scoped slot to make it clearer.", true ); } el.slotScope = slotScope;//添加slotScope 的做用域 } var slotTarget = getBindingAttr(el, 'slot'); //獲取slot 屬性 if (slotTarget) { el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget; // preserve slot as an attribute for native shadow DOM compat // only for non-scoped slots. //保留slot做爲本地影子DOM compat的屬性 //只適用於非做用域插槽。 if (el.tag !== 'template' && !el.slotScope) { //添加插槽屬性 addAttr(el, 'slot', slotTarget); } } } } // 判斷虛擬dom 是否有 :is屬性,是否有inline-template 內聯模板屬性 若是有則標記下 爲el 虛擬dom 添加component屬性或者inlineTemplate 標誌 function processComponent(el) { var binding; if ((binding = getBindingAttr(el, 'is'))) { //獲取:is 或者是 v-bind:is 屬性 el.component = binding; //若是有 把他綁定在屬性中 } if (getAndRemoveAttr(el, 'inline-template') != null) { //當 inline-template 這個特殊的特性出如今一個子組件上時,這個組件將會使用其裏面的內容做爲模板,而不是將其做爲被分發的內容。這使得模板的撰寫工做更加靈活。 el.inlineTemplate = true; //標誌有內聯模板 } } //檢查屬性,爲虛擬dom屬性轉換成對應須要的虛擬dom vonde數據 爲el虛擬dom 添加muted, events,nativeEvents,directives function processAttrs(el) { var list = el.attrsList; //獲取屬性列表 var i, //循環數組的索引 l, //屬性數組長度 name, //獲取 view 屬性的名稱 rawName,//獲取 view 屬性的名稱 value, //屬性名 modifiers, isProp; //是不是props 屬性 for (i = 0, l = list.length; i < l; i++) { //循環屬性列表 name = rawName = list[i].name; //獲取 view 屬性的名稱 value = list[i].value; //獲取屬性的值 if (dirRE.test(name)) { // 判斷是不是 v-或者@或者: 屬性開頭的 // mark element as dynamic el.hasBindings = true; // 動態標記元素 // modifiers 編輯器 //把字符串中的對象拆分紅 對象好比 data.object.info.age 變成對象{object:true,info:true,age:true} 返回出去 modifiers = parseModifiers(name); if (modifiers) { //把剛纔後面的.+字符串去除掉 獲取最後一位的key name = name.replace(modifierRE, ''); } if (bindRE.test(name)) { // v-bind 匹配開始匹配是 :或者是v-bind name = name.replace(bindRE, ''); //去除 開始匹配是 :或者是v-bind // 處理value 解析成正確的value,把過濾器 轉換成vue 虛擬dom的解析方法函數 好比把過濾器 ' ab | c | d' 轉換成 _f("d")(_f("c")(ab)) // 表達式中的過濾器解析方法 value = parseFilters(value); isProp = false; if (modifiers) { //匹配到對象點的時候 if (modifiers.prop) {//匹配到有prop屬性的時候 isProp = true; //屬性 v-model 變成 vModel name = camelize(name); //若是是innerHtml屬性變成innerHTML if (name === 'innerHtml') { name = 'innerHTML'; } } if (modifiers.camel) { name = camelize(name); } if (modifiers.sync) { //同步屬性 //爲虛擬dom添加events 事件對象屬性,若是添加@click='clickEvent' 則此時 虛擬dom爲el.events.click.value="clickEvent" //或者虛擬dom添加nativeEvents 事件對象屬性,若是添加@click.native='clickEvent' 則此時 虛擬dom爲el.nativeEvents.click.value="clickEvent" addHandler( el, ("update:" + (camelize(name))), // //屬性 v-model 變成 vModel //建立賦值代碼 // 創賦值代碼,子功能轉義字符串對象拆分字符串對象 把後一位key分離出來 genAssignmentCode( //返回值 函數 value, //對象 "$event" //key ) ); } } if ( isProp //若是是prop屬性 || ( !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name) //校驗特定的屬性方法 )) { //添加props屬性 addProp(el, name, value); } else { //添加普通的屬性 在attrs屬性中 addAttr(el, name, value); } } else if (onRE.test(name)) { // v-on 判斷是不是 @或者v-on:屬性開頭的 name = name.replace(onRE, ''); console.log(name) console.log(value) console.log(modifiers) console.log(false) console.log(warn$2) console.log(el) addHandler( el, //虛擬dom name, //name 事件名稱 事件類型 value, // 事件名稱的值 modifiers, false, warn$2 //警告的日誌 ); console.log(el) } else { // normal directives 正常的指令 //通常也不會進來這裏 由於前面已經匹配了 :或者v-bind @或者v-on:屬性 開頭的,因此進來這裏的就是自定義指令 name = name.replace(dirRE, ''); //判斷是不是 v-或者@或者: 屬性開頭的 去除掉 值獲取name // parse arg var argMatch = name.match(argRE); //匹配字符串是否含有: 只是匹配一個 var arg = argMatch && argMatch[1]; //獲取字符串 好比原字符串是 abc:efg:hig 獲取到efg:hig if (arg) { name = name.slice(0, -(arg.length + 1)); // 截取name 取得abc } /* 固然也能夠這麼寫 var index = argMatch&&argMatch.index; if (index) { name = name.slice(0, index+ 1); // 截取name 取得abc } */ console.log(el) console.log(name) console.log(rawName) console.log(value) console.log(arg) console.log(modifiers) //爲虛擬dom 添加一個 指令directives屬性 對象 addDirective( el, //虛擬dom vonde name, //獲取 view 原始屬性的名稱 不包含 v- : @的 rawName,// 獲取 view 原始屬性的名稱 包含 v- : @的 value, // 屬性view 屬性上的值 arg, // efg:hig 屬性名稱冒號後面多出來的標籤 modifiers ); if ("development" !== 'production' && name === 'model') { //檢查指令的命名值 不能爲for 或者 for中的遍歷的item checkForAliasModel(el, value); } } } else { // literal attribute文字屬性 { //匹配view 指令,而且把他轉換成 虛擬dom vonde 須要渲染的函數,好比指令{{name}}轉換成 _s(name) //好比字符串 我叫{{name}},今年{{age}},數據{{data.number}}個手機 轉換成 我叫+_s(name)+,今年+_s(age)+,數據+_s(data.number)+個手機 var res = parseText(value, delimiters); //校驗是否含有{{}} 括號的屬性 好比 <div style='style'> </div> 若是寫成 <div style='{{style}}'> </div> 則報錯警告 if (res) { warn$2( name + "=\"" + value + "\": " + 'Interpolation inside attributes has been removed. ' + 'Use v-bind or the colon shorthand instead. For example, ' + 'instead of <div id="{{ val }}">, use <div :id="val">.' ); } } //添加屬性 addAttr( el, //虛擬dom name, //view 屬性名稱 JSON.stringify(value) //view 屬性值 ); // #6887 firefox doesn't update muted state if set via attribute // even immediately after element creation // #6887若是經過屬性設置,firefox不會更新靜音狀態 //甚至在元素建立以後 if ( !el.component && //若是不是組件 name === 'muted' && // Video 屬性 muted 屬性設置或返回視頻是否應該被靜音(關閉聲音)。 platformMustUseProp(el.tag, el.attrsMap.type, name) // 校驗特定的屬性方法 ) { //添加音頻屬性 addProp(el, name, 'true'); } } } } //檢查當前虛擬dom vonde 是否有for指令,或者父組件是否有for指令 function checkInFor(el) { var parent = el; while (parent) { if (parent.for !== undefined) { return true } parent = parent.parent; } return false } //把字符串中的對象拆分紅 對象好比 data.object.info.age 變成對象{object:true,info:true,age:true} 返回出去 function parseModifiers(name) { // 匹配以點開頭的分組 不屬於點 data.object.info.age 匹配到 ['.object','.info' , '.age'] var match = name.match(modifierRE); if (match) { var ret = {}; match.forEach(function (m) { ret[m.slice(1)] = true; //去除點,丟棄第一位。 把他變成對象{object:true,info:true,age:true} }); return ret } } /* 把數組對象轉換成 對象 例如 attrs = [{name:tag1,value:1},{ name:tag2,value:2},{name:tag3,value:3}] 轉換成 map={tag1:1,tag2:2,tag3:3} * */ function makeAttrsMap(attrs) { var map = {}; for (var i = 0, l = attrs.length; i < l; i++) { if ( "development" !== 'production' && map[attrs[i].name] && !isIE && !isEdge ) { warn$2('duplicate attribute: ' + attrs[i].name); } map[attrs[i].name] = attrs[i].value; } return map } // for script (e.g. type="x/template") or style, do not decode content //判斷標籤是不是script或者是style function isTextTag(el) { return el.tag === 'script' || el.tag === 'style' } //若是是style或者是是script 標籤而且type屬性不存在 或者存在而且是javascript 屬性 的時候返回真 function isForbiddenTag(el) { return ( el.tag === 'style' || //若是標籤是 style ( el.tag === 'script'&& //若是是script 標籤 ( !el.attrsMap.type || //若是type屬性不存在 el.attrsMap.type === 'text/javascript' //或者若是type屬性是javascript ) ) ) } var ieNSBug = /^xmlns:NS\d+/; //匹配 字符串 xmlns:NS+數字 var ieNSPrefix = /^NS\d+:/; //匹配 字符串 NS+數字 /* istanbul ignore next */ //防止ie瀏覽器 svu 的 bug 替換屬性含有NS+數字 去除 NS+數字 function guardIESVGBug(attrs) { var res = []; //屬性數組 for (var i = 0; i < attrs.length; i++) { //循環屬性 var attr = attrs[i]; if (!ieNSBug.test(attr.name)) { //匹配 字符串 xmlns:NS+數字 attr.name = attr.name.replace( ieNSPrefix, //匹配 字符串 NS+數字 ''); res.push(attr); } } return res } //檢查指令的命名值 不能爲for 或者 for中的遍歷的item function checkForAliasModel(el, value) { var _el = el; while (_el) { if (_el.for && _el.alias === value) { warn$2( "<" + (el.tag) + " v-model=\"" + value + "\">: " + "You are binding v-model directly to a v-for iteration alias. " + "This will not be able to modify the v-for source array because " + "writing to the alias is like modifying a function local variable. " + "Consider using an array of objects and use v-model on an object property instead." ); } _el = _el.parent; } } /* */ /** * Expand input[v-model] with dyanmic type bindings into v-if-else chains * 使用dyanmic類型綁定將輸入[v-model]展開到v-if-else鏈中 * Turn this: * 把這個 * <input v-model="data[type]" :type="type"> * into this: 到這個 * <input v-if="type === 'checkbox'" type="checkbox" v-model="data[type]"> * <input v-else-if="type === 'radio'" type="radio" v-model="data[type]"> * <input v-else :type="type" v-model="data[type]"> * */ // preTransformNode把attrsMap與attrsList屬性值轉換添加到el ast虛擬dom中爲虛擬dom添加for,alias,iterator1,iterator2, // addRawAttr ,type ,key, ref,slotName或者slotScope或者slot,component或者inlineTemplate , plain,if ,else,elseif 屬性 function preTransformNode( el, //虛擬dom vonde options ) { if (el.tag === 'input') { //若是是input標籤 var map = el.attrsMap; //獲取vonde 全部屬性 if (!map['v-model']) { //若是屬性中沒有v-model 則不須要執行 return } var typeBinding; //類型 if (map[':type'] || map['v-bind:type']) { //獲取類型屬性 typeBinding = getBindingAttr(el, 'type'); //獲取類型屬性值 } if (!map.type && !typeBinding && map['v-bind']) { //若是獲取不到type屬性也獲取不到v-bind:type屬性,能夠獲取到v-bind屬性 typeBinding = "(" + (map['v-bind']) + ").type"; //獲取到v-bind的值,好比v-bind等於abc變成 (abc).type } if (typeBinding) { //判斷 typeBinding 是否存在 var ifCondition = getAndRemoveAttr(el, 'v-if', true); //獲取v-if值 var ifConditionExtra = ifCondition ? ("&&(" + ifCondition + ")") : ""; //判斷if是否有值好比v-if="flag" 若是有 變成 &&(flag) var hasElse = getAndRemoveAttr(el, 'v-else', true) != null; //獲取 v-else 屬性值 標誌 若是有有 多是 '' , ''!= null 爲真 var elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true); //獲取v-else-if 的值 // 1. checkbox 克隆 建立 checkbox ast 元素 var branch0 = cloneASTElement(el); // process for on the main node //判斷獲取v-for屬性是否存在若是有則轉義 v-for指令 把for,alias,iterator1,iterator2屬性添加到虛擬dom中 processFor(branch0); //添加type 屬性 值爲checkbox addRawAttr(branch0, 'type', 'checkbox'); //校驗屬性的值,爲el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 標誌 屬性 processElement(branch0, options); branch0.processed = true; // prevent it from double-processed 防止它被重複處理 branch0.if = "(" + typeBinding + ")==='checkbox'" + ifConditionExtra; // ifConditionExtra 是 判斷if是否有值好比v-if="flag" 若是有 變成 &&(flag) 最終合併成 ((abc).type)===checkbox&&(flag) //爲if指令添加標記 addIfCondition( branch0, //虛擬dom { exp: branch0.if, //if指令的標誌 block: branch0 //虛擬dom } ); // 2. add radio else-if condition 添加radio else-if條件 //克隆 建立 radio ast 元素 var branch1 = cloneASTElement(el); //刪除v-for 屬性 getAndRemoveAttr(branch1, 'v-for', true); //添加type 屬性 addRawAttr(branch1, 'type', 'radio'); //校驗屬性的值,爲el 虛擬dom添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 標誌 屬性 processElement(branch1, options); //爲if指令添加標記 addIfCondition(branch0, { exp: "(" + typeBinding + ")==='radio'" + ifConditionExtra, block: branch1 }); // 3. other 克隆 建立 ast 元素 var branch2 = cloneASTElement(el); //刪除v-for屬性 getAndRemoveAttr(branch2, 'v-for', true); //添加:type 屬性 addRawAttr(branch2, ':type', typeBinding); //校驗屬性的值,爲el添加muted, events,nativeEvents,directives, key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 標誌 屬性 processElement(branch2, options); //爲if指令添加標記 addIfCondition( branch0, { exp: ifCondition, //v-if 屬性值 block: branch2 //ast元素 須要渲染的ast子組件 } ); //判斷是else仍是elseif if (hasElse) { branch0.else = true; } else if (elseIfCondition) { branch0.elseif = elseIfCondition; } //返回轉換過虛擬dom的對象值 return branch0 } } } function cloneASTElement(el) { //轉換屬性,把數組屬性轉換成對象屬性,返回對象 AST元素 return createASTElement( el.tag, //標籤 el.attrsList.slice(), //變成真正的數組 el.parent //父層節點 ) } var model$2 = { preTransformNode: preTransformNode } var modules$1 = [ klass$1, // class 轉換函數 style$1, //style 轉換函數 model$2 //把attrsMap與attrsList屬性值轉換添加到el ast虛擬dom中爲虛擬dom添加for,alias,iterator1,iterator2,addRawAttr ,type ,key, ref,slotName或者slotScope或者slot,component或者inlineTemplate , plain,if ,else,elseif 屬性 ] /* *爲虛擬dom添加textContent 屬性 * */ function text(el, dir) { if (dir.value) { addProp( el, 'textContent', ("_s(" + (dir.value) + ")") ); } } /* * 爲虛擬dom添加innerHTML 屬性 * */ function html(el, dir) { if (dir.value) { addProp(el, 'innerHTML', ("_s(" + (dir.value) + ")")); } } var directives$1 = { model: model, //根據判斷虛擬dom的標籤類型是什麼?給相應的標籤綁定 相應的 v-model 雙數據綁定代碼函數 text: text, // 爲虛擬dom添加textContent 屬性 html: html// 爲虛擬dom添加innerHTML 屬性 } /* * 爲虛擬dom添加基本須要的屬性 modules=modules$1=[ { // class 轉換函數 staticKeys: ['staticClass'], transformNode: transformNode, genData: genData }, { //style 轉換函數 staticKeys: ['staticStyle'], transformNode: transformNode$1, genData: genData$1 }, { preTransformNode: preTransformNode } ] * */ var baseOptions = { expectHTML: true, //標誌 是html modules: modules$1, //爲虛擬dom添加staticClass,classBinding,staticStyle,styleBinding,for,alias,iterator1,iterator2,addRawAttr ,type ,key, ref,slotName或者slotScope或者slot,component或者inlineTemplate , plain,if ,else,elseif 屬性 directives: directives$1,// 根據判斷虛擬dom的標籤類型是什麼?給相應的標籤綁定 相應的 v-model 雙數據綁定代碼函數,爲虛擬dom添加textContent 屬性,爲虛擬dom添加innerHTML 屬性 isPreTag: isPreTag, //判斷標籤是不是pre isUnaryTag: isUnaryTag,//匹配標籤是不是 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen, link,meta,param,source,track,wbr' //校驗屬性 /* * 1. attr === 'value', tag 必須是 'input,textarea,option,select,progress' 其中一個 type !== 'button' * 2. attr === 'selected' && tag === 'option' * 3. attr === 'checked' && tag === 'input' * 4. attr === 'muted' && tag === 'video' * 的狀況下爲真 * */ mustUseProp: mustUseProp, canBeLeftOpenTag: canBeLeftOpenTag, //判斷標籤是不是 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source' isReservedTag: isReservedTag, //保留標籤 判斷是否是真的是 html 原有的標籤 或者svg標籤 getTagNamespace: getTagNamespace, //判斷 tag 是不是svg或者math 標籤 staticKeys: genStaticKeys(modules$1) // * 把數組對象 [{ staticKeys:1},{staticKeys:2},{staticKeys:3}]鏈接數組對象中的 staticKeys key值,鏈接成一個字符串 str=‘1,2,3’ }; console.log('==baseOptions==') console.log(baseOptions) /* */ var isStaticKey; var isPlatformReservedTag; //匹配type,tag,attrsList,attrsMap,plain,parent,children,attrs +key 字符串 var genStaticKeysCached = cached(genStaticKeys$1); /** * Goal of the optimizer: walk the generated template AST tree * and detect sub-trees that are purely static, i.e. parts of * the DOM that never needs to change. * * Once we detect these sub-trees, we can: * * 1. Hoist them into constants, so that we no longer need to * create fresh nodes for them on each re-render; * 2. Completely skip them in the patching process. * 優化器的目標:遍歷生成的模板AST樹 檢測純靜態的子樹,即 永遠不須要更改的DOM。 * *一旦咱們檢測到這些子樹,咱們能夠: * * 1。把它們變成常數,這樣咱們就不須要了 *在每次從新渲染時爲它們建立新的節點; * 2。在修補過程當中徹底跳過它們。 * * * 循環遞歸虛擬node,標記是否是靜態節點 * 根據node.static或者 node.once 標記staticRoot的狀態 */ function optimize(root, options) { if (!root) { return } //匹配type,tag,attrsList,attrsMap,plain,parent,children,attrs + staticKeys 字符串 isStaticKey = genStaticKeysCached(options.staticKeys || ''); //保留標籤 判斷是否是真的是 html 原有的標籤 或者svg標籤 isPlatformReservedTag = options.isReservedTag || no; // first pass: mark all non-static nodes. ////第一遍:標記全部非靜態節點。 //循環遞歸虛擬node,標記不是靜態節點 markStatic$1(root); // second pass: mark static roots. //第二步:標記靜態根。 markStaticRoots(root, false); } //匹配type,tag,attrsList,attrsMap,plain,parent,children,attrs +key 字符串 function genStaticKeys$1(keys) { return makeMap( 'type,tag,attrsList,attrsMap,plain,parent,children,attrs' + (keys ? ',' + keys : '') ) } //循環遞歸虛擬node,標記不是靜態節點 function markStatic$1(node) { node.static = isStatic(node); //判斷是不是靜態的ast虛擬dom type必須不等於2和3,pre必須爲真 if (node.type === 1) { // do not make component slot content static. this avoids // 1. components not able to mutate slot nodes // 2. static slot content fails for hot-reloading //不要將組件插槽內容設置爲靜態。這就避免了 // 1。組件沒法更改插槽節點 // 2。靜態插槽內容沒法熱加載 if ( !isPlatformReservedTag(node.tag) && //保留標籤 判斷是否是真的是 html 原有的標籤 或者svg標籤 node.tag !== 'slot' && //當前標籤不等於slot node.attrsMap['inline-template'] == null // 也不是inline-template 內聯模板 ) { return } //深遞歸循環 for (var i = 0, l = node.children.length; i < l; i++) { var child = node.children[i]; markStatic$1(child); if (!child.static) { node.static = false; } } if (node.ifConditions) { //if標記 for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) { var block = node.ifConditions[i$1].block; //虛擬dom markStatic$1(block); if (!block.static) { node.static = false; } } } } } //根據node.static或者 node.once 標記staticRoot的狀態 function markStaticRoots(node, isInFor) { if (node.type === 1) { //虛擬 dom 節點 if ( node.static || //靜態節點 node.once // v-once 只渲染一次節點。 ) { node.staticInFor = isInFor; } // For a node to qualify as a static root, it should have children that // are not just static text. Otherwise the cost of hoisting out will // outweigh the benefits and it's better off to just always render it fresh. //要使一個節點符合靜態根的條件,它應該有這樣的子節點 //不只僅是靜態文本。不然,吊裝費用將會增長 //好處大於好處,最好老是保持新鮮。 if ( node.static && //若是是靜態節點 node.children.length && //若是是有子節點 !( node.children.length === 1 && //若是隻有一個子節點 node.children[0].type === 3 //屬性節點 )) { node.staticRoot = true; //標記靜態根節點 return } else { node.staticRoot = false; } if (node.children) { for (var i = 0, l = node.children.length; i < l; i++) { markStaticRoots( node.children[i], isInFor || !!node.for ); } } if (node.ifConditions) { for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) { markStaticRoots( node.ifConditions[i$1].block, isInFor ); } } } } //判斷是不是靜態的ast虛擬dom type必須不等於2和3,pre必須爲真 function isStatic(node) { if (node.type === 2) { // expression 屬性節點 expression return false } if (node.type === 3) { // text 文本節點或者是空註釋節點 return true } return !!( // 跳過這個元素和它的子元素的編譯過程。能夠用來顯示原始 Mustache 標籤。跳過大量沒有指令的節點會加快編譯。 遇到指令不須要編譯成模板顯示原始指令 node.pre || //標記 標籤是否還有 v-pre 指令 ,若是有則爲真 ( !node.hasBindings && // no dynamic bindings // 沒有動態標記元素 !node.if && !node.for && // not v-if or v-for or v-else 沒有 v-if 或者 v-for 或者 v-else !isBuiltInTag(node.tag) && // not a built-in 沒有 slot,component isPlatformReservedTag(node.tag) && // not a component 不是一個組件 保留標籤 判斷是否是真的是 html 原有的標籤 或者svg標籤 !isDirectChildOfTemplateFor(node) && // 判斷當前ast 虛擬dom 的父標籤 若是不是template則返回false,若是含有v-for則返回true Object.keys(node).every(isStaticKey) //node的key必須每一項都符合 匹配type,tag,attrsList,attrsMap,plain,parent,children,attrs + staticKeys 的字符串 ) ) } // 判斷當前ast 虛擬dom 的父標籤 若是不是template則返回false,若是含有v-for則返回true function isDirectChildOfTemplateFor(node) { while (node.parent) { //父dom node = node.parent; if (node.tag !== 'template') { //不是模板 標籤 return false } if (node.for) { //含有v-for return true } } return false } /* */ var fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/; var simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/; // KeyboardEvent.keyCode aliases var keyCodes = { esc: 27, tab: 9, enter: 13, space: 32, up: 38, left: 37, right: 39, down: 40, 'delete': [8, 46] }; // KeyboardEvent.key aliases var keyNames = { esc: 'Escape', tab: 'Tab', enter: 'Enter', space: ' ', // #7806: IE11 uses key names without `Arrow` prefix for arrow keys. up: ['Up', 'ArrowUp'], left: ['Left', 'ArrowLeft'], right: ['Right', 'ArrowRight'], down: ['Down', 'ArrowDown'], 'delete': ['Backspace', 'Delete'] }; // #4868: modifiers that prevent the execution of the listener // need to explicitly return null so that we can determine whether to remove // the listener for .once var genGuard = function (condition) { return ("if(" + condition + ")return null;"); }; var modifierCode = { stop: '$event.stopPropagation();', prevent: '$event.preventDefault();', self: genGuard("$event.target !== $event.currentTarget"), ctrl: genGuard("!$event.ctrlKey"), shift: genGuard("!$event.shiftKey"), alt: genGuard("!$event.altKey"), meta: genGuard("!$event.metaKey"), left: genGuard("'button' in $event && $event.button !== 0"), middle: genGuard("'button' in $event && $event.button !== 1"), right: genGuard("'button' in $event && $event.button !== 2") }; function genHandlers( events, isNative, warn ) { var res = isNative ? 'nativeOn:{' : 'on:{'; for (var name in events) { res += "\"" + name + "\":" + (genHandler(name, events[name])) + ","; } return res.slice(0, -1) + '}' } function genHandler( name, handler ) { if (!handler) { return 'function(){}' } if (Array.isArray(handler)) { return ("[" + (handler.map(function (handler) { return genHandler(name, handler); }).join(',')) + "]") } var isMethodPath = simplePathRE.test(handler.value); var isFunctionExpression = fnExpRE.test(handler.value); if (!handler.modifiers) { if (isMethodPath || isFunctionExpression) { return handler.value } /* istanbul ignore if */ return ("function($event){" + (handler.value) + "}") // inline statement } else { var code = ''; var genModifierCode = ''; var keys = []; for (var key in handler.modifiers) { if (modifierCode[key]) { genModifierCode += modifierCode[key]; // left/right if (keyCodes[key]) { keys.push(key); } } else if (key === 'exact') { var modifiers = (handler.modifiers); genModifierCode += genGuard( ['ctrl', 'shift', 'alt', 'meta'] .filter(function (keyModifier) { return !modifiers[keyModifier]; }) .map(function (keyModifier) { return ("$event." + keyModifier + "Key"); }) .join('||') ); } else { keys.push(key); } } if (keys.length) { code += genKeyFilter(keys); } // Make sure modifiers like prevent and stop get executed after key filtering if (genModifierCode) { code += genModifierCode; } var handlerCode = isMethodPath ? ("return " + (handler.value) + "($event)") : isFunctionExpression ? ("return (" + (handler.value) + ")($event)") : handler.value; /* istanbul ignore if */ return ("function($event){" + code + handlerCode + "}") } } function genKeyFilter(keys) { return ("if(!('button' in $event)&&" + (keys.map(genFilterCode).join('&&')) + ")return null;") } function genFilterCode(key) { var keyVal = parseInt(key, 10); if (keyVal) { return ("$event.keyCode!==" + keyVal) } var keyCode = keyCodes[key]; var keyName = keyNames[key]; return ( "_k($event.keyCode," + (JSON.stringify(key)) + "," + (JSON.stringify(keyCode)) + "," + "$event.key," + "" + (JSON.stringify(keyName)) + ")" ) } /* */ //包裝事件 function on(el, dir) { if ("development" !== 'production' && dir.modifiers) { warn("v-on without argument does not support modifiers."); } //包裝事件 el.wrapListeners = function (code) { return ("_g(" + code + "," + (dir.value) + ")"); }; } /* */ //包裝數據 function bind$1(el, dir) { el.wrapData = function (code) { return ("_b(" + code + ",'" + (el.tag) + "'," + (dir.value) + "," + (dir.modifiers && dir.modifiers.prop ? 'true' : 'false') + (dir.modifiers && dir.modifiers.sync ? ',true' : '') + ")") }; } /* * 基本指令參數 */ var baseDirectives = { on: on, //包裝事件 bind: bind$1, //包裝數據 cloak: noop //空函數 } /* * 擴展指令,on,bind,cloak,方法, * dataGenFns 獲取到一個數組,數組中有兩個函數genData和genData$1 * */ var CodegenState = function CodegenState(options) { this.options = options; this.warn = options.warn || baseWarn; //警告日誌輸出函數 /* * 爲虛擬dom添加基本須要的屬性 modules=modules$1=[ { // class 轉換函數 staticKeys: ['staticClass'], transformNode: transformNode, genData: genData }, { //style 轉換函數 staticKeys: ['staticStyle'], transformNode: transformNode$1, genData: genData$1 }, { preTransformNode: preTransformNode } ] */ //循環過濾數組或者對象的值,根據key循環 過濾對象或者數組[key]值,若是不存在則丟棄,若是有相同多個的key值,返回多個值的數組 //這裏返回是空 this.transforms = pluckModuleFunction(options.modules, 'transformCode'); //獲取到一個數組,數組中有兩個函數genData和genData$1 this.dataGenFns = pluckModuleFunction(options.modules, 'genData'); console.log(this.transforms ) console.log(this.dataGenFns ) console.log(options) // options.directives= { // model: model, //根據判斷虛擬dom的標籤類型是什麼?給相應的標籤綁定 相應的 v-model 雙數據綁定代碼函數 // text: text, // 爲虛擬dom添加textContent 屬性 // html: html// 爲虛擬dom添加innerHTML 屬性 // } /* * 基本指令參數 */ // var baseDirectives = { // on: on, //包裝事件 // bind: bind$1, //包裝數據 // cloak: noop //空函數 // } // var directives$1 = { // model: model, //根據判斷虛擬dom的標籤類型是什麼?給相應的標籤綁定 相應的 v-model 雙數據綁定代碼函數 // text: text, // 爲虛擬dom添加textContent 屬性 // html: html// 爲虛擬dom添加innerHTML 屬性 // } // 擴展指令,on,bind,cloak,方法 this.directives = extend( extend( {}, baseDirectives ), options.directives ); var isReservedTag = options.isReservedTag || no; //保留標籤 判斷是否是真的是 html 原有的標籤 或者svg標籤 //也許是組件 this.maybeComponent = function (el) { return !isReservedTag(el.tag); }; this.onceId = 0; //靜態渲染方法 this.staticRenderFns = []; }; //初始化擴展指令,on,bind,cloak,方法, dataGenFns 獲取到一個數組,數組中有兩個函數genData和genData$1 //genElement根據el判斷是不是組件,或者是否含有v-once,v-if,v-for,是否有template屬性,或者是slot插槽,轉換style,css等轉換成虛擬dom須要渲染的參數函數 function generate( ast, //ast 對象模板數據 options ) { // options 參數爲 // 原型中有baseOptions方法 // { // shouldDecodeNewlines: shouldDecodeNewlines, //flase //IE在屬性值中編碼換行,而其餘瀏覽器則不會 // shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, //true chrome在a[href]中編碼內容 // delimiters: options.delimiters, //改變純文本插入分隔符。修改指令的書寫風格,好比默認是{{mgs}} delimiters: ['${', '}']以後變成這樣 ${mgs} // comments: options.comments //當設爲 true 時,將會保留且渲染模板中的 HTML 註釋。默認行爲是捨棄它們。 // }, //生成狀態 // * 擴展指令,on,bind,cloak,方法, // * dataGenFns 獲取到一個數組,數組中有兩個函數genData和genData$1 var state = new CodegenState(options); //根據el判斷是不是組件,或者是否含有v-once,v-if,v-for,是否有template屬性,或者是slot插槽,轉換style,css等轉換成虛擬dom須要渲染的參數函數 var code = ast ? genElement(ast, state) : '_c("div")'; console.log({ render: ("with(this){return " + code + "}"), staticRenderFns: state.staticRenderFns }) return { //with 綁定js的this 縮寫 render: ("with(this){return " + code + "}"), staticRenderFns: state.staticRenderFns //空數組 } } //根據el判斷是不是組件,或者是否含有v-once,v-if,v-for,是否有template屬性,或者是slot插槽,轉換style,css等轉換成虛擬dom須要渲染的參數函數 function genElement( el, //ast對象或者虛擬dom state //渲染虛擬dom的一些方法 ) { console.log(state) console.log(el) if (el.staticRoot && !el.staticProcessed) { //將子節點導出虛擬dom 渲染函數的參數形式。靜態渲染 return genStatic(el, state) } else if (el.once && !el.onceProcessed) { //參考文檔 https://cn.vuejs.org/v2/api/#v-once // v-once // 不須要表達式 // 詳細:只渲染元素和組件一次。隨後的從新渲染,元素/組件及其全部的子節點將被視爲靜態內容並跳過。這能夠用於優化更新性能 // <!-- 單個元素 --> // <span v-once>This will never change: {{msg}}</span> return genOnce(el, state); } else if (el.for && !el.forProcessed) { // v-for //判斷標籤是否含有v-for屬性 解析v-for指令中的參數 而且返回 虛擬dom須要的參數js渲染函數 return genFor(el, state) } else if (el.if && !el.ifProcessed) { //判斷標籤是否有if屬性 // v-if //判斷標籤是否含有if屬性 解析 if指令中的參數 而且返回 虛擬dom須要的參數js渲染函數 return genIf(el, state) } else if (el.tag === 'template' && !el.slotTarget) { //標籤是模板template //獲取虛擬dom子節點 return genChildren(el, state) || 'void 0' } else if (el.tag === 'slot') { //若是標籤是插槽 return genSlot(el, state) } else { // component or element //組件或元素 var code; if (el.component) { //若是是組件 //建立一個虛擬dom 的參數渲染的函數 code = genComponent( el.component, el, state ); } else { var data = el.plain ? //若是標籤中沒有屬性則這個標誌爲真 undefined : genData$2(el, state); var children = el.inlineTemplate ? //是否是內聯模板標籤 null : genChildren(el, state, true); code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")"; } // module transforms for (var i = 0; i < state.transforms.length; i++) { code = state.transforms[i](el, code); } //返回 虛擬dom須要的參數js渲染函數 return code } } // hoist static sub-trees out 將靜態子樹吊出 //將子節點導出虛擬dom 渲染函數的參數形式 function genStatic(el, state) { //標記已經處理過 el.staticProcessed = true; //添加渲染函數 //genElement根據el判斷是不是組件,或者是否含有v-once,v-if,v-for,是否有template屬性,或者是slot插槽,轉換style,css等轉換成虛擬dom須要渲染的參數函數 state.staticRenderFns.push(("with(this){return " + (genElement(el, state)) + "}")); //返回虛擬dom渲染須要的參數格式 return ("_m(" + (state.staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")") } // v-once //文檔https://cn.vuejs.org/v2/api/#v-once // v-once // 不須要表達式 // 詳細:只渲染元素和組件一次。隨後的從新渲染,元素/組件及其全部的子節點將被視爲靜態內容並跳過。這能夠用於優化更新性能。 function genOnce(el, state) { //標誌已經處理過的 el.onceProcessed = true; if (el.if && !el.ifProcessed) { //判斷標籤是否含有if屬性 return genIf(el, state) } else if (el.staticInFor) { var key = ''; var parent = el.parent; while (parent) { if (parent.for) { key = parent.key; break } parent = parent.parent; } if (!key) { "development" !== 'production' && state.warn( "v-once can only be used inside v-for that is keyed. " ); //genElement根據el判斷是不是組件,或者是否含有v-once,v-if,v-for,是否有template屬性,或者是slot插槽,轉換style,css等轉換成虛擬dom須要渲染的參數函數 return genElement(el, state) } //genElement根據el判斷是不是組件,或者是否含有v-once,v-if,v-for,是否有template屬性,或者是slot插槽,轉換style,css等轉換成虛擬dom須要渲染的參數函數 return ("_o(" + (genElement(el, state)) + "," + (state.onceId++) + "," + key + ")") } else { //將子節點導出虛擬dom 渲染函數的參數形式 return genStatic(el, state) } } //判斷標籤是否含有if屬性 解析 if指令中的參數 而且返回 虛擬dom須要的參數js渲染函數 function genIf( el, //dom節點 state, //狀態 altGen, // 不知道幹嗎的 altEmpty // 不知道幹嗎的 ) { console.log('==el==') console.log(el) el.ifProcessed = true; // avoid recursion 標記已經處理過 避免遞歸 //el.ifConditions.slice() if條件參數 //解析 if指令中的參數 而且返回 虛擬dom須要的參數js渲染函數 return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty) } //解析 if指令中的參數 而且返回 虛擬dom須要的參數js渲染函數 function genIfConditions( conditions, //el 虛擬dom state, //狀態 altGen, //知道幹嗎的 altEmpty//知道幹嗎的 ) { if (!conditions.length) { //若是conditions 不存在 則返回一個空的虛擬dom參數 return altEmpty || '_e()' } var condition = conditions.shift(); //取第一個元素 console.log('==condition==') console.log(condition) if (condition.exp) { //判斷if指令參數是否存在 若是存在則遞歸condition.block 數據此時ifProcessed 變爲true 下次不會再進來 return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty))) } else { return ("" + (genTernaryExp(condition.block))) } // v-if with v-once should generate code like (a)?_m(0):_m(1) //若是用v-once生成像(a)?_m(0):_m(1)這樣的代碼 function genTernaryExp(el) { console.log('==altGen=='); console.log(altGen); //數據此時ifProcessed 變爲true 下次不會再進來 return altGen ? altGen(el, state) //altGen 一個自定義函數吧 : el.once ? //靜態標籤標誌 存在麼 不存在 genOnce(el, state) //導出一個靜態標籤的虛擬dom參數 : genElement(el, state) //遞歸el 數據此時ifProcessed 變爲true 下次不會再進來 } } function genFor( el, //虛擬dom 節點 state, //狀態 altGen, //函數不知道是什麼 altHelper //函數不知道是什麼 ) { var exp = el.for; //含有for的標籤 var alias = el.alias; //"item" var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : ''; //iterator1 "index" 索引 var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : ''; //iterator2: "key" if ("development" !== 'production' && state.maybeComponent(el) && el.tag !== 'slot' && el.tag !== 'template' && !el.key ) { state.warn( "<" + (el.tag) + " v-for=\"" + alias + " in " + exp + "\">: component lists rendered with " + "v-for should have explicit keys. " + "See https://vuejs.org/guide/list.html#key for more info.", true /* tip */ ); } el.forProcessed = true; // avoid recursion 標記已經處理過for //遞歸回調 return (altHelper || '_l') + "((" + exp + ")," + "function(" + alias + iterator1 + iterator2 + "){" + "return " + ((altGen || genElement)(el, state)) + '})' } //根據判斷el是否含有 指令屬性,key,ref,refInFor,v-for,pre,component function genData$2(el, state) { var data = '{'; // directives first. // directives may mutate the el's other properties before they are generated. //初始化指令屬性參數,把ast對象中的指令屬性對象提取出來成數組只保留name和rawName這兩個key 好比<div v-info></div> 則變成 directives:[{name:"info",rawName:"v-info"}] var dirs = genDirectives(el, state); if (dirs) { data += dirs + ','; } // key if (el.key) { data += "key:" + (el.key) + ","; } // ref if (el.ref) { data += "ref:" + (el.ref) + ","; } if (el.refInFor) { data += "refInFor:true,"; } // pre if (el.pre) { data += "pre:true,"; } // record original tag name for components using "is" attribute if (el.component) { data += "tag:\"" + (el.tag) + "\","; } // module data generation functions for (var i = 0; i < state.dataGenFns.length; i++) { data += state.dataGenFns[i](el); } // attributes if (el.attrs) { //普通屬性 //把props 變成 一個 由 字符串對象數組 // name1:value1,name2:value2,name3:value3 data += "attrs:{" + (genProps(el.attrs)) + "},"; } // DOM props if (el.props) { //props屬性 //把props 變成 一個 由 字符串對象數組 // name1:value1,name2:value2,name3:value3 data += "domProps:{" + (genProps(el.props)) + "},"; } // event handlers if (el.events) { data += (genHandlers(el.events, false, state.warn)) + ","; } if (el.nativeEvents) { data += (genHandlers(el.nativeEvents, true, state.warn)) + ","; } // slot target // only for non-scoped slots if (el.slotTarget && !el.slotScope) { data += "slot:" + (el.slotTarget) + ","; } // scoped slots if (el.scopedSlots) { data += (genScopedSlots(el.scopedSlots, state)) + ","; } // component v-model if (el.model) { data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},"; } // inline-template if (el.inlineTemplate) { var inlineTemplate = genInlineTemplate(el, state); if (inlineTemplate) { data += inlineTemplate + ","; } } data = data.replace(/,$/, '') + '}'; // v-bind data wrap if (el.wrapData) { data = el.wrapData(data); } // v-on data wrap if (el.wrapListeners) { data = el.wrapListeners(data); } return data } //初始化指令屬性參數,把ast對象中的指令屬性對象提取出來成數組只保留name和rawName這兩個key 好比<div v-info></div> 則變成 directives:[{name:"info",rawName:"v-info"}] function genDirectives(el, state) { var dirs = el.directives; //是不是指令 if (!dirs) { return } var res = 'directives:['; var hasRuntime = false; var i, l, dir, needRuntime; //爲虛擬dom 添加一個 指令directives屬性 對象 // addDirective( // el, //虛擬dom vonde // name, //獲取 view 原始屬性的名稱 不包含 v- : @的 // rawName,// 獲取 view 原始屬性的名稱 包含 v- : @的 // value, // 屬性view 屬性上的值 // arg, // efg:hig 屬性名稱冒號後面多出來的標籤 // modifiers // ); console.log(dirs) for (i = 0, l = dirs.length; i < l; i++) { //一個虛擬dom可能會有能綁定多個指令 dir = dirs[i]; console.log(dir) needRuntime = true; var gen = state.directives[dir.name]; console.log(gen) if (gen) { // compile-time directive that manipulates AST. // returns true if it also needs a runtime counterpart. // 操做AST的編譯時指令。 // 若是還須要運行時對等項,則返回true。 needRuntime = !!gen(el, dir, state.warn); } if (needRuntime) { hasRuntime = true; res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:\"" + (dir.arg) + "\"") : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},"; } } if (hasRuntime) { res = res.slice(0, -1) + ']' console.log(res) return res; } } function genInlineTemplate(el, state) { var ast = el.children[0]; if ("development" !== 'production' && ( el.children.length !== 1 || ast.type !== 1 )) { state.warn('Inline-template components must have exactly one child element.'); } if (ast.type === 1) { var inlineRenderFns = generate(ast, state.options); return ("inlineTemplate:{render:function(){" + (inlineRenderFns.render) + "},staticRenderFns:[" + (inlineRenderFns.staticRenderFns.map(function (code) { return ("function(){" + code + "}"); }).join(',')) + "]}") } } function genScopedSlots(slots, state) { return ("scopedSlots:_u([" + (Object.keys(slots).map(function (key) { return genScopedSlot(key, slots[key], state) }).join(',')) + "])") } function genScopedSlot(key, el, state) { if (el.for && !el.forProcessed) { return genForScopedSlot(key, el, state) } var fn = "function(" + (String(el.slotScope)) + "){" + "return " + (el.tag === 'template' ? el.if ? ((el.if) + "?" + (genChildren(el, state) || 'undefined') + ":undefined") : genChildren(el, state) || 'undefined' : genElement(el, state)) + "}"; //genElement根據el判斷是不是組件,或者是否含有v-once,v-if,v-for,是否有template屬性,或者是slot插槽,轉換style,css等轉換成虛擬dom須要渲染的參數函數 return ("{key:" + key + ",fn:" + fn + "}") } function genForScopedSlot(key, el, state) { var exp = el.for; var alias = el.alias; var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : ''; var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : ''; el.forProcessed = true; // avoid recursion return "_l((" + exp + ")," + "function(" + alias + iterator1 + iterator2 + "){" + "return " + (genScopedSlot(key, el, state)) + '})' } //獲取虛擬dom子節點 function genChildren(el, //dom state, //狀態 checkSkip, // 布爾值 altGenElement, altGenNode ) { var children = el.children; //子節點 if (children.length) { var el$1 = children[0]; // optimize single v-for 優化單 v-for。 if ( children.length === 1 &&//若是隻有一個子節點 el$1.for && el$1.tag !== 'template' && //節點不是template el$1.tag !== 'slot' //節點不是slot ) { //子節點若是隻是一個 //altGenElement和genElement是一個函數 傳進來參數是el$1, state return (altGenElement || genElement)(el$1, state) } //肯定子數組所需的標準化。 // 0:不須要標準化 // 1:須要簡單的標準化(多是1級深嵌套數組) // 2:須要徹底標準化 var normalizationType = checkSkip ? getNormalizationType( //若是children.length==0 就返回0,若是若是有for屬性存在或者tag等於template或者是slot 則問真就返回1,若是是組件則返回2 children, //子節點 state.maybeComponent //判斷是不是組件 ) : 0; var gen = altGenNode || genNode; //genNode根據node.type 屬性不一樣調用不一樣的方法,獲得不一樣的虛擬dom渲染方法 return ("[" + (children.map(function (c) { return gen(c, state); //genNode根據node.type 屬性不一樣調用不一樣的方法,獲得不一樣的虛擬dom渲染方法 }).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : '')) } } // determine the normalization needed for the children array. // 0: no normalization needed // 1: simple normalization needed (possible 1-level deep nested array) // 2: full normalization needed //肯定子數組所需的標準化。 // 0:不須要標準化 // 1:須要簡單的標準化(多是1級深嵌套數組) // 2:須要徹底標準化 //若是children.length==0 就返回0,若是若是有for屬性存在或者tag等於template或者是slot 則問真就返回1,若是是組件則返回2 function getNormalizationType( children, maybeComponent ) { var res = 0; for (var i = 0; i < children.length; i++) { //循環子節點 var el = children[i]; if (el.type !== 1) { //若是是真是dom則跳過循環 continue } //若是有for屬性存在或者tag等於template或者是slot 則問真 if (needsNormalization(el) || (el.ifConditions && el.ifConditions.some(function (c) { //判斷數組中是否存在知足條件的項,只要有一項知足條件,就會返回true。 return needsNormalization(c.block); }))) { res = 2; break } if (maybeComponent(el) || //判斷是不是組件 (el.ifConditions && el.ifConditions.some(function (c) {//判斷數組中是否存在知足條件的項,只要有一項知足條件,就會返回true。 return maybeComponent(c.block); }))) { res = 1; } } return res } //若是for屬性存在或者tag等於template或者是slot 則問真 function needsNormalization(el) { return el.for !== undefined || el.tag === 'template' || el.tag === 'slot' } //根據node.type 屬性不一樣調用不一樣的方法 function genNode(node, state) { if (node.type === 1) { //返回虛擬dom vonde渲染調用的函數 //genElement根據el判斷是不是組件,或者是否含有v-once,v-if,v-for,是否有template屬性,或者是slot插槽,轉換style,css等轉換成虛擬dom須要渲染的參數函數 return genElement(node, state) } if (node.type === 3 && node.isComment) { //返回虛擬dom vonde渲染調用的函數 return genComment(node) } else { //返回虛擬dom vonde渲染調用的函數 return genText(node) } } //返回虛擬dom vonde渲染調用的函數 function genText(text) { return ("_v(" + (text.type === 2 ? text.expression // no need for () because already wrapped in _s() : transformSpecialNewlines(JSON.stringify(text.text))) + ")") } //返回虛擬dom vonde渲染調用的函數 function genComment(comment) { return ("_e(" + (JSON.stringify(comment.text)) + ")") } //返回虛擬dom vonde渲染調用的函數 function genSlot(el, state) { var slotName = el.slotName || '"default"'; //獲取slotName 插槽名稱 var children = genChildren(el, state); //獲取子節點的虛擬dom渲染 函數 var res = "_t(" + slotName + (children ? ("," + children) : ''); var attrs = el.attrs && ("{" + (el.attrs.map(function (a) { //屬性 return ((camelize(a.name)) + ":" + (a.value)); }).join(',')) + "}"); var bind$$1 = el.attrsMap['v-bind']; //v-bind屬性 if ((attrs || bind$$1) && !children) { res += ",null"; } if (attrs) { res += "," + attrs; } if (bind$$1) { res += (attrs ? '' : ',null') + "," + bind$$1; } return res + ')' } // componentName is el.component, take it as argument to shun flow's pessimistic refinement //返回虛擬dom vonde渲染調用的函數 function genComponent( componentName, //組件名稱 el, state ) { var children = el.inlineTemplate ? null : genChildren(el, state, true); return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")") } //把props 變成 一個 由 字符串對象數組 // name1:value1,name2:value2,name3:value3 function genProps(props) { var res = ''; for (var i = 0; i < props.length; i++) { var prop = props[i]; /* istanbul ignore if */ { res += "\"" + (prop.name) + "\":" + (transformSpecialNewlines(prop.value)) + ","; } } //去除最後一位字符串 return res.slice(0, -1) } /* \u2028 行分隔符 行結束符 \u2029 段落分隔符 行結束符 這個編碼爲2028的字符爲行分隔符,會被瀏覽器理解爲換行,而在Javascript的字符串表達式中是不容許換行的,從而致使錯誤。 把特殊字符轉義替換便可,代碼以下所示: str = str.Replace("\u2028", "\\u2028"); */ function transformSpecialNewlines(text) { return text .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029') } /* */ // these keywords should not appear inside expressions, but operators like 這些關鍵字不該該出如今表達式中,可是操做符喜歡 // typeof, instanceof and in are allowed 容許使用類型of、instanceof和in //匹配 配有全局匹配 只會匹配到一個 // do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' + // 'super,throw,while,yield,delete,export,import,return,switch,default,' + // 'extends,finally,continue,debugger,function,arguments //匹配是否含有 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' + // 'super,throw,while,yield,delete,export,import,return,switch,default,' + // 'extends,finally,continue,debugger,function,arguments' var prohibitedKeywordRE = new RegExp('\\b' + ( 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' + 'super,throw,while,yield,delete,export,import,return,switch,default,' + 'extends,finally,continue,debugger,function,arguments' ).split(',').join('\\b|\\b') + '\\b'); // these unary operators should not be used as property/method names 這些一元運算符不該該用做屬性/方法名 // 匹配 delete (任何字符) 或 typeof (任何字符) 或 void (任何字符) var unaryOperatorsRE = new RegExp('\\b' + ( 'delete,typeof,void' ).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)'); // strip strings in expressions 在表達式中剝離字符串 //判斷是不是真正的字符串 var stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g; //'([^'\\]|\\.)*' ''內的若干字符 //| //"([^"\\]|\\.)*" ""內的若干字符 //| // `(?:[^`\\]|\\.)* \$\{|\}(?:[^`\\]|\\.)*` `字符和${字符}和字符` //| //`([^`\\]|\\.)*` `和`之間的若干字符 // detect problematic expressions in a template //檢測模板中有問題的表達式 function detectErrors(ast) { var errors = []; if (ast) { //檢查模板中的表達式 checkNode(ast, errors); } return errors } //檢測 模板指令 把字符串變成真正的js是否有報錯 function checkNode(node, errors) { //node // 元素element 1 // 屬性attr 2 // 文本text 3 if (node.type === 1) { //text 節點類型,至關於在dom點中的空白區域 //attrsMap 節點記錄屬性的對象 for (var name in node.attrsMap) { if (dirRE.test(name)) { // var dirRE = /^v-|^@|^:/; 判斷屬性開頭是否爲 v- @ : 等 //若是是vue 中的屬性則抽離出來 var value = node.attrsMap[name]; //獲取屬性名稱 if (value) { if (name === 'v-for') { //若是是v-for checkFor(node, ("v-for=\"" + value + "\""), errors); //檢查字符串模板 轉換成js是否有報錯 } else if (onRE.test(name)) { // var onRE = /^@|^v-on:/; 匹配@開頭 或者是v-on: 開頭 //檢查事件是否含有關鍵詞 type void delete 而且不是$開頭的 收集錯誤信息 checkEvent(value, (name + "=\"" + value + "\""), errors); } else { //檢查字符串轉成真正js的時候是否會報錯 能夠替代eval() checkExpression(value, (name + "=\"" + value + "\""), errors); } } } } if (node.children) { //若是有子節點則遞歸 for (var i = 0; i < node.children.length; i++) { //遞歸子節點 檢查子節點 checkNode(node.children[i], errors); } } } else if (node.type === 2) { //檢查屬性 字符串轉成真正js的時候是否會報錯 能夠替代eval() checkExpression(node.expression, node.text, errors); } } //檢查事件,去除掉模板字符串,匹配是否含有delete (任何字符) 或 typeof (任何字符) 或 void (任何字符) 關鍵詞,檢查字符串開頭是否含有$ function checkEvent(exp, text, errors) { var stipped = exp.replace(stripStringRE, ''); //去除掉模板字符串 var keywordMatch = stipped.match(unaryOperatorsRE); //匹配是否含有delete (任何字符) 或 typeof (任何字符) 或 void (任何字符) 關鍵詞 //判斷匹配到的 字符串 開頭是不是$ 開頭的 if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') { errors.push( "avoid using JavaScript unary operator as property name: " + "\"" + (keywordMatch[0]) + "\" in expression " + (text.trim()) ); } //字符串轉成真正js的時候是否會報錯 能夠替代eval() checkExpression(exp, text, errors); } //檢查 for function checkFor(node, //節點 text, //for的text "(itme,index) in list" errors //錯誤信息 ) { //檢查字符串 轉成真正的js的時候是否會報錯 checkExpression(node.for || '', text, errors); //檢查 new Function(("var " + ident + "=_")); 是否會報錯 至關於 var str = _; checkIdentifier(node.alias, 'v-for alias', text, errors); checkIdentifier(node.iterator1, 'v-for iterator', text, errors); checkIdentifier(node.iterator2, 'v-for iterator', text, errors); } //檢查var a ='_' 或者 檢查var a =_ 是否會報錯 new function 用來檢測js錯誤 與eval差很少 function checkIdentifier(ident, //識別 type, //類型 text, //爲本 errors //錯誤信息 ) { if (typeof ident === 'string') { try { new Function(("var " + ident + "=_")); //檢查var a ='_' 或者 檢查var a =_ 是否會報錯 new function 用來檢測js錯誤 與eval差很少 } catch (e) { errors.push(("invalid " + type + " \"" + ident + "\" in expression: " + (text.trim()))); } } } // new function 用來檢測js錯誤 能夠替代eval() 字符轉換js檢查 字符串變量指向Function,防止有些前端編譯工具報錯 function checkExpression(exp, text, errors) { try { // new function 用來檢測js錯誤 能夠替代eval() 字符轉換js檢查 字符串變量指向Function,防止有些前端編譯工具報錯 new Function(("return " + exp)); } catch (e) { //把裏面的字符串替換成空的 //而後在匹配 // 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' + // 'super,throw,while,yield,delete,export,import,return,switch,default,' + // 'extends,finally,continue,debugger,function,arguments' 這些關鍵詞 var keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE); if (keywordMatch) { //收集錯誤信息 errors.push( "avoid using JavaScript keyword as property name: " + "\"" + (keywordMatch[0]) + "\"\n Raw expression: " + (text.trim()) ); } else { errors.push( "invalid expression: " + (e.message) + " in\n\n" + " " + exp + "\n\n" + " Raw expression: " + (text.trim()) + "\n" ); } } } /* * * 建立一個函數 * */ //把字符串 轉成真正的js 而且以一個函數形式導出去 function createFunction(code, errors) { try { return new Function(code) } catch (err) { errors.push({err: err, code: code}); return noop } } //建立編譯函數 /********************************************************************************* *Function: createCompileToFunctionFn * Description: 函數科裏化 建立一個對象,而且把字符串轉換成 對象函數方式存在在對象中,導出去匿名函數 *Calls: *Called By: //調用本函數的清單 *Input: template 模板字符串 options參數 vm vnode節點 *Return: function 返回一個匿名函數 **********************************************************************************/ function createCompileToFunctionFn(compile) { //建立一個空的對象 var cache = Object.create(null); //函數科裏化 // 把字符串 編譯變成 真正的js 而且以對象函數方式導出去 /********************************************************************************* *Function: compileToFunctions * Description: 把字符串 編譯變成 真正的js 而且以對象函數方式導出去 *Calls: *Called By: *Input: template 模板字符串 options參數 vm vnode節點 *Return: object 對象函數 //函數返回值的說明 **********************************************************************************/ return function compileToFunctions( template, //字符串模板 options, //參數 vm //vmnode ) { //淺拷貝參數 options = extend({}, options); //警告 var warn$$1 = options.warn || warn; //刪除參數中的警告 delete options.warn; /* istanbul ignore if */ { // detect possible CSP restriction try { new Function('return 1'); } catch (e) { if (e.toString().match(/unsafe-eval|CSP/)) { warn$$1( 'It seems you are using the standalone build of Vue.js in an ' + 'environment with Content Security Policy that prohibits unsafe-eval. ' + 'The template compiler cannot work in this environment. Consider ' + 'relaxing the policy to allow unsafe-eval or pre-compiling your ' + 'templates into render functions.' ); } } } // check cache 攔阻索 /* *這個選項只在完整構建版本中的瀏覽器內編譯時可用。 * 詳細:改變純文本插入分隔符。 * * 示例: new Vue({ delimiters: ['${', '}'] }) // 分隔符變成了 ES6 模板字符串的風格 * * */ var key = options.delimiters ? String(options.delimiters) + template : template; if (cache[key]) { return cache[key] } // compile 傳進來的函數 var compiled = compile( template, //模板字符串 options //參數 ); console.log(compiled) // check compilation errors/tips { if (compiled.errors && compiled.errors.length) { warn$$1( "Error compiling template:\n\n" + template + "\n\n" + compiled.errors.map(function (e) { return ("- " + e); }).join('\n') + '\n', vm ); } if (compiled.tips && compiled.tips.length) { compiled.tips.forEach(function (msg) { return tip(msg, vm); }); } } // turn code into functions 將代碼轉換爲函數 var res = {}; var fnGenErrors = []; //將compiled.render建立一個函數,若是發生錯誤則記錄fnGenErrors錯誤 //把字符串 轉化成真正的js而且以 函數的方式導出去 res.render = createFunction( compiled.render, fnGenErrors); //字符串轉化js 建立一個集合函數 res.staticRenderFns = compiled.staticRenderFns.map(function (code) { return createFunction(code, fnGenErrors) }); // check function generation errors. // this should only happen if there is a bug in the compiler itself. // mostly for codegen development use /* istanbul ignore if */ //檢查函數生成錯誤。 //只有在編譯器自己存在錯誤時才應該這樣作。 //主要用於codegen開發 //伊斯坦布爾忽略若是*/ { if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) { warn$$1( "Failed to generate render function:\n\n" + fnGenErrors.map(function (ref) { var err = ref.err; var code = ref.code; return ((err.toString()) + " in\n\n" + code + "\n"); }).join('\n'), vm ); } } return (cache[key] = res) } } /* 建立編譯器 * *把字符串 轉化成真正的js函數 * */ /********************************************************************************* *Function: createCompilerCreator * Description: 函數科裏化 建立一個對象,而且把字符串轉換成 對象函數方式存在在對象中,導出去匿名函數 *Input: baseCompile 基本編譯函數 *Return: function 返回一個函數 **********************************************************************************/ function createCompilerCreator( baseCompile //基本的編譯函數 ) { console.log(baseCompile) return function createCompiler(baseOptions) { console.log(baseOptions) function compile( template, //字符串模板 options //options 參數 ) { console.log(options) //template 模板 options 參數 // 建立一個對象 拷貝baseOptions 拷貝到 原型 protype 中 var finalOptions = Object.create(baseOptions); //爲虛擬dom添加基本須要的屬性 console.log(finalOptions) console.log(finalOptions.__proto__) console.log(finalOptions.property) var errors = []; var tips = []; //聲明警告函數 finalOptions.warn = function (msg, tip) { (tip ? tips : errors).push(msg); }; if (options) { console.log(options) // merge custom modules //baseOptions中的modules參數爲 // modules=modules$1=[ // { // class 轉換函數 // staticKeys: ['staticClass'], // transformNode: transformNode, // genData: genData // }, // { //style 轉換函數 // staticKeys: ['staticStyle'], // transformNode: transformNode$1, // genData: genData$1 // }, // { // preTransformNode: preTransformNode // } // ] if (options.modules) { // finalOptions.modules = (baseOptions.modules || []).concat(options.modules); } // merge custom directives 合併定製指令 if (options.directives) { finalOptions.directives = extend(Object.create(baseOptions.directives || null), options.directives); } console.log(options) // options 爲: // comments: undefined // delimiters: undefined // shouldDecodeNewlines: false // shouldDecodeNewlinesForHref: true // copy other options 複製其餘選項 for (var key in options) { if (key !== 'modules' && key !== 'directives') { //淺拷貝 finalOptions[key] = options[key]; } } } //參數傳進來的函數 //template 模板 //finalOptions 基本參數 var compiled = baseCompile( template, //template 模板 finalOptions //finalOptions 基本參數 爲虛擬dom添加基本須要的屬性 ); { errors.push.apply(errors, detectErrors(compiled.ast)); } compiled.errors = errors; compiled.tips = tips; return compiled } /* * compile *在 render 函數中編譯模板字符串。只在獨立構建時有效 var res = Vue.compile('<div><span>{{ msg }}</span></div>') new Vue({ data: { msg: 'hello' }, render: res.render, staticRenderFns: res.staticRenderFns }) * * * * */ return { compile: compile, compileToFunctions: createCompileToFunctionFn(compile) } } } /* */ // `createCompilerCreator` allows creating compilers that use alternative 容許建立使用替代的編譯器 // parser/optimizer/codegen, e.g the SSR optimizing compiler. 解析器/優化/ codegen,e。SSR優化編譯器。 // Here we just export a default compiler using the default parts. 這裏咱們只是使用默認部分導出一個默認編譯器。 //編譯器建立的創造者 var createCompiler = createCompilerCreator( //把html變成ast模板對象,而後再轉換成 虛擬dom 渲染的函數參數形式。 // 返回出去一個對象 // {ast: ast, //ast 模板 // render: code.render, //code 虛擬dom須要渲染的參數函數 //staticRenderFns: code.staticRenderFns } //空數組 function baseCompile( template, //string模板 options // ) { /* template, //模板字符串 options 參數爲 原型中有baseOptions方法 { shouldDecodeNewlines: shouldDecodeNewlines, //flase //IE在屬性值中編碼換行,而其餘瀏覽器則不會 shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, //true chrome在a[href]中編碼內容 delimiters: options.delimiters, //改變純文本插入分隔符。修改指令的書寫風格,好比默認是{{mgs}} delimiters: ['${', '}']以後變成這樣 ${mgs} comments: options.comments //當設爲 true 時,將會保留且渲染模板中的 HTML 註釋。默認行爲是捨棄它們。 }, */ console.log(options) //返回ast模板對象 var ast = parse(template.trim(), options); if (options.optimize !== false) { //optimize 的主要做用是標記 static 靜態節點, // * 循環遞歸虛擬node,標記是否是靜態節點 //* 根據node.static或者 node.once 標記staticRoot的狀態 optimize(ast, options); } //初始化擴展指令,on,bind,cloak,方法, dataGenFns 獲取到一個數組,數組中有兩個函數genData和genData$1 //genElement根據el判斷是不是組件,或者是否含有v-once,v-if,v-for,是否有template屬性,或者是slot插槽,轉換style,css等轉換成虛擬dom須要渲染的參數函數 //返回對象{ render: ("with(this){return " + code + "}"),staticRenderFns: state.staticRenderFns} //空數組 var code = generate(ast, options); return { ast: ast, //ast 模板 render: code.render, //code 虛擬dom須要渲染的參數函數 staticRenderFns: code.staticRenderFns //空數組 } }); /* * * * */ //建立編譯獲取編譯對象函數 var ref$1 = createCompiler(baseOptions); //執行編譯對象函數 compileToFunctions 是一個函數 var compileToFunctions = ref$1.compileToFunctions; /* */ // check whether current browser encodes a char inside attribute values var div; //檢查a標籤是否有href 地址,若是有則渲染a標籤,若是沒有則渲染div標籤 // 判斷標籤屬性是不是真正的原生屬性 function getShouldDecode(href) { div = div || document.createElement('div'); div.innerHTML = href ? "<a href=\"\n\"/>" : "<div a=\"\n\"/>"; //html裏title屬性換行的方法: <div title="123& #10;456">text</div> return div.innerHTML.indexOf(' ') > 0 } // #3663: IE encodes newlines inside attribute values while other browsers don't //IE在屬性值中編碼換行,而其餘瀏覽器則不會 var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false; // #6828: chrome encodes content in a[href] //chrome在a[href]中編碼內容 var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false; /* * * * aFn 函數會屢次調用 裏面就能體現了 * 用對象去緩存記錄函數 * idToTemplate 是一個函數,根據key值來 取值,若是第二次的key仍是同樣則從對象中取值,而不是從新在執行一次函數 * */ var idToTemplate = cached(function (id) { var el = query(id); return el && el.innerHTML }); var mount = Vue.prototype.$mount; //緩存上一次的Vue.prototype.$mount // Vue 的$mount()爲手動掛載, // 在項目中可用於延時掛載(例如在掛載以前要進行一些其餘操做、判斷等),以後要手動掛載上。 // new Vue時,el和$mount並無本質上的不一樣。 Vue.prototype.$mount = function (el, hydrating) { //重寫Vue.prototype.$mount el = el && query(el); //獲取dom /* istanbul ignore if */ //若是el 是body 或者文檔 則警告 if (el === document.body || el === document.documentElement) { "development" !== 'production' && warn( "Do not mount Vue to <html> or <body> - mount to normal elements instead." ); return this } //獲取參數 var options = this.$options; // resolve template/el and convert to render function //解析模板/el並轉換爲render函數 if (!options.render) { //獲取模板字符串 var template = options.template; if (template) { //若是有模板 if (typeof template === 'string') { //模板是字符串 //模板第一個字符串爲# 則判斷該字符串爲 dom的id if (template.charAt(0) === '#') { console.log(template) template = idToTemplate(template); //獲取字符串模板的innerHtml console.log(template) /* istanbul ignore if */ if ("development" !== 'production' && !template) { warn( ("Template element not found or is empty: " + (options.template)), this ); } } } else if (template.nodeType) { //若是template 是don節點 則獲取他的html template = template.innerHTML; } else { //若是什麼都是否是則發出警告 { warn('invalid template option:' + template, this); } return this } } else if (el) { //若是模板沒有,dom節點存在則獲取dom節點中的html 給模板 template = getOuterHTML(el); console.log(template) } if (template) { /* istanbul ignore if */ //監聽性能監測 if ("development" !== 'production' && config.performance && mark) { mark('compile'); } //建立模板 console.log('==options.comments==') console.log(options.comments) var ref = compileToFunctions( template, //模板字符串 { shouldDecodeNewlines: shouldDecodeNewlines, //flase //IE在屬性值中編碼換行,而其餘瀏覽器則不會 shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, //true chrome在a[href]中編碼內容 delimiters: options.delimiters, //改變純文本插入分隔符。修改指令的書寫風格,好比默認是{{mgs}} delimiters: ['${', '}']以後變成這樣 ${mgs} comments: options.comments //當設爲 true 時,將會保留且渲染模板中的 HTML 註釋。默認行爲是捨棄它們。 }, this ); // res.render = createFunction(compiled.render, fnGenErrors); //獲取編譯函數 是將字符串轉化成真正js的函數 console.log('==ref.render==') console.log(ref.render) console.log(ref) console.log('==ref.render-end==') // res.render = createFunction(compiled.render, fnGenErrors); // //字符串轉化js 建立一個集合函數 // res.staticRenderFns = compiled.staticRenderFns.map(function (code) { // return createFunction(code, fnGenErrors) // }); // ast: ast, //ast 模板 //render: code.render, //code 虛擬dom須要渲染的參數函數 //staticRenderFns: code.staticRenderFns //空數組 //這樣賦值能夠有效地 防止 引用按地址引用,形成數據修改而其餘對象也修改問題, var render = ref.render; var staticRenderFns = ref.staticRenderFns; /* render 是 虛擬dom,須要執行的編譯函數 相似於這樣的函數 (function anonymous( ) { with(this){return _c('div',{attrs:{"id":"app"}},[_c('input',{directives:[{name:"info",rawName:"v-info"},{name:"data",rawName:"v-data"}],attrs:{"type":"text"}}),_v(" "),_m(0)])} }) */ options.render = render; options.staticRenderFns = staticRenderFns; console.log(options); console.log(options.render); /* istanbul ignore if */ if ("development" !== 'production' && config.performance && mark) { mark('compile end'); measure(("vue " + (this._name) + " compile"), 'compile', 'compile end'); } } } console.log(render) console.log(el) console.log(hydrating) //執行$mount方法 一共執行了兩次 第一次是在9000多行那一個 用$mount的方法把擴展掛載到dom上 return mount.call( this, el, //真實的dom hydrating //undefined ) }; /** * Get outerHTML of elements, taking care * of SVG elements in IE as well. *獲取 dom的html //outerHTML 輸出當前標籤的自己和標籤內的文本內容,若是有子標籤,那麼子標籤自己和標籤內的文本內容也將一塊兒輸出 */ function getOuterHTML(el) { if (el.outerHTML) { // return el.outerHTML } else { //建立一個div節點 而且 包裹着el var container = document.createElement('div'); container.appendChild(el.cloneNode(true)); return container.innerHTML } } Vue.compile = compileToFunctions; return Vue; })));