vue源碼業餘時間差不多看了一年,以前在網上找帖子,發現很多帖子很零散,都是一部分一部分說,斷章的很多,所以自己下定決定一行行看,經過自己堅持與努力,現在基本看完了,差ddf那部分,因為考慮到自己要換工作了,所以暫緩下來先,ddf那塊後期我會補上去。這個vue源碼逐行分析,我基本每一行都打上註釋,加 ...
vue源碼業餘時間差不多看了一年,以前在網上找帖子,發現很多帖子很零散,都是一部分一部分說,斷章的很多,所以自己下定決定一行行看,經過自己堅持與努力,現在基本看完了,差ddf那部分,因為考慮到自己要換工作了,所以暫緩下來先,ddf那塊後期我會補上去。這個vue源碼逐行分析,我基本每一行都打上註釋,加上整個框架的流程思維導圖,基本上是小白也能看懂的vue源碼了。
說的非常的詳細,裡面的源碼註釋,有些是參考網上帖子的,有些是自己多年開發vue經驗而猜測的,有些是自己跑上下文程式知道的,本人水平可能有限,不一定是很正確,如果有不足的地方可以聯繫我QQ群 :302817612 修改,或者發郵件給我[email protected] 謝謝。
1.vue源碼解讀流程 1.nwe Vue 調用的是 Vue.prototype._init 從該函數開始 經過 $options 參數合併之後 initLifecycle 初始化生命周期標誌 初始化事件,初始化渲染函數。初始化狀態就是數據。把數據添加到觀察者中實現雙數據綁定。
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元素。
具體看我源碼和流程圖,這裡文字就不描述這麼多了,流程圖是下麵這中的網盤,源碼是vue.js,基本每一行都有註釋,然後ddf待更新中。
程式流程圖太大了沒法線上看,只能網盤下載到本地看了,給一個大概圖
鏈接:https://pan.baidu.com/s/10IxV6mQ2TIwkRACKu2T0ng
提取碼:1fnu
github源碼,包括平常我看vue中的一些小demo
https://github.com/qq281113270/vue-ddf-
/*! * 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))