您好,登錄后才能下訂單哦!
這篇文章給大家介紹ES6 Proxy實現Vue的變化檢測,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
模塊劃分
參照之前Vue變化檢測的代碼,將Vue 變化檢測的功能分為以下幾個部分。
Observer
Dep
Watcher
Utils
首先,我們要確定的問題是,將Dep依賴搜集存在哪里。Vue 2.x里,Object的依賴收集放在defineRactive,Array的依收集存入到Observer中。ES6 Proxy里,考慮到讓handler訪問dep,我們將依賴放入到Observer中。
Observer
observer.js功能代碼如下:
import Dep from './dep'; import { isObject } from './utils'; export default class Observer { constructor (value) { // 遞歸處理子元素 this.obeserve(value); // 實現當前元素的代理 this.value = this.proxyTarget(value); } proxyTarget (targetBefore, keyBefore) { const dep = new Dep(); targetBefore.__dep__ = dep; let self = this; const filtersAtrr = val => ['__dep__', '__parent__'].indexOf(val) > -1; return new Proxy(targetBefore, { get: function(target, key, receiver){ if (filtersAtrr(key)) return Reflect.get(target, key, receiver); if (!Array.isArray(target)) { dep.depend(key); } // sort/reverse等不改變數組長度的,在get里觸發 if (Array.isArray(target)) { if ((key === 'sort' || key === 'reverse') && target.__parent__) { target.__parent__.__dep__.notify(keyBefore); } } return Reflect.get(target, key, receiver); }, set: function(target, key, value, receiver){ if (filtersAtrr(key)) return Reflect.set(target, key, value, receiver); // 新增元素,需要proxy const { newValue, isChanged } = self.addProxyTarget(value, target, key, self); // 設置key為新元素 Reflect.set(target, key, newValue, receiver); // notify self.depNotify(target, key, keyBefore, dep, isChanged); return true; }, }); } addProxyTarget(value, target, key, self) { let newValue = value; let isChanged = false; if (isObject(value) && !value.__parent__) { self.obeserve(newValue); newValue = self.proxyTarget(newValue, key); newValue.__parent__ = target; isChanged = true; } return { newValue, isChanged, } } depNotify(target, key, keyBefore, dep, isChanged) { if (isChanged && target.__parent__) { target.__parent__.__dep__.notify(keyBefore); return; } if (Array.isArray(target)) { if (key === 'length' && target.__parent__) { target.__parent__.__dep__.notify(keyBefore); } } else { dep.notify(key); } } obeserve(target) { // 只處理對象類型,包括數組、對象 if (!isObject(target)) return; for (let key in target) { if (isObject(target[key]) && target[key] !== null) { this.obeserve(target[key]); target[key] = this.proxyTarget(target[key], key); // 設置__parent__,方便子元素調用 target[key].__parent__ = target; } } } }
在Observer中,針對對象,只需要執行 dep.depend(key)
、 dep.notify(key)
即可。添加 key 是為了能正確的觸發收集,不知道怎么說明白為什么要這樣做,只能一切盡在不言中了。
Array, 如何實現依賴的收集和觸發那。依賴收集與Object類似, dep.depend(key)
完成數組的收集。關于觸發,可以分為兩個方面,一是改變數組長度、二未改變數組長度的。改變數組長度的,在set里,通過長度屬性的設置觸發父級元素的notify。為什么要使用父級元素的notify那?我們可以分析以下,在你設置數組的長度時,這時候的target\key\value分別是[]\length*, 這個時候,數組的依賴收集是沒有的,你watcher的是數組,并不是數組本身。這個時候只能通過 target.__parent__.__dep__.notify(keyBefore)
觸發父級的收集,完成數據變化的檢測。二對于未改變數組長度的,這里的做法,雖然是直接 target.__parent__.__dep__.notify(keyBefore) 觸發依賴,但是有個嚴重的問題,實際上更新的數據不是最新的,這個地方暫時還沒想到比較好的方法,歡迎大家討論。
Dep
Dep.js
let uid = 0; export default class Dep { constructor () { this.subs = {}; this.id = uid++; } addSub(prop, sub) { this.subs[prop] = this.subs[prop] || []; this.subs[prop].push(sub); } removeSub(prop, sub) { this.remove(this.subs[prop] || [], sub); } depend(prop) { if (Dep.target) { // 傳入的是當前依賴 Dep.target.addDep(prop, this) } } notify(prop) { const subs = (this.subs[prop] || []).slice(); for (let i = 0, l = subs.length; i < l; i++) { subs[i].update(); } } remove(arr, item) { if (arr.length) { const index = arr.indexOf(item); if (index > -1) { return arr.splice(index, 1); } } } } Dep.target = null const targetStack = [] export function pushTarget (_target) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { Dep.target = targetStack.pop() }
dep 添加prop實現類型的綁定,為什么要這么做那?使用proxy代理后,你假如wahcter對象下的幾個元素,此時的deps將同時存在這幾個元素,你觸發依賴的時候,這些依賴都會執行。因此,通過key值綁定觀察事件,觸發時,能完成對象的正確觸發。
watcher、utils
import { parsePath } from './utils'; import { pushTarget, popTarget } from './dep' export default class Watcher { constructor(vm, expOrFn, cb) { // dep id集合 this.depIds = new Set(); this.vm = vm; this.getter = parsePath(expOrFn); this.cb = cb; this.value = this.get(); } get () { pushTarget(this); let value = this.getter.call(this.vm, this.vm); popTarget(); return value; } update() { const oldValue = this.value; this.value = this.get(); this.cb.call(this.vm, this.value, oldValue); } addDep (prop, dep) { const id = dep.id; if (!this.depIds.has(id)) { this.depIds.add(id); dep.addSub(prop, this); } } }
utils.js
/** * 解析簡單路徑 */ const bailRE = /[^\w.$]/; export function parsePath (path) { if (bailRE.test(path)) { return; } const segments = path.split('.'); return function (obj) { for (let i = 0; i < segments.length; i++) { if (!obj) return; obj = obj[segments[i]]; } return obj; }; } /** * Define a property. */ export function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }) } /** * Quick object check - this is primarily used to tell * Objects from primitive values when we know the value * is a JSON-compliant type. */ export function isObject (obj) { return obj !== null && typeof obj === 'object' } /** * Check whether an object has the property. */ const hasOwnProperty = Object.prototype.hasOwnProperty export function hasOwn (obj, key) { return hasOwnProperty.call(obj, key) }
Utils.js/Watchers.js與Vue 2.x類似,這里就不多介紹了。
測試一下
test.js
import Observer from './observer'; import Watcher from './watcher'; let data = { name: 'lijincai', password: '***********', address: { home: '安徽亳州譙城區', }, list: [{ name: 'lijincai', password: 'you know it Object', }], }; const newData = new Observer(data); let index = 0; const watcherName = new Watcher(newData, 'value.name', (newValue, oldValue) => { console.log(`${index++}: name newValue:`, newValue, ', oldValue:', oldValue); }); const watcherPassword = new Watcher(newData, 'value.password', (newValue, oldValue) => { console.log(`${index++}: password newValue:`, newValue, ', oldValue:', oldValue); }); const watcherAddress = new Watcher(newData, 'value.address', (newValue, oldValue) => { console.log(`${index++}: address newValue:`, newValue, ', oldValue:', oldValue); }); const watcherAddressHome = new Watcher(newData, 'value.address.home', (newValue, oldValue) => { console.log(`${index++}: address.home newValue:`, newValue, ', oldValue:', oldValue); }); const watcherAddProp = new Watcher(newData, 'value.addProp', (newValue, oldValue) => { console.log(`${index++}: addProp newValue:`, newValue, ', oldValue:', oldValue); }); const watcherDataObject = new Watcher(newData, 'value.list', (newValue, oldValue) => { console.log(`${index++}: newValue:`, newValue, ', oldValue:', oldValue); }); newData.value.name = 'resetName'; newData.value.password = 'resetPassword'; newData.value.name = 'hello world name'; newData.value.password = 'hello world password'; newData.value.address.home = 'hello home'; newData.value.address.home = 'hello home2'; newData.value.addProp = 'hello addProp'; newData.value.addProp ={ name: 'ceshi', }; newData.value.addProp.name = 'ceshi2'; newData.value.list.push('1'); newData.value.list.splice(0, 1); newData.value.list.sort(); newData.value.list.reverse(); newData.value.list.push('1'); newData.value.list.unshift({name: 'nihao'}); newData.value.list[0] = { name: 'lijincai', password: 'you know it Array', }; newData.value.list[0].name = 'you know it array after'; newData.value.list.pop(); newData.value.list.shift(); newData.value.list.length = 1;
我們使用對象、數組測試一下我們的ES6 Proxy檢測。
20:17:44.725 index.js?afc7:20 0: name newValue: resetName , oldValue: lijincai 20:17:44.725 index.js?afc7:24 1: password newValue: resetPassword , oldValue: *********** 20:17:44.725 index.js?afc7:20 2: name newValue: hello world name , oldValue: resetName 20:17:44.725 index.js?afc7:24 3: password newValue: hello world password , oldValue: resetPassword 20:17:44.726 index.js?afc7:32 4: address.home newValue: hello home , oldValue: 安徽亳州譙城區 20:17:44.726 index.js?afc7:32 5: address.home newValue: hello home2 , oldValue: hello home 20:17:44.726 index.js?afc7:36 6: addProp newValue: hello addProp , oldValue: undefined 20:17:44.727 index.js?afc7:36 7: addProp newValue: Proxy {name: "ceshi", __dep__: Dep, __parent__: {…}} , oldValue: hello addProp 20:17:44.727 index.js?afc7:40 0: newValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}} 20:17:44.728 index.js?afc7:40 1: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} 20:17:44.729 index.js?afc7:40 2: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} 20:17:44.731 index.js?afc7:40 3: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} 20:17:44.734 index.js?afc7:40 4: newValue: Proxy {0: "1", 1: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", 1: "1", __dep__: Dep, __parent__: {…}} 20:17:44.735 index.js?afc7:40 5: newValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} 20:17:44.735 index.js?afc7:40 6: newValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} 20:17:44.736 index.js?afc7:40 7: newValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", 2: "1", __dep__: Dep, __parent__: {…}} 20:17:44.737 index.js?afc7:40 8: newValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: Proxy, 1: "1", __dep__: Dep, __parent__: {…}} 20:17:44.738 index.js?afc7:40 9: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} 20:17:44.738 index.js?afc7:40 10: newValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}} , oldValue: Proxy {0: "1", __dep__: Dep, __parent__: {…}}
關于ES6 Proxy實現Vue的變化檢測就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。