源碼分享者:姚觀壽
vue源碼業余時間差不多看了一年,以前在網上找帖子,發現很多帖子很零散,都是一部分一部分說,斷章的很多,所以自己下定決定一行行看,經過自己堅持與努力,現在基本看完了, 。這個vue源碼逐行分析,我基本每一行都打上注釋,加上整個框架的流程思維導圖,基本上是小白也能看懂的vue源碼了。
說的非常的詳細,里面的源碼注釋,有些是參考網上帖子的,有些是自己多年開發vue經驗而猜測的,有些是自己跑上下文程序知道的,本人水平可能有限,不一定是很正確,如果有不足的地方可以聯系我QQ群 :302817612 修改,或者發郵件給我281113270@qq.com 謝謝。
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,基本每一行都有注釋,然后diff待更新中。
程序流程圖太大了沒法在線看,只能網盤下載到本地看了,給一個大概圖

鏈接:https://pan.baidu.com/s/10IxV6mQ2TIwkRACKu2T0ng
提取碼:1fnu
github源碼,包括平常我看vue中的一些小demo。看到該帖子朋友,幫忙去github點贊下,謝謝,有你的支持,我會更加努力。
https://github.com/qq281113270/vue
/*!
* 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;
})));
