Vue3.0 響應式由 Object.defineProperty 改成 Proxy 實現
由於前者沒法監聽到對象上增刪屬性的變化html
Vue3.0的響應式用到了Proxy和Reflect兩個ES6新增的功能es6
Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。數組
Reflect
對象與Proxy
對象同樣,也是 ES6 爲了操做對象而提供的新 API。Reflect
對象的設計目的有這樣幾個。app
Object
對象的一些明顯屬於語言內部的方法(好比Object.defineProperty
),放到Reflect
對象上。Object
方法的返回結果,讓其變得更合理。好比,Object.defineProperty(obj, name, desc)
在沒法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)
則會返回false
。Reflect.get
方法查找並返回target
對象的name
屬性,若是沒有該屬性,則返回undefined
。Reflect.set
方法設置target
對象的name
屬性等於value
。createReactive
函數中改成使用Proxy異步
let createReactive = (target, prop, value) => { target._dep = new Dep() return new Proxy(target, { get(target, prop) { target._dep.depend() return Reflect.get(target, prop) }, set(target, prop, value) { target._dep.notify() return Reflect.set(target, prop, value) } }) }
示例:函數
均可以成功this
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <button id="add">add</button> <div id="app"></div> <hr> <button id="addArr">addArr</button> <div id="appArr"></div> </body> <script> let active let effect = (fn, options = {}) => { // 爲何要增長一個_effect函數 // 由於須要給_effect增長屬性 // 也能夠直接給fn增長,可是因爲引用類型的緣由,會對fn函數形成污染 let _effect = (...args) => { try { active = _effect return fn(...args) } finally { active = null } } _effect.options = options _effect.deps = [] // effect和dep的關係-1 return _effect } let cleanUpEffect = (effect) => { // 清除依賴 // 須要反向查找effect被哪些dep依賴了 // 在effect上添加[] 創建雙向索引 const { deps } = effect console.log(deps) console.log(effect) if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect) } } } let watchEffect = (cb) => { /* active = cb active() active = null */ let runner = effect(cb) runner() return () => { cleanUpEffect(runner) } } let nextTick = (cb) => Promise.resolve().then(cb) // 隊列 let queue = [] // 添加隊列 let queueJob = (job) => { if (!queue.includes(job)) { queue.push(job) // 添加以後,將執行放到異步任務中 nextTick(flushJob) } } // 執行隊列 let flushJob = () => { while (queue.length > 0) { let job = queue.shift() job && job() } } let Dep = class { constructor() { // 存放收集的active this.deps = new Set() } // 依賴收集 depend() { if (active) { this.deps.add(active) active.deps.push(this.deps) // effect和dep的關係-2 } } // 觸發 notify() { this.deps.forEach(dep => queueJob(dep)) this.deps.forEach(dep => { dep.options && dep.options.schedular && dep.options.schedular() }) } } let createReactive = (target, prop, value) => { target._dep = new Dep() return new Proxy(target, { get(target, prop) { target._dep.depend() return Reflect.get(target, prop) }, set(target, prop, value) { target._dep.notify() return Reflect.set(target, prop, value) } }) } let ref = (initValue) => createReactive({}, 'value', initValue) const set = (target, prop, initValue) => createReactive(target, prop, initValue) let computed = (fn) => { let value let dirty = true // 爲true代表依賴的變量發生了變化,此時須要從新計算 let runner = effect(fn, { schedular() { if (!dirty) { dirty = true } } }) return { get value() { if (dirty) { // 什麼時候將dirty重置爲true,當執行fn後 // 所以須要經過配置回調函數,在執行fn後將dirty重置爲true // value = fn() value = runner() dirty = false } return value } } } let watch = (source, cb, options = {}) => { const { immediate } = options const getter = () => { return source() } // 將函數添加到count的依賴上去,當count變化時 let oldValue const runner = effect(getter, { schedular: () => applyCb() }) const applyCb = () => { let newValue = runner() if (newValue !== oldValue) { cb(newValue, oldValue) oldValue = newValue } } if (immediate) { applyCb() } else { oldValue = runner() } } // set示例: let count = ref(0) // count.v新增屬性 document.getElementById('add').addEventListener('click', function () { if (!count.v) { count.v = 0 } count.v++ }) let str let stop = watchEffect(() => { str = `hello ${count.v}` document.getElementById('app').innerText = str }) // 數組push示例: let arrValue = 0 // set函數中已經對依賴進行了一次添加 let countArr = set([], 1, 0) document.getElementById('addArr').addEventListener('click', function () { arrValue++ countArr.push(arrValue) }) watchEffect(() => { str = `hello ${countArr.join(',')}` document.getElementById('appArr').innerText = str }) </script> </html>