Vue收集依賴與觸發(fā)依賴源碼刨析
定義依賴
定義依賴是什么時(shí)候開始的呢?通過源碼可以發(fā)現(xiàn)在執(zhí)行_init函數(shù)的時(shí)候會(huì)執(zhí)行initState(vm)方法:
function initState(vm) { ... if (opts.data) { initData(vm); } else { var ob = observe((vm._data = {})); ob && ob.vmCount++; } ... }
先觸發(fā)initData方法:
function initData(vm) { var data = vm.$options.data; data = vm._data = isFunction(data) ? getData(data, vm) : data || {}; ... var keys = Object.keys(data); ... var i = keys.length; while (i--) { var key = keys[i]; { if (methods && hasOwn(methods, key)) { warn$2("Method \"".concat(key, "\" has already been defined as a data property."), vm); } } if (props && hasOwn(props, key)) { warn$2("The data property \"".concat(key, "\" is already declared as a prop. ") + "Use prop default value instead.", vm); } else if (!isReserved(key)) { proxy(vm, "_data", key); } } // observe data var ob = observe(data); ob && ob.vmCount++; }
首先會(huì)獲取data數(shù)據(jù),然后執(zhí)行proxy(vm, “_data”, key):
var sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop }; function proxy(target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter() { return this[sourceKey][key]; }; sharedPropertyDefinition.set = function proxySetter(val) { this[sourceKey][key] = val; }; Object.defineProperty(target, key, sharedPropertyDefinition); }
在vm實(shí)例中添加了_data對(duì)象并將data的數(shù)據(jù)給了_data。隨后執(zhí)行observe(data):
function observe(value, shallow, ssrMockReactivity) { ... else if (shouldObserve && (ssrMockReactivity || !isServerRendering()) && (isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value.__v_skip /* ReactiveFlags.SKIP */) { ob = new Observer(value, shallow, ssrMockReactivity); } return ob; }
主要執(zhí)行 new Observer(value, shallow, ssrMockReactivity)方法:
function Observer(value, shallow, mock) { if (shallow === void 0) { shallow = false; } if (mock === void 0) { mock = false; } this.value = value; this.shallow = shallow; this.mock = mock; // this.value = value this.dep = mock ? mockDep : new Dep(); this.vmCount = 0; def(value, '__ob__', this); if (isArray(value)) { ... } else { /** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. */ var keys = Object.keys(value); for (var i = 0; i < keys.length; i++) { var key = keys[i]; defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock); } } }
主要執(zhí)行defineReactive:
function defineReactive(obj, key, val, customSetter, shallow, mock) { var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return; } // cater for pre-defined getter/setters var getter = property && property.get; var setter = property && property.set; if ((!getter || setter) && (val === NO_INIITIAL_VALUE || arguments.length === 2)) { val = obj[key]; } var childOb = !shallow && observe(val, false, mock); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; if (Dep.target) { { dep.depend({ target: obj, type: "get" /* TrackOpTypes.GET */, key: key }); } if (childOb) { childOb.dep.depend(); if (isArray(value)) { dependArray(value); } } } return isRef(value) && !shallow ? value.value : value; }, set: function reactiveSetter(newVal) { var value = getter ? getter.call(obj) : val; if (!hasChanged(value, newVal)) { return; } if (customSetter) { customSetter(); } if (setter) { setter.call(obj, newVal); } else if (getter) { // #7981: for accessor properties without setter return; } else if (!shallow && isRef(value) && !isRef(newVal)) { value.value = newVal; return; } else { val = newVal; } childOb = !shallow && observe(newVal, false, mock); { dep.notify({ type: "set" /* TriggerOpTypes.SET */, target: obj, key: key, newValue: newVal, oldValue: value }); } } }); return dep; }
可以看出新增了一個(gè)依賴對(duì)象Dep,表示是該數(shù)據(jù)被哪些組件所依賴,并定義了data下數(shù)據(jù)的get和set方法。
收集依賴
vue是怎么收集依賴的呢?當(dāng)組件渲染的時(shí)候會(huì)執(zhí)行下面的渲染函數(shù):
var render = function render() { var _vm = this, _c = _vm._self._c return _c("div", [ _vm._v("\n " + _vm._s(_vm.num) + "\n " + _vm._s(_vm.a) + "\n "), _c("button", { on: { click: _vm.addModule } }, [_vm._v("新增")]), ]) }
原內(nèi)容如下:
<template> <div> {{num}} {{a}} <button @click="addModule">新增</button> </div> </template> <script> export default { name: "TestWebpackTest", mounted() { console.log(this); }, data() { return { num: 1, a:2 }; }, computed:{ getNum(){ return this.num+Math.random() }, getA(){ return this.a+Math.random() } }, methods: { addModule() { this.num++; } } }; </script> <style lang="scss"> div { .test { width: 10px; height: 15px; background-color: blue; } } </style>
在渲染組件模版的時(shí)候會(huì)取獲取數(shù)據(jù),此時(shí)會(huì)觸發(fā)data中定義數(shù)據(jù)的getter方法,此時(shí)為當(dāng)前掛載組件實(shí)例化watcher的時(shí)候會(huì)設(shè)置Dep.target。
Dep.prototype.depend = function (info) { if (Dep.target) { Dep.target.addDep(this); if (info && Dep.target.onTrack) { Dep.target.onTrack(__assign({ effect: Dep.target }, info)); } } };
通知當(dāng)前依賴的組件去添加依賴,當(dāng)前依賴的組件會(huì)將該依賴添加進(jìn)入newDeps。相反依賴也可能被多個(gè)組件使用,所以在該依賴也有多個(gè)組件。
Watcher.prototype.addDep = function (dep) { var id = dep.id; if (!this.newDepIds.has(id)) { this.newDepIds.add(id); this.newDeps.push(dep); if (!this.depIds.has(id)) { dep.addSub(this); } } };
其實(shí)本質(zhì)就是建立組件和變量之間的依賴關(guān)系,一個(gè)組件可以有多個(gè)依賴,一個(gè)依賴可以被多個(gè)組件使用。
觸發(fā)依賴
當(dāng)數(shù)據(jù)發(fā)生變化時(shí)會(huì)觸發(fā)數(shù)據(jù)的gettter方法:
dep.notify({ type: "set" /* TriggerOpTypes.SET */, target: obj, key: key, newValue: newVal, oldValue: value });
調(diào)用當(dāng)前依賴的notify方法去通知組件更新:
Dep.prototype.notify = function (info) { // stabilize the subscriber list first var subs = this.subs.slice(); ... for (var i = 0, l = subs.length; i < l; i++) { if (info) { var sub = subs[i]; sub.onTrigger && sub.onTrigger(__assign({ effect: subs[i] }, info)); } subs[i].update(); } };
該方法就是獲取當(dāng)前依賴下的組件并調(diào)用該組件的update方法:
Watcher.prototype.update = function () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true; } else if (this.sync) { this.run(); } else { queueWatcher(this); } };
下面的內(nèi)容我在另外一篇文章中講過:vue2.7.10在數(shù)據(jù)發(fā)生變化后是如何更新頁面的。
總結(jié)
- 定義依賴是在實(shí)例化組件的時(shí)候執(zhí)行的此時(shí)的Dep.target指向當(dāng)前vm實(shí)例,在initData方法中會(huì)遍歷data數(shù)據(jù)并設(shè)置get和set方法,每個(gè)數(shù)據(jù)都有一個(gè)dep(依賴),表示每個(gè)數(shù)據(jù)都會(huì)被多個(gè)組件所依賴。
- 收集依賴是在執(zhí)行render方法的時(shí)候。該方法觸發(fā)數(shù)據(jù)的get方法,建立數(shù)據(jù)的dep(依賴)和watcher之間的聯(lián)系。
- 觸發(fā)依賴是在數(shù)據(jù)發(fā)生改變的時(shí)候執(zhí)行。此時(shí)會(huì)觸發(fā)數(shù)據(jù)的set方法,取出當(dāng)前數(shù)據(jù)的依賴者(watcher)并循環(huán)調(diào)用依賴者的update方法更新視圖。
到此這篇關(guān)于Vue收集依賴與觸發(fā)依賴源碼刨析的文章就介紹到這了,更多相關(guān)Vue收集依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue動(dòng)態(tài)子組件的兩種實(shí)現(xiàn)方式
這篇文章主要介紹了vue動(dòng)態(tài)子組件的兩種實(shí)現(xiàn)方式,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09vue實(shí)現(xiàn)html轉(zhuǎn)化pdf并復(fù)制文字
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)html轉(zhuǎn)化pdf的兩種方式,分別為能復(fù)制文字和不能復(fù)制文字的方法,有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-10-10Vue動(dòng)態(tài)數(shù)據(jù)實(shí)現(xiàn)?el-select?多級(jí)聯(lián)動(dòng)、數(shù)據(jù)回顯方式
這篇文章主要介紹了Vue動(dòng)態(tài)數(shù)據(jù)實(shí)現(xiàn)?el-select?多級(jí)聯(lián)動(dòng)、數(shù)據(jù)回顯方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07Vue+Element-UI中el-table動(dòng)態(tài)合并單元格:span-method方法代碼詳解
el-table是element-ui提供的表格組件,可以用于展示和操作數(shù)據(jù),這篇文章主要給大家介紹了關(guān)于Vue+Element-UI中el-table動(dòng)態(tài)合并單元格:span-method方法的相關(guān)資料,需要的朋友可以參考下2023-09-09詳解vue-cli 腳手架項(xiàng)目-package.json
本篇文章主要介紹了詳解vue-cli 腳手架項(xiàng)目-package.json,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07vue?element-ui?Radio單選框默認(rèn)值選不中的原因:混用字符和數(shù)字問題
這篇文章主要介紹了vue?element-ui?Radio單選框默認(rèn)值選不中的原因:混用字符和數(shù)字問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12