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