1. 前言
理解響應式有助于理解代碼邏輯
Vue3 基于proxy
Vue2 基于Object.defineProperty
2. 什么是數(shù)據(jù)響應式
所謂的數(shù)據(jù)響應式就是能夠使數(shù)據(jù)的
變化
可以被檢測
并對這種變化做出響應
的機制
我們常見的
MVVM
框架中要解決的一個核心問題就是連接數(shù)據(jù)層
和視圖層
,
通過數(shù)據(jù)驅動
應用,數(shù)據(jù)變化,視圖更新
,
需要對數(shù)據(jù)做響應式處理
,這樣一旦數(shù)據(jù)發(fā)生變化就可以立即做出``更新處理`
響應式
數(shù)據(jù)變化可偵測, 從而對使用數(shù)據(jù)的地方進行更新
3.Vue中的應用
通過數(shù)據(jù)響應式 加上
虛擬DOM
和patch算法
,可以使我們只需要操作數(shù)據(jù),完全不用
接觸繁瑣的DOM操作
,從而大大提升開發(fā)效率,降低開發(fā)難度
4. 簡要對比
4.1 Vue2
基于
Proxy
的數(shù)據(jù)響應式Vue 2
的響應式系統(tǒng)使用Object.defineProperty
的getter
和setter
杖虾。
Vue2的數(shù)據(jù)響應式會根據(jù)數(shù)據(jù)類型做不同的處理:
對象
就采用Object.defineProperty()
的定義方式來攔截數(shù)據(jù),當數(shù)據(jù)被訪問或者發(fā)生變化時,我們感知并作出響應;
數(shù)組
則通過覆蓋數(shù)組原型
的方法,擴展它的7個變更方法,使這些方法可以額外的做更新通知,從而做出響應
這種機制很好的解決了數(shù)據(jù)響應式的問題,但也存在缺點:
1.比如
初始
化的時候遞歸
遍歷會造成性能損失
2.新增或刪除
屬性時需要用戶使用Vue.set/delete
這樣的特殊api
才能生效
3.對于es6中的Map,Set
這些數(shù)據(jù)結構不支持
等問題
4.2 Vue3
Vue 3
將使用 ES2015 Proxy
作為 其觀察機制徊哑,這將會帶來如下變化:
1.組件實例初始化的速度
提高 100%
2.使用 Proxy節(jié)省
以前一半的內存
開銷弦讽,加快速度,但是存在低瀏覽器版本的不兼容
3.為了繼續(xù)支持IE11
涧偷,Vue 3 將發(fā)布一個支持舊觀察者機制
和新 Proxy 版本的構建
4.編程體驗是一致的,不需要使用特殊的api
5.Vue2實現(xiàn)數(shù)據(jù)的監(jiān)聽的簡要核心代碼
5.1 簡要代碼
if(typeof obj !== "object" || obj == null){
return
}
const keys = Object.keys(obj)
for(let i = 0;i < keys.length;i++){
const key = keys[i]
defineReactive(obj,key,obj[key])
}
console.log("變了",obj)
}
function defineReactive(obj,key,val){
observe(obj)
Object.defineProperty(obj,key,{
get (){
return val
},
set(v){
val = v
update()
}
})
}
function update(){
console.log(obj.yzs)
}
const obj = {}
defineReactive(obj,"yzs","名字")
obj.yzs = "幸福一家人"
5.2 運行
1.可以作為
nodejs
來運行
2.也可以在前端頁面引入查看瀏覽器控制臺結果
5.3 分析
1.攔截每個
key
從而可以偵測
數(shù)據(jù)的變化
2.只能對于對象
支持比較好 , 數(shù)組的話就得單獨寫
3.遍歷每個key成本高
:內存大,速度慢
4.新增或刪除 屬性無法監(jiān)聽 需要使用特殊的API
5.Vue.set(obj,"yzs","幸福一家人")
6.Vue.delete(obj,"幸福一家人")
7.不支持 Map,Set,Class等數(shù)據(jù)結構
6. Vue3響應式簡要代碼
6.1 核心代碼
function reactive(obj) {
return new Proxy(obj,{
get(target,key){
console.log("get 的key",key);
return target[key]
},
set(target,key,val){
// notify 通知
console.log("set 的key",key);
target[key] = val
},
deleteProperty(target,key){
// notify 通知
console.log("delete 的key",key);
delete target[key]
}
})
}
const state = reactive({
name:"yzs"
})
state.yzs
state.yzs = "yzs001"
delete state.yzs
state.age = 31
state.age
6.2 分析
代理整個對象,從而偵測數(shù)據(jù)變化
1.
語言
級別的支持
對象數(shù)組 都可以監(jiān)聽
2.Proxy
原理就是 在對象外面套一層殼
,這個殼就是Proxy ,
屬于懶處理
不訪問不進行處理
例如:不恰當?shù)牧凶?Vue2就是全員檢測 Vue3就是只針對出門的進行檢測
3.es6的proxy
數(shù)據(jù)響應式,很好的解決了以上問題
4.上面的代碼其實只能檢測到 單層對象對象里面嵌套的話檢測不到,我把代碼貼到下邊,有興趣的可以看看
7. 嵌套對象監(jiān)聽
//代理整個對象,從而偵測數(shù)據(jù)變化
function reactive(obj) {
return new Proxy(obj,{
get(target,key){
console.log("get 的key",key);
// 依賴手機
track(target,key)
return typeof target[key] === "object"
? reactive(target[key])
: target[key]
},
set(target,key,val){
// notify 通知
console.log("set 的key",key);
trigger(target,key)
target[key] = val
},
deleteProperty(target,key){
// notify 通知
console.log("delete 的key",key);
delete target[key]
}
})
}
// 臨時存儲副作用函數(shù)
const effectStack = []
// 1. 依賴 收集函數(shù)
// 包裝 fn
// 立即執(zhí)行 fn
// 返回 fn
function effect(fn) {
const e = createReactiveEffect(fn)
e()
return e
}
function createReactiveEffect(fn) {
const effect = function () {
try {
effectStack.push(fn)
return fn()
}
catch (error) {
} finally{
effectStack.pop()
}
}
return effect
}
// 保存依賴關系的數(shù)據(jù)結構
const targetMap = new WeakMap()
// 弱引用 不去影響垃圾回收機制
// 2. 依賴收集:建立 target/key 和 fn 之間的映射關系
function track(target,key) {
// 1. 獲取當前的副作用函數(shù)
const effect = effectStack[effectStack.length - 1]
if(effect){
// 2. 取出 target/key 對應的map
let depMap = targetMap.get(target)
if(!depMap){
depMap = new Map()
targetMap.set(target,depMap)
}
// 3. 獲取key 對應的 set
let deps = depMap.get(key)
if(!deps){
deps = new Set()
depMap.set(key,deps)
}
// 4. 存入set
deps.add(effect)
}
}
// 3. 觸發(fā)更新函數(shù): 當某個響應數(shù)據(jù)發(fā)生變化,根據(jù) target key 獲取對應的 fn并執(zhí)行他們
function trigger(target,key) {
// 1. 獲取 target/key 對應的set 并遍歷執(zhí)行他們
const depMap = targetMap.get(target)
if(depMap){
const deps = depMap.get(key)
if(deps){
deps.forEach(dep =>dep());
}
}
}
const state = reactive({
name:"yzs",
children:{
age:3
}
})
// state.children.age
// state.children = {num:2}
effect(()=>{
console.log("effect-1",state.name);
})
effect(()=>{
console.log("effect-2",state.name,state.children.age);
})
state.name = "yzs001"
state.children.age = 30