目前,Vue 的反應系統是使用 Object.defineProperty 的 getter 和 setter。 可是,Vue 3 將使用 ES2015 Proxy 做爲其觀察者機制。 這消除了之前存在的警告,使速度加倍,並節省了一半的內存開銷。同時使用新的Composition Api,更好的邏輯複用,類型推導,更高的性能。javascript
使用遞歸對數據進行劫持,多層嵌套內存消耗大,性能不高。 只能劫持預先設置好的數據,直接添加的數據沒法劫持。能夠經過vue.set(xxx)。 對數組的操做只能是內部劫持的7種方法,直接修改下標不能觸發響應式。html
關於3.0的一些基本api的用法這裏就不詳細介紹,咱們今天着重講一下,reactive和effect的實現,也就是3.0核心的響應式原理是怎麼實現,以及怎樣收集依賴。Composition Apivue
/** * 這是第一步,實現數據的劫持 */
function reactive(target) {
return createReactiveObject(target)
}
function createReactiveObject(target) {
if(!isObject(target)) {
return target
}
// 說明已經代理過了
const proxyed = toProxy.get(target)
if (proxyed) {
return proxyed
}
// 防止反覆代理
// reactive(proxy) reactive(proxy)
if (toRow.has(target)) {
return target
}
const handles = {
get(target, key, receiver) {
let result = Reflect.get(target, key, receiver)
// 若是是多層次的對象的,咱們須要遞歸代理
return isObject(result) ? reactive(result) : result
},
set(target, key, value, receiver) {
let oldValue = target[key]
// 咱們不知道設置是否成功,因此要作一個反射,來告訴咱們是否成功
let flag = Reflect.set(target, key, value, receiver)
return flag
},
// 刪除的同上
deleteProperty() {
}
}
const observe = new Proxy(target, handles)
return observe
}
複製代碼
上面的代碼很簡單,咱們遞歸對咱們的數據實現了劫持,咱們用Reflect反射,這裏set中若是直接用target[key] = value來賦值會報錯,用Reflect能夠返回是否set成功。 這裏有幾個問題咱們須要解決:java
let name = {a:123}
reactive(name)
reactive(name)
reactive(name)
複製代碼
let name = {a:123}
let proxy = reactive(name)
reactive(proxy)
reactive(proxy)
複製代碼
爲了解決上面的倆個問題,源碼裏面用倆個WeakMap來作映射表關係的。因此上面的代碼咱們增長以下代碼。一樣WeakMap也是es6中的,不知道的同窗能夠去看看。WeakMapreact
const toProxy = new WeakMap() // 用來放 當前對象:代理過的對象
const toRow = new WeakMap() // 用來放 代理過的對象: 當前對象
// 說明已經代理過了
const proxyed = toProxy.get(target)
if (proxyed) {
return proxyed
}
// 防止反覆代理
// reactive(proxy) reactive(proxy)
if (toRow.has(target)) {
return target
}
// 對已經代理過的,進行保存
toProxy.set(target, observe)
toRow.set(observe, target)
複製代碼
咱們知道在2.0中對數組咱們只能調用特定的7個方法才能讓數據是響應式的。但在3.0中用Proxy咱們能直接監聽到數組的變換。git
/** * 這裏是數組的一個處理,若是push[1,2] => [1,2,3] * 這裏會觸發兩次的set,一次是下標2的set,一次是length的set * 可是length的set的觸發在這裏是無心義的,length的修改並不須要是響應式的 * oldValue !== value 能夠規避length的修改帶來的影響 */
if (!isOwnProperty(target, key)) {
console.log('設置新的屬性')
// 修改屬性
} else if (oldValue !== value) {
console.log('修改原有的屬性')
}
複製代碼
####effect以及依賴收集 下面咱們來實現響應式原理,在vue3.0中effect是一個很是有用的api,它會首先執行一次,而後依賴的數據改變了會自動在執行傳入的函數,至關於computed。(我是這麼理解的)es6
// 棧數組,先進後出
/** * 依賴收集 (數據: [effect]) * 每一個數據對應它的依賴,數據一變執行方法 */
const activeEffectStacks = []
/** * 創建依賴關係 * 數據結構 * * (WeakMap): { * target: (Map) { * key: (Set) [effect,effect] * } * } */
const targetMap = new WeakMap()
function track(target, key) {
let effect = activeEffectStacks[activeEffectStacks.length - 1]
if (effect) {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
if (!deps.has(effect)) {
deps.add(effect)
}
}
}
/** * 第二步,實現數據的響應式 * 數據變通知依賴的數據更新 * * 反作用,先會執行一次,當數據變話的時候在執行一次 * 這裏面設計到一個依賴收集的東西,源碼裏面用一個棧(數組[])來作的 * */
function effect(fn) {
const effectFun = createReactiveEffect(fn)
effectFun()
}
function createReactiveEffect(fn) {
const effect = function() {
run(effect, fn)
}
return effect
}
function run(effect, fn) {
try {
// 棧裏面已經拿到數據了之後,清掉保證數據量
// try 保證fn執行報錯時,必定能將棧清除
activeEffectStacks.push(effect)
fn()
} finally{
activeEffectStacks.pop(effect)
}
}
// 這裏增長依賴收集
get(target, key, receiver) {
let result = Reflect.get(target, key, receiver)
// 進行依賴收集
/** * 這裏很巧妙,在第一次調用effect的時候,必定能觸發一次target的get方法的 * 此時咱們將依賴的關係創建 */
track(target, key)
// 若是是多層次的對象的,咱們須要遞歸代理
return isObject(result) ? reactive(result) : result
},
複製代碼
這裏咱們主要將一下這個收集依賴的數據結構關係。用一個weakMap來放[target, Map], Map[key, Set],Set[effect],這樣咱們就能創建一個target,key,effect的依賴關係,每次target[key]改變的時候咱們就能將對應的effect循環執行一遍。github
/** * 依賴的觸發 */
function trigger(target, type, key) {
// 這裏先不作type的區分
const depsMap = targetMap.get(target)
if (depsMap) {
const deps = depsMap.get(key)
if (deps) {
deps.forEach(effect => {
effect()
})
}
}
}
// 咱們在get的時候觸發依賴
if (!isOwnProperty(target, key)) {
trigger(target, 'add', key)
console.log('設置新的屬性')
// 修改屬性
} else if (oldValue !== value) {
trigger(target, 'set', key)
console.log('修改原有的屬性')
}
複製代碼
好了,這樣咱們一個完整的一個數據劫持,依賴收集,依賴觸發就基本完成。 完整代碼api