雖然vue3已經(jīng)出來很久了,但我覺得vue.js的源碼還是非常值得去學(xué)習(xí)一下。vue.js里面封裝的很多工具類在我們平時(shí)工作項(xiàng)目中也會(huì)經(jīng)常用到。所以我近期會(huì)對(duì)vue.js的源碼進(jìn)行解讀,分享值得去學(xué)習(xí)的代碼片段,這篇文章將會(huì)持續(xù)更新。
一、哪些值得我們學(xué)習(xí)的地方:
1.代碼嚴(yán)謹(jǐn),做了很多值和類型判斷的工具類.例如判斷已定義、未定義、判斷原始類型、判斷對(duì)象等等。
2.源碼中多處使用到call,bind,apply,所以學(xué)習(xí)call,apply,bind很重要,請(qǐng)閱讀我的另一篇文章可進(jìn)行學(xué)習(xí):(http://www.lxweimin.com/p/0e145af54212)
- Object.freeze凍結(jié)對(duì)象,可以提高性能。
4. 高階函數(shù):(很多大廠面試可能會(huì)問到)
①cached函數(shù) 創(chuàng)建純函數(shù)的緩存版本。
高階函數(shù)cached函數(shù),輸入?yún)?shù)為函數(shù),返回值為函數(shù)。利用了閉包變量不會(huì)被回收的特點(diǎn),
可以緩存變量,下次再調(diào)用的時(shí)候可以從緩存中讀取,如果存在緩存就使用緩存,如果不存在就重新計(jì)
算下
②looseEqual
高級(jí)函數(shù) 對(duì)對(duì)象的淺相等進(jìn)行判斷 ES6有一個(gè)方法來判斷兩個(gè)對(duì)象是否相等 Object.is()
這個(gè)方法判斷的是a和b是不是同一個(gè)指針的對(duì)象
判斷a、b兩個(gè)集合是否相等,如果a包含于b,且b包含于a,則 A = B 判斷兩個(gè)對(duì)象相等 (判斷兩個(gè)對(duì)象鍵名與鍵值對(duì)應(yīng)相同 而非指引用地址相同)
③polyfillBind
高級(jí)函數(shù),簡單綁定polyfill,用于不支持它的環(huán)境,例如PhantomJS 1.x;
Polyfill是一個(gè)js庫,主要撫平不同瀏覽器之間對(duì)js實(shí)現(xiàn)的差異
④passive
passived主要用于優(yōu)化瀏覽器頁面滾動(dòng)的性能,Chrome提出的一個(gè)新的瀏覽器特性:Web開發(fā)者通過一個(gè)新的屬性passive來告訴瀏覽器,
當(dāng)前頁面內(nèi)注冊(cè)的事件監(jiān)聽器內(nèi)部是否會(huì)調(diào)用preventDefault函數(shù)來阻止事件的默認(rèn)行為,
以便瀏覽器根據(jù)這個(gè)信息更好地做出決策來優(yōu)化頁面性能。當(dāng)屬性passive的值為true的時(shí)候,
代表該監(jiān)聽器內(nèi)部不會(huì)調(diào)用preventDefault函數(shù)來阻止默認(rèn)滑動(dòng)行為,Chrome瀏覽器稱這類型的監(jiān)聽器為被動(dòng)
(passive)監(jiān)聽器。目前Chrome主要利用該特性來優(yōu)化頁面的滑動(dòng)性能,所以Passive Event Listeners特性
當(dāng)前僅支持mousewheel/touch相關(guān)事件
2.Passive Event Listeners特性是為了提高頁面的滑動(dòng)流暢度而設(shè)計(jì)的,頁面滑動(dòng)流暢度的提升, 直接影響到用戶對(duì)這個(gè)頁面最直觀的感受。
3.passive的簡單實(shí)現(xiàn) function handler(event) {
console.log(event.type); } document.addEventListener("mousewheel", handler, {passive:true});
二、vue核心
①數(shù)據(jù)監(jiān)聽最重要之一的 Dep
dep是一個(gè)可觀察的,可以有多個(gè)訂閱它的指令 ,是訂閱者Watcher對(duì)應(yīng)的數(shù)據(jù)依賴 , Dep 相當(dāng)于把
Observe 監(jiān)聽到的信號(hào)做一個(gè)收集 ,然后通過dep.notify()再通知到對(duì)應(yīng) Watcher ,從而進(jìn)行視圖更新。
在vue中我們要實(shí)現(xiàn)Dep和watcher Dep的主要作用是收集依賴 在vue中的每一個(gè)響應(yīng)屬性,都會(huì)創(chuàng)建一個(gè)dep對(duì)象負(fù)責(zé)手機(jī)依賴于該屬性的所有依賴 即訂閱者 并在數(shù)據(jù)更新時(shí)候發(fā)布通知,調(diào)用watcher對(duì)象中的update方法去更新視圖簡單說明就是在數(shù)據(jù)劫持監(jiān)聽中的get去添加依賴 在set中去發(fā)布通知
②視圖更新最重要的 VNode
VNode是基于面向?qū)ο筮M(jìn)行設(shè)計(jì)的 將template模板描述成 VNode,然后一系列操作之后通過VNode形成真實(shí)DOM進(jìn)行掛載
更新的時(shí)候?qū)Ρ扰f的VNode和新的VNode,只更新有變化的那一部分,提高視圖更新速度
④雙向綁定Observer:遍歷對(duì)象的所有屬性將其進(jìn)行雙向綁定
Observer為數(shù)據(jù)加上響應(yīng)式屬性進(jìn)行雙向綁定。如果是對(duì)象則進(jìn)行深度遍歷,為每一個(gè)子對(duì)象都綁定上方法,如果是數(shù)組則為每一個(gè)成員都綁定上方法。
如果是修改一個(gè)數(shù)組的成員,該成員是一個(gè)對(duì)象,那只需要遞歸對(duì)數(shù)組的成員進(jìn)行雙向綁定即可。
監(jiān)聽數(shù)組:從數(shù)組的原型新建一個(gè)Object.create(arrayProto)對(duì)象,通過修改此原型可以保證原生數(shù)組方法不被污染。如果當(dāng)前瀏覽器支持proto這個(gè)屬性的話就可以直接覆蓋該屬性則使數(shù)組對(duì)象具有了重寫后的數(shù)組方法。如果沒有該屬性的瀏覽器,則必須通過遍歷def所有需要重寫的數(shù)組方法
⑤派發(fā)更新:defineReactive 函數(shù)(響應(yīng)式的核心函數(shù))
defineReactive 函數(shù)最開始初始化 Dep 對(duì)象的實(shí)例,接著拿到 obj 的屬性描述符, 然后對(duì)子對(duì)象遞歸調(diào)用 observe
方法,這樣就保證了無論 obj 的結(jié)構(gòu)多復(fù)雜, 它的所有子屬性也能變成響應(yīng)式的對(duì)象,這樣我們?cè)L問或修改 obj 中一個(gè)嵌套較深的屬性,
也能觸發(fā) getter 和 setter。最后利用 Object.defineProperty 去給 obj 的屬性 key 添加
getter 和 setter。 核心就是利用 Object.defineProperty 給數(shù)據(jù)添加了 getter 和
setter,目的就是 為了在我們?cè)L問數(shù)據(jù)以及寫數(shù)據(jù)的時(shí)候能自動(dòng)執(zhí)行一些邏輯:getter 做的事情是依賴收集,setter
做的事情是派發(fā)更新
三、1~1200行的代碼解讀:
//AMD規(guī)范和commonJS規(guī)范,都是為了模塊化
//AMD規(guī)范則是非同步加載模塊,允許指定回調(diào)函數(shù)。
//CommonJS規(guī)范加載模塊是同步的,也就是說,只有加載完成,才能執(zhí)行后面的操作。
//是個(gè)匿名函數(shù),該匿名函數(shù)并沒自執(zhí)行 設(shè)計(jì)參數(shù) window,并傳入window對(duì)象。不污染全局變量,也不會(huì)被別的代碼污染
(function (global, factory) {
//檢查 CommonJS,CommonJS模塊作用域,每一個(gè)文件就是一個(gè)模塊,擁有自己獨(dú)立的作用域,變量,以及方法等,對(duì)其他的模塊都不可見,
//所有代碼都運(yùn)行在模塊作用域,不會(huì)污染全局作用域,
//模塊可以多次加載,但是只會(huì)在第一次加載時(shí)運(yùn)行一次,然后運(yùn)行結(jié)果就被緩存了,以后再加載,就直接讀取緩存結(jié)果。
//要想讓模塊再次運(yùn)行,必須清除緩存。模塊加載的順序,按照其在代碼中出現(xiàn)的順序
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
//AMD異步模塊定義 檢查JavaScript依賴管理庫 require.js 的存在
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Vue = factory());
}(this, function () {
'use strict';
/* */
//凍結(jié)對(duì)象,一個(gè)被凍結(jié)的對(duì)象再也不能被修改。可以使用Object.freeze提升性能
var emptyObject = Object.freeze({});
//判斷未定義
function isUndef (v) {
return v === undefined || v === null
}
//判斷已定義
function isDef (v) {
return v !== undefined && v !== null
}
function isTrue (v) {
return v === true
}
function isFalse (v) {
return v === false
}
/**
* 判斷為原始類型
*/
function isPrimitive (value) {
return (
typeof value === 'string' ||
typeof value === 'number' ||
// $flow-disable-line
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}
// 判斷為對(duì)象
function isObject (obj) {
return obj !== null && typeof obj === 'object'
}
/**
* 獲取值的原始類型字符串,例如,[對(duì)象]
*/
var _toString = Object.prototype.toString;
// 切割引用類型得到后面的基本類型,例如:[object RegExp] 得到的就是 RegExp
function toRawType (value) {
return _toString.call(value).slice(8, -1)
}
//判斷純粹的對(duì)象:"純粹的對(duì)象",就是通過 {}、new Object()、Object.create(null) 創(chuàng)建的對(duì)象
function isPlainObject (obj) {
return _toString.call(obj) === '[object Object]'
}
// 判斷原生引用類型
function isRegExp (v) {
return _toString.call(v) === '[object RegExp]'
}
/**
* 檢查val是否是有效的數(shù)組索引,驗(yàn)證是否是一個(gè)非無窮大的正整數(shù)。
*/
function isValidArrayIndex (val) {
var n = parseFloat(String(val));
return n >= 0 && Math.floor(n) === n && isFinite(val)
}
// 判斷是否是Promise
function isPromise (val) {
return (
isDef(val) &&
typeof val.then === 'function' &&
typeof val.catch === 'function'
)
}
/**
* 將值轉(zhuǎn)換為字符串。
*/
function toString (val) {
return val == null
? ''
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2)
: String(val)
}
/**
將輸入值轉(zhuǎn)換為數(shù)字以便持久化。如果轉(zhuǎn)換失敗,則返回原始字符串。
*/
function toNumber (val) {
var n = parseFloat(val);
return isNaN(n) ? val : n
}
/**
* makeMap 方法將字符串切割,放到map中,用于校驗(yàn)其中的某個(gè)字符串是否存在(區(qū)分大小寫)于map中
*/
function makeMap (
str,
expectsLowerCase
) {
var map = Object.create(null);
var list = str.split(',');
for (var i = 0; i < list.length; i++) {
map[list[i]] = true;
}
return expectsLowerCase
? function (val) { return map[val.toLowerCase()]; }
: function (val) { return map[val]; }
}
/**
* 檢查標(biāo)記是否為內(nèi)置標(biāo)記。
*/
var isBuiltInTag = makeMap('slot,component', true);
/**
* 檢查屬性是否為保留屬性。
*/
var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');
/**
* 從數(shù)組中刪除項(xiàng)
*/
function remove (arr, item) {
if (arr.length) {
var index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1)
}
}
}
/**
* 檢查對(duì)象是否具有該屬性。
* hasOwnProperty() 方法會(huì)返回一個(gè)布爾值,指示對(duì)象自身屬性中是否具有指定的屬性(也就是,是否有指定的鍵)。
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn (obj, key) {
return hasOwnProperty.call(obj, key)
}
/**
* 創(chuàng)建純函數(shù)的緩存版本。
* 高階函數(shù)cached函數(shù),輸入?yún)?shù)為函數(shù),返回值為函數(shù)。利用了閉包變量不會(huì)被回收的特點(diǎn),
* 可以緩存變量,下次再調(diào)用的時(shí)候可以從緩存中讀取,如果存在緩存就使用緩存,如果不存在就重新計(jì)算下
*/
function cached (fn) {
var cache = Object.create(null);
return (function cachedFn (str) {
var hit = cache[str];
return hit || (cache[str] = fn(str))
})
}
/**
* 駝峰化一個(gè)連字符連接的字符串
*/
var camelizeRE = /-(\w)/g;
var camelize = cached(function (str) {
return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
});
/**
* 將字符串首字母大寫。
*/
var capitalize = cached(function (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
});
/**
* 用字符號(hào)連接一個(gè)駝峰的字符串
*/
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cached(function (str) {
return str.replace(hyphenateRE, '-$1').toLowerCase()
});
/**
* 高級(jí)函數(shù),簡單綁定polyfill,用于不支持它的環(huán)境,例如PhantomJS 1.x;
* Polyfill是一個(gè)js庫,主要撫平不同瀏覽器之間對(duì)js實(shí)現(xiàn)的差異
*/
/* 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)
}
Function.prototype.bind()
// bind() 方法創(chuàng)建一個(gè)新的函數(shù),在 bind() 被調(diào)用時(shí),這個(gè)新函數(shù)的 this 被指定為 bind() 的第一個(gè)參數(shù),
// 而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時(shí)使用。
var bind = Function.prototype.bind
? nativeBind
: polyfillBind;
/**
* 將類似數(shù)組的對(duì)象轉(zhuǎn)換為實(shí)數(shù)組
*/
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
}
/**
* 將多個(gè)屬性插入目標(biāo)的對(duì)象
*/
function extend (to, _from) {
for (var key in _from) {
to[key] = _from[key];
}
return to
}
/**
* 將對(duì)象數(shù)組合并為單個(gè)對(duì)象。
*/
function toObject (arr) {
var res = {};
for (var i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i]);
}
}
return res
}
/* eslint-disable no-unused-vars */
/**
* 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) {}
/**
* 總是返回false。
*/
var no = function (a, b, c) { return false; };
/* eslint-enable no-unused-vars */
/**
* 返回相同的值
*/
var identity = function (_) { return _; };
/**
* 從編譯器模塊生成包含靜態(tài)鍵的字符串。
*/
function genStaticKeys (modules) {
return modules.reduce(function (keys, m) {
return keys.concat(m.staticKeys || [])
}, []).join(',')
}
/**高級(jí)函數(shù) 對(duì)對(duì)象的淺相等進(jìn)行判斷
* ES6有一個(gè)方法來判斷兩個(gè)對(duì)象是否相等 Object.is() 這個(gè)方法判斷的是a和b是不是同一個(gè)指針的對(duì)象
* 判斷a、b兩個(gè)集合是否相等,如果a包含于b,且b包含于a,則 A = B
*判斷兩個(gè)對(duì)象相等 (判斷兩個(gè)對(duì)象鍵名與鍵值對(duì)應(yīng)相同 而非指引用地址相同)
*/
function looseEqual (a, b) {
//判斷是否恒相等
if (a === b) { return true }
//判斷是否為對(duì)象
var isObjectA = isObject(a);
var isObjectB = isObject(b);
if (isObjectA && isObjectB) {
try {
// 當(dāng)a,b都是數(shù)組時(shí)
var isArrayA = Array.isArray(a);
var isArrayB = Array.isArray(b);
if (isArrayA && isArrayB) {
//遞歸判斷兩個(gè)數(shù)組中的每一項(xiàng)
return a.length === b.length && a.every(function (e, i) {
return looseEqual(e, b[i])
})
// 否則判斷a,b是否為Date類型,
} else if (a instanceof Date && b instanceof Date) {
//使a和b恒相等
return a.getTime() === b.getTime()
//當(dāng)a,b是對(duì)象時(shí),首先判斷l(xiāng)ength長度是否相同,長度相同再判斷每個(gè)屬性對(duì)應(yīng)的屬于值是否相同
} else if (!isArrayA && !isArrayB) {
var keysA = Object.keys(a);
var keysB = Object.keys(b);
return keysA.length === keysB.length && keysA.every(function (key) {
return looseEqual(a[key], b[key])
})
} else {
return false
}
} catch (e) {
return false
}
} else if (!isObjectA && !isObjectB) {
return String(a) === String(b)
} else {
return false
}
}
/**
* 返回索引,如果沒找到返回-1,否則執(zhí)行l(wèi)ooseEqual()
*/
function looseIndexOf (arr, val) {
for (var i = 0; i < arr.length; i++) {
if (looseEqual(arr[i], val)) { return i }
}
return -1
}
/**
* 確保函數(shù)只調(diào)用一次。
*/
function once (fn) {
var called = false;
return function () {
if (!called) {
called = true;
fn.apply(this, arguments);
}
}
}
// cached
// polyfillBind
// looseEqual
//閉包,類型判斷,函數(shù)之間的相互調(diào)用調(diào)用
//定義變量
var SSR_ATTR = 'data-server-rendered';// 服務(wù)端渲染
// 全局函數(shù) component、directive、filter
var ASSET_TYPES = [
'component',
'directive',
'filter'
];
// 生命周期
var LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured',
'serverPrefetch'
];
/* */
// 全局配置
var config = ({
/**
* 選項(xiàng)合并策略(用于core/util/options)
*/
// $flow-disable-line
optionMergeStrategies: Object.create(null),
/**
* 是否抑制警告
*/
silent: false,
/**
* 啟動(dòng)時(shí)顯示生產(chǎn)模式提示消息?
*/
productionTip: "development" !== 'production',
/**
* 是否啟用devtools
*/
devtools: "development" !== 'production',
/**
* 是否記錄性能
*/
performance: false,
/**
* 觀察程序錯(cuò)誤的錯(cuò)誤處理程序
*/
errorHandler: null,
/**
* 觀察者警告的警告處理程序
*/
warnHandler: null,
/**
* 忽略某些自定義元素
*/
ignoredElements: [],
/**
* v-on的自定義用戶密鑰別名
*/
// $flow-disable-line
keyCodes: Object.create(null),
/**
* 檢查標(biāo)記是否已保留,以便無法將其注冊(cè)為組件。這取決于平臺(tái),可能會(huì)被覆蓋。
*/
isReservedTag: no,
/**
檢查屬性是否已保留,以使其不能用作組件屬性。這取決于平臺(tái),可能會(huì)被覆蓋。
*/
isReservedAttr: no,
/**
* 檢查標(biāo)記是否為未知元素。依賴于平臺(tái)。
*/
isUnknownElement: no,
/**
* 獲取元素的命名空間
*/
getTagNamespace: noop,
/**
* 解析特定平臺(tái)的真實(shí)標(biāo)記名。
*/
parsePlatformTagName: identity,
/**
* 檢查屬性是否必須使用屬性綁定,例如valuePlatform dependent。
*/
mustUseProp: no,
/**
* 異步執(zhí)行更新。打算由Vue Test Utils使用,如果設(shè)置為false,這將顯著降低性能。
*/
async: true,
/**
* 因遺留原因暴露
*/
_lifecycleHooks: LIFECYCLE_HOOKS
});
/* */
/**
用于解析html標(biāo)記、組件名稱和屬性路徑的unicode字母。
使用https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname
*/
var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;
/**
* 檢查字符串是否以$或_開頭
*/
function isReserved (str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5F
}
/**
* 定義屬性。
* 在一個(gè)對(duì)象上定義一個(gè)屬性的構(gòu)造函數(shù),其中 !!enumerable 強(qiáng)制轉(zhuǎn)換 boolean
*/
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
/**
* 解析簡單路徑。
*/
var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]"));
function parsePath (path) {
if (bailRE.test(path)) {
return
}
var segments = path.split('.');
return function (obj) {
for (var i = 0; i < segments.length; i++) {
if (!obj) { return }
obj = obj[segments[i]];
}
return obj
}
}
/* */
var hasProto = '__proto__' in {};
// 判斷瀏覽器環(huán)境
var inBrowser = typeof window !== 'undefined';
// 運(yùn)行環(huán)境是微信
var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform;
var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
//瀏覽器 UA 判斷
var UA = inBrowser && window.navigator.userAgent.toLowerCase();
// IE的內(nèi)核是trident
var isIE = UA && /msie|trident/.test(UA);
var isIE9 = UA && UA.indexOf('msie 9.0') > 0;
var isEdge = UA && UA.indexOf('edge/') > 0;
// 判斷 android ios
var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android');
var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios');
// 判斷chrome
var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge;
var isPhantomJS = UA && /phantomjs/.test(UA);
var isFF = UA && UA.match(/firefox\/(\d+)/);
// Firefox has a "watch" function on Object.prototype...
var nativeWatch = ({}).watch;
/*https://blog.csdn.net/dj0379/article/details/52883315
1.passive是什么?passived主要用于優(yōu)化瀏覽器頁面滾動(dòng)的性能,Chrome提出的一個(gè)新的瀏覽器特性:Web開發(fā)者通過一個(gè)新的屬性passive來告訴瀏覽器,
當(dāng)前頁面內(nèi)注冊(cè)的事件監(jiān)聽器內(nèi)部是否會(huì)調(diào)用preventDefault函數(shù)來阻止事件的默認(rèn)行為,
以便瀏覽器根據(jù)這個(gè)信息更好地做出決策來優(yōu)化頁面性能。當(dāng)屬性passive的值為true的時(shí)候,
代表該監(jiān)聽器內(nèi)部不會(huì)調(diào)用preventDefault函數(shù)來阻止默認(rèn)滑動(dòng)行為,Chrome瀏覽器稱這類型的監(jiān)聽器為被動(dòng)
(passive)監(jiān)聽器。目前Chrome主要利用該特性來優(yōu)化頁面的滑動(dòng)性能,所以Passive Event Listeners特性
當(dāng)前僅支持mousewheel/touch相關(guān)事件
2.Passive Event Listeners特性是為了提高頁面的滑動(dòng)流暢度而設(shè)計(jì)的,頁面滑動(dòng)流暢度的提升,
直接影響到用戶對(duì)這個(gè)頁面最直觀的感受。
3.passive的簡單實(shí)現(xiàn)
function handler(event) {
console.log(event.type);
}
document.addEventListener("mousewheel", handler, {passive:true});
*/
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) {}
}
// 視圖服務(wù)器渲染器可以設(shè)置視圖環(huán)境
var _isServer;
var isServerRendering = function () {
if (_isServer === undefined) {
if (!inBrowser && !inWeex && typeof global !== 'undefined') {
// 檢測(cè)是否存在vue服務(wù)器呈現(xiàn)程序并避免網(wǎng)頁包填充進(jìn)程
_isServer = global['process'] && global['process'].env.VUE_ENV === 'server';
} else {
_isServer = false;
}
}
return _isServer
};
var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
// 判斷一個(gè)函數(shù)是否為 JavaScript 內(nèi)置方法的方法
function isNative (Ctor) {
return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}
// 判斷是否含有 Symbol 類型
var hasSymbol =
typeof Symbol !== 'undefined' && isNative(Symbol) &&
typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys);
var _Set;
//判斷Set是否為內(nèi)置對(duì)象
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 = /*@__PURE__*/(function () {
function Set () {
this.set = Object.create(null);
}
// Set.prototype.has()方法接受一個(gè)參數(shù),返回一個(gè)布爾值,表明該參數(shù)是否存在于Set對(duì)象中
Set.prototype.has = function has (key) {
return this.set[key] === true
};
// Set.prototype.add()方法用于向Set對(duì)象末尾添加一個(gè)指定的值,并返回Set對(duì)象本身。如果值已存在,則添加不會(huì)成功
Set.prototype.add = function add (key) {
this.set[key] = true;
};
// 用來清空一個(gè)Set對(duì)象的所有元素,沒有返回值
Set.prototype.clear = function clear () {
this.set = Object.create(null);
};
return Set;
}());
}
/* 日志信息模塊 */
var warn = noop;
var tip = noop;
//generateComponentTrace這個(gè)function,這個(gè)方法初始化的值也是noop,什么都沒有做,目的是解決流量檢查問題
var generateComponentTrace = (noop);
var formatComponentName = (noop);
{
var hasConsole = typeof console !== 'undefined';
// 這個(gè)正則就是把連接符轉(zhuǎn)換成的駝峰寫法, 并且第一個(gè)字符大寫^|[-_]的意思是字符串的開頭, 或者 -_ 后面的一個(gè)字符
var classifyRE = /(?:^|[-_])(\w)/g;
var classify = function (str) { return str
.replace(classifyRE, function (c) { return c.toUpperCase(); })
.replace(/[-_]/g, ''); };
//控制臺(tái)打印錯(cuò)誤提示
warn = function (msg, vm) {
var trace = vm ? generateComponentTrace(vm) : '';
//如果配置的console.silent, 就不會(huì)打印錯(cuò)誤日志
if (config.warnHandler) {
config.warnHandler.call(null, msg, vm, trace);
} else if (hasConsole && (!config.silent)) {
console.error(("[Vue warn]: " + msg + trace));
}
};
//控制臺(tái)打警告提示
tip = function (msg, vm) {
if (hasConsole && (!config.silent)) {
console.warn("[Vue tip]: " + msg + (
vm ? generateComponentTrace(vm) : ''
));
}
};
//格式化組件名稱
formatComponentName = function (vm, includeFile) {
//如果是根組件, 它會(huì)有一個(gè)屬性.$root 指向它自己
if (vm.$root === vm) {
return '<Root>'
}
// 先判斷option有沒自定義name,如果沒有就用vm.name, 這個(gè)name應(yīng)該是vue自己配置的一個(gè)隨機(jī)數(shù)
var options = typeof vm === 'function' && vm.cid != null
? vm.options
: vm._isVue
? vm.$options || vm.constructor.options
: vm;
var name = options.name || options._componentTag;
var file = options.__file;
if (!name && file) {
//如果沒有name,但存在此文件,則取文件名
var match = file.match(/([^/\\]+)\.vue$/);
name = match && match[1];
}
return (
//返回駝峰組件名稱
(name ? ("<" + (classify(name)) + ">") : "<Anonymous>") +
//提示文件路徑出錯(cuò)
(file && includeFile !== false ? (" at " + file) : '')
)
};
//返回一個(gè)str循環(huán)n次的字符串,例如str="a",n=5,則返回"aaaaa"
var repeat = function (str, n) {
var res = '';
while (n) {
if (n % 2 === 1) { res += str; }
if (n > 1) { str += str; }
n >>= 1;
}
return res
};
//生成組件跟蹤路徑(組件數(shù)規(guī)則)
generateComponentTrace = function (vm) {
if (vm._isVue && vm.$parent) {
var tree = [];
var currentRecursiveSequence = 0;
while (vm) {
if (tree.length > 0) {
var last = tree[tree.length - 1];
if (last.constructor === vm.constructor) {
currentRecursiveSequence++;
vm = vm.$parent;
continue
} else if (currentRecursiveSequence > 0) {
tree[tree.length - 1] = [last, currentRecursiveSequence];
currentRecursiveSequence = 0;
}
}
tree.push(vm);
vm = vm.$parent;
}
return '\n\nfound in\n\n' + tree
.map(function (vm, i) { return ("" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + (Array.isArray(vm)
? ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)")
: formatComponentName(vm))); })
.join('\n')
} else {
return ("\n\n(found in " + (formatComponentName(vm)) + ")")
}
};
}
var uid = 0;
// Vue核心:數(shù)據(jù)監(jiān)聽最重要之一的 Dep
// dep是一個(gè)可觀察的,可以有多個(gè)訂閱它的指令
// Dep是訂閱者Watcher對(duì)應(yīng)的數(shù)據(jù)依賴
// Dep 相當(dāng)于把 Observe 監(jiān)聽到的信號(hào)做一個(gè)收集
// 然后通過dep.notify()再通知到對(duì)應(yīng) Watcher ,從而進(jìn)行視圖更新。
// 要實(shí)現(xiàn)數(shù)據(jù)的響應(yīng)機(jī)制 即數(shù)據(jù)變化 視圖變化
// 在vue的響應(yīng)機(jī)制中 我們要使用觀察模式來監(jiān)聽數(shù)據(jù)的變化
// 因此 在vue中我們要實(shí)現(xiàn)Dep和watcher Dep的主要作用是收集依賴 在vue中的每一個(gè)響應(yīng)屬性
// 都會(huì)創(chuàng)建一個(gè)dep對(duì)象 負(fù)責(zé)手機(jī)依賴于該屬性的所有依賴 即訂閱者 并在數(shù)據(jù)更新時(shí)候發(fā)布通知
// 調(diào)用watcher對(duì)象中的update方法去更新視圖 簡單說明就是在數(shù)據(jù)劫持監(jiān)聽中的get去添加依賴 在set中去發(fā)布通知
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
//向Dep實(shí)例中的subs數(shù)組中添加監(jiān)視器對(duì)象
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
//刪除Dep實(shí)例中subs數(shù)組中指定的監(jiān)視器對(duì)象
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
//設(shè)置某個(gè)Watcher的依賴
//這里添加了Dep.target是否存在的判斷,目的是判斷是不是Watcher的構(gòu)造函數(shù)調(diào)用
//也就是說判斷他是Watcher的this.get調(diào)用的,而不是普通調(diào)用
//如果target存在就會(huì)繼續(xù)監(jiān)聽
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
//通知監(jiān)聽者
// 變量Dep的subs數(shù)組中的監(jiān)視器并調(diào)用其的update方法
Dep.prototype.notify = function notify () {
var subs = this.subs.slice();
if (!config.async) {
// 如果未運(yùn)行異步,則不會(huì)在調(diào)度程序中對(duì)sub進(jìn)行排序
subs.sort(function (a, b) { return a.id - b.id; });
}
//通知所有綁定 Watcher。調(diào)用watcher的update()
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
// 正在評(píng)估的當(dāng)前目標(biāo)觀察程序。
Dep.target = null;
var targetStack = [];
// 如果Dep實(shí)例的target的值為真向targetStack數(shù)組尾部添加此監(jiān)視器,設(shè)置當(dāng)前Dep實(shí)例的target為傳入的監(jiān)視器。
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
// 從targetStack數(shù)組中的頭部取出一個(gè)監(jiān)視器對(duì)象賦值給Dep實(shí)例的target屬性。
function popTarget () {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
// VNode是基于面向?qū)ο筮M(jìn)行設(shè)計(jì)的
// 視圖更新最重要的VNode
// 將template模板描述成 VNode,然后一系列操作之后通過VNode形成真實(shí)DOM進(jìn)行掛載
// 更新的時(shí)候?qū)Ρ扰f的VNode和新的VNode,只更新有變化的那一部分,提高視圖更新速度
var VNode = function VNode (
tag,
data,
children,
text,
elm,
context,
componentOptions,
asyncFactory
) {
this.tag = tag; //標(biāo)簽屬性
this.data = data;//渲染成真實(shí)DOM后,節(jié)點(diǎn)上到class attr style 事件等
this.children = children;//子節(jié)點(diǎn),也上vnode
this.text = text; // 文本
this.elm = elm;//對(duì)應(yīng)著真實(shí)的dom節(jié)點(diǎn)
this.ns = undefined;//當(dāng)前節(jié)點(diǎn)的namespace(命名空間)
this.context = context;//編譯的作用域
this.fnContext = undefined;// 函數(shù)化組件上下文
this.fnOptions = undefined;// 函數(shù)化組件配置項(xiàng)
this.fnScopeId = undefined;// 函數(shù)化組件ScopeId
this.key = data && data.key;//只有綁定數(shù)據(jù)下存在,在diff的過程中可以提高性能
this.componentOptions = componentOptions;// 通過vue組件生成的vnode對(duì)象,若是普通dom生成的vnode,則此值為空
this.componentInstance = undefined;//當(dāng)前組件實(shí)例
this.parent = undefined;//vnode、組件的占位節(jié)點(diǎn)
this.raw = false; //是否為原生HTML或只是普通文本
this.isStatic = false; //靜態(tài)節(jié)點(diǎn)標(biāo)識(shí) || keep-alive
this.isRootInsert = true; // 是否作為根節(jié)點(diǎn)插入
this.isComment = false;// 是否為注釋節(jié)點(diǎn)
this.isCloned = false; //是否為克隆節(jié)點(diǎn)
this.isOnce = false; //是否為v-once節(jié)點(diǎn)
this.asyncFactory = asyncFactory;// 異步工廠方法
this.asyncMeta = undefined; //異步Meta
this.isAsyncPlaceholder = false;//是否為異步占位
};
var prototypeAccessors = { child: { configurable: true } };
prototypeAccessors.child.get = function () {
return this.componentInstance
};
// 通過 Object.defineProperties 為 VNode 的原型綁定了對(duì)象 prototypeAccessors ,
// prototypeAccessors 設(shè)置 child 是可修改的狀態(tài)。
Object.defineProperties( VNode.prototype, prototypeAccessors );
/*創(chuàng)建一個(gè)空VNode節(jié)點(diǎn)*/
var createEmptyVNode = function (text) {
if ( text === void 0 ) text = '';
var node = new VNode();
node.text = text;
node.isComment = true;
return node
};
/*創(chuàng)建一個(gè)文本節(jié)點(diǎn)*/
function createTextVNode (val) {
return new VNode(undefined, undefined, undefined, String(val))
}
// cloneVNode 克隆VNode節(jié)點(diǎn)
function cloneVNode (vnode) {
var cloned = new VNode(
vnode.tag,
vnode.data,
// 克隆子數(shù)組以避免在克隆時(shí)突變?cè)紨?shù)組的子數(shù)組
vnode.children && vnode.children.slice(),
vnode.text,
vnode.elm,
vnode.context,
vnode.componentOptions,
vnode.asyncFactory
);
cloned.ns = vnode.ns;
cloned.isStatic = vnode.isStatic;
cloned.key = vnode.key;
cloned.isComment = vnode.isComment;
cloned.fnContext = vnode.fnContext;
cloned.fnOptions = vnode.fnOptions;
cloned.fnScopeId = vnode.fnScopeId;
cloned.asyncMeta = vnode.asyncMeta;
cloned.isCloned = true;
return cloned
}
/*取得原生數(shù)組的原型*/
var arrayProto = Array.prototype;
/*創(chuàng)建一個(gè)新的數(shù)組對(duì)象,修改該對(duì)象上的數(shù)組的七個(gè)方法,防止污染原生數(shù)組方法*/
var arrayMethods = Object.create(arrayProto);
/*這里重寫了數(shù)組的這些方法,在保證不污染原生數(shù)組原型的情況下重寫數(shù)組的這些方法,
截獲數(shù)組的成員發(fā)生的變化,執(zhí)行原生數(shù)組操作的同時(shí)dep通知關(guān)聯(lián)的所有觀察者進(jìn)行響應(yīng)式處理*/
var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
// 數(shù)組?較特別,它的操作?法不會(huì)觸發(fā)setter,需要特別處理。修改數(shù)組7個(gè)變更?法使其可以發(fā)送更新通知
methodsToPatch.forEach(function (method) {
/*將數(shù)組的原生方法緩存起來*/
var original = arrayProto[method];
def(arrayMethods, method, function mutator () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
/*調(diào)用原生的數(shù)組方法*/
var result = original.apply(this, args);
/*數(shù)組新插入的元素需要重新進(jìn)行observe才能響應(yīng)式*/
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
// 方法用于異步監(jiān)視數(shù)組發(fā)生的變化,類似于針對(duì)對(duì)象的 Object.observe() 。
if (inserted) { ob.observeArray(inserted); }
/*dep通知所有注冊(cè)的觀察者進(jìn)行響應(yīng)式處理*/
ob.dep.notify();
return result
});
});
// 一般也是用來獲取一個(gè)JSON對(duì)象中所有屬性,
// getOwnPropertyNames 獲取對(duì)象自身的可枚舉和不可枚舉屬性,不包括屬性名為Symbol值的屬性
var arrayKeys = Object.getOwnPropertyNames(arrayMethods);
/**
* In some cases we may want to disable observation inside a component's
* update computation.
*/
var shouldObserve = true;
// 這個(gè)方法是vue內(nèi)部對(duì)邏輯的一個(gè)優(yōu)化,如果當(dāng)前組件是根組件,那么根組件是不應(yīng)該有props的。
// toggleObserving就是禁止掉根組件 props的依賴收集
function toggleObserving (value) {
shouldObserve = value;
}
// 附加到每個(gè)被觀察對(duì)象的觀察者類。一旦附加,觀察者將目標(biāo)對(duì)象的屬性鍵轉(zhuǎn)換為getter/setter,用于收集依賴項(xiàng)和分派更新。
// Observer為數(shù)據(jù)加上響應(yīng)式屬性進(jìn)行雙向綁定。如果是對(duì)象則進(jìn)行深度遍歷,為每一個(gè)子對(duì)象都綁定上方法,如果是數(shù)組則為每一個(gè)成員都綁定上方法。
// 如果是修改一個(gè)數(shù)組的成員,該成員是一個(gè)對(duì)象,那只需要遞歸對(duì)數(shù)組的成員進(jìn)行雙向綁定即可。
export class Observer {
var Observer = function Observer (value) {
this.value = value;//這個(gè)就是傳入的要被監(jiān)聽的對(duì)象
this.dep = new Dep();// 保存新的Dep實(shí)例
this.vmCount = 0;// 設(shè)置vmCount的值為0
// 為被監(jiān)聽對(duì)象定義了一個(gè) __ob__ 屬性,這個(gè)屬性的值就是當(dāng)前 Observer 實(shí)例對(duì)象
// 其中 def 函數(shù)其實(shí)就是 Object.defineProperty 函數(shù)的簡單封裝
//之所以這里使用 def 函數(shù)定義 __ob__ 屬性是因?yàn)檫@樣可以定義不可枚舉的屬性,
// 這樣后面遍歷數(shù)據(jù)對(duì)象的時(shí)候就能夠防止遍歷到 __ob__ 屬性
def(value, '__ob__', this);
/*
如果是數(shù)組,將修改后可以截獲響應(yīng)的數(shù)組方法替換掉該數(shù)組的原型中的原生方法,達(dá)到監(jiān)聽數(shù)組數(shù)據(jù)變化響應(yīng)的效果。
這里如果當(dāng)前瀏覽器支持__proto__屬性,則直接覆蓋當(dāng)前數(shù)組對(duì)象原型上的原生數(shù)組方法,如果不支持該屬性,
則直接覆蓋數(shù)組對(duì)象的原型。
*/
if (Array.isArray(value)) {
if (hasProto) {
/*直接覆蓋原型的方法來修改目標(biāo)對(duì)象*/
protoAugment(value, arrayMethods);
} else {
/*定義(覆蓋)目標(biāo)對(duì)象或數(shù)組的某一個(gè)方法*/
copyAugment(value, arrayMethods, arrayKeys);
}
/*如果是數(shù)組則需要遍歷數(shù)組的每一個(gè)成員進(jìn)行observe*/
this.observeArray(value);
} else {
// 如果是對(duì)象,遍歷執(zhí)行響應(yīng)式的操作
this.walk(value);
}
};
// 遍歷每個(gè)屬性并將它們轉(zhuǎn)換為getter/setter。此方法只應(yīng)在值類型為Object時(shí)調(diào)用。
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
// 觀察數(shù)組項(xiàng)的列表,如果是數(shù)組,遍歷每一項(xiàng),對(duì)每一項(xiàng)進(jìn)行一次observe
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
// helpers
// 直接指定原型
function protoAugment (target, src) {
/* eslint-disable no-proto */
target.__proto__ = src;
/* eslint-enable no-proto */
}
//遍歷keys,定義(覆蓋)目標(biāo)對(duì)象或數(shù)組的某一個(gè)方法
function copyAugment (target, src, keys) {
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
def(target, key, src[key]);
}
}
// 嘗試創(chuàng)建一個(gè)Observer實(shí)例(__ob__),如果成功創(chuàng)建Observer實(shí)例則返回新的Observer實(shí)例,
// 如果已有Observer實(shí)例則返回現(xiàn)有的Observer實(shí)例。
// Vue的響應(yīng)式數(shù)據(jù)都會(huì)有一個(gè)__ob__的屬性作為標(biāo)記,里面存放了該屬性的觀察器,也就是Observer的實(shí)例,防止重復(fù)綁定。
function observe (value, asRootData) {
/*判斷是否是一個(gè)對(duì)象或者是否是一個(gè)虛擬節(jié)點(diǎn)*/
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
/*這里用__ob__這個(gè)屬性來判斷是否已經(jīng)有Observer實(shí)例,如果沒有Observer實(shí)例則會(huì)
新建一個(gè)Observer實(shí)例并賦值給__ob__這個(gè)屬性,如果已有Observer實(shí)例則直接返回該Observer實(shí)例*/
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
/*這里的判斷是為了確保value是單純的對(duì)象,而不是函數(shù)或者是Regexp等情況。*/
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
/*如果是根數(shù)據(jù)則計(jì)數(shù),后面Observer中的observe的asRootData非true*/
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
// defineReactive 函數(shù)最開始初始化 Dep 對(duì)象的實(shí)例,接著拿到 obj 的屬性描述符,
// 然后對(duì)子對(duì)象遞歸調(diào)用 observe 方法,這樣就保證了無論 obj 的結(jié)構(gòu)多復(fù)雜,
// 它的所有子屬性也能變成響應(yīng)式的對(duì)象,這樣我們?cè)L問或修改 obj 中一個(gè)嵌套較深的屬性,
// 也能觸發(fā) getter 和 setter。最后利用 Object.defineProperty 去給 obj 的屬性 key 添加 getter 和 setter。
// 核心就是利用 Object.defineProperty 給數(shù)據(jù)添加了 getter 和 setter,目的就是
// 為了在我們?cè)L問數(shù)據(jù)以及寫數(shù)據(jù)的時(shí)候能自動(dòng)執(zhí)行一些邏輯:getter 做的事情是依賴收集,setter 做的事情是派發(fā)更新
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
// 這個(gè) dep 常量所引用的 Dep 實(shí)例對(duì)象
// 每一個(gè)數(shù)據(jù)字段都通過閉包引用著屬于自己的 dep 常量
// 每次調(diào)用 defineReactive 定義訪問器屬性時(shí),該屬性的 setter/getter 都閉包引用了一個(gè)屬于自己的“筐
var dep = new Dep();
// 不可配置的直接返回
// 獲取該字段可能已有的屬性描述對(duì)象
var property = Object.getOwnPropertyDescriptor(obj, key);
// 判斷該字段是否是可配置的
// 一個(gè)不可配置的屬性是不能使用也沒必要使用 Object.defineProperty 改變其屬性定義的。
if (property && property.configurable === false) {
return
}
// 保存了來自 property 對(duì)象的 get 和 set
// 避免原有的 set 和 get 方法被覆蓋
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
// 遞歸響應(yīng)式處理 給每一層屬性附加一個(gè)Obeserver實(shí)例
// shallow不存在時(shí)代表沒有__ob__屬性 將val進(jìn)行observe返回一個(gè)ob實(shí)例賦值給childO
// 如果是對(duì)象繼續(xù)調(diào)用 observe(val) 函數(shù)觀測(cè)該對(duì)象從而深度觀測(cè)數(shù)據(jù)對(duì)象
// walk 函數(shù)中調(diào)用 defineReactive 函數(shù)時(shí)沒有傳遞 shallow 參數(shù),所以該參數(shù)是 und
// 默認(rèn)就是深度觀測(cè)
var childOb = !shallow && observe(val);
// 數(shù)據(jù)攔截
// 通過Object.defineProperty對(duì)obj的key進(jìn)行數(shù)據(jù)攔截
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 進(jìn)行依賴收集
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
// 判斷是否有Dep.target 如果有就代表Dep添加了Watcher實(shí)例化對(duì)象
if (Dep.target) {
// 加入到dep去管理watcher
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
// 循環(huán)添加watcher
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 獲取value值 觸發(fā)依賴收集
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 (customSetter) {
customSetter();
}
// 對(duì)于沒有setter的訪問器屬性 返回
if (getter && !setter) { return }
if (setter) {
// 設(shè)置新值
setter.call(obj, newVal);
} else {
// 如果沒有setter ,直接給新值
val = newVal;
}
// 遞歸,對(duì)新來的值 對(duì)新值進(jìn)行observe 返回ob實(shí)例
childOb = !shallow && observe(newVal);
// 當(dāng)set時(shí)觸發(fā)通知
dep.notify();
}
});
}
// 給對(duì)象設(shè)置一個(gè)屬性,添加新屬性和添加觸發(fā)更改通知(dep.notify),如果這個(gè)屬性不是早已存在
function set (target, key, val) {
// 判斷數(shù)據(jù) 是否是undefined或者null
// 判斷數(shù)據(jù)類型是否是string,number,symbol,boolean
if (isUndef(target) || isPrimitive(target)
) {
// target必須是對(duì)象或者數(shù)組,否則發(fā)出警告
warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
}
// 如果是數(shù)組 并且檢查key是否是有效的數(shù)組索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 設(shè)置數(shù)組長度
target.length = Math.max(target.length, key);
// 像數(shù)組尾部添加一個(gè)新數(shù)據(jù),相當(dāng)于push
target.splice(key, 1, val);
return val
}
// 如果key在target上 并且不是通過原型鏈查找的,就直接賦值
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val
}
// 聲明一個(gè)對(duì)象ob 值為該target對(duì)象中的原型上面的所有方法和屬性,表明該數(shù)據(jù)加入過觀察者中
var ob = (target).__ob__;
// 如果是vue 或者 檢測(cè)vue被實(shí)例化的次數(shù) vmCount
if (target._isVue || (ob && ob.vmCount)) {
warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
);
return val
}
// 如果ob不存在,證明沒有添加觀察者,不是相應(yīng),直接賦值返回
if (!ob) {
target[key] = val;
return val
}
// 通過defineReactive將ob.value加入的觀察者
defineReactive$$1(ob.value, key, val);
// 觸發(fā)通知更新,通知訂閱者obj.value更新數(shù)據(jù)
ob.dep.notify();
return val
}