本篇文章給大家?guī)砹藇ue2&vue3數(shù)據(jù)響應(yīng)式原理分析及手動實現(xiàn)的相關(guān)知識,數(shù)據(jù)響應(yīng)式視圖跟數(shù)據(jù)是自動更新的,數(shù)據(jù)更新的時候視圖是自動的更新的追蹤數(shù)據(jù)的變化,希望對大家有幫助。
數(shù)據(jù)響應(yīng)式
- 視圖跟數(shù)據(jù)是自動更新的,數(shù)據(jù)更新的時候視圖是自動的更新的
- 追蹤數(shù)據(jù)的變化,在讀取數(shù)據(jù)或者設(shè)置數(shù)據(jù)的時候能夠做一些劫持的一些操作
- vue2 使用defineProperty
- vue3 改用Proxy
使用defineProperty
如何追蹤變化
var obj = {}var age Object.defineProperty(obj, 'age', { get: function() { consoel.log('get age ...') return age }, set: function(val) { console.log('set age ...') age = val }})obj.age =100 //set age ...console.log(obj.age)//get age ...
對象obj在取age屬性的時候會調(diào)用數(shù)據(jù)劫持的get方法
在給age屬性賦值的時候會調(diào)用set方法
那怎么使用Object.defineProperty實現(xiàn)一個數(shù)據(jù)響應(yīng)式呢
function defineReactive(data) { if (!data || Object.prototype.toString.call(data) !== '[object Object]') return; for (let key in data) { let val = data[key]; Object.defineProperty(data, key, { enumerable: true, //可枚舉 configurable: true, //可配置 get: function() { track(data, key); return val; }, set: function() { trigger(val, key); }, }); if (typeof val === "object") { defineReactive(val); } }}function trigger(val, key) { console.log("sue set", val, key);}function track(val, key) { console.log("sue set", val, key);}const data = { name:'better', firends:['1','2']}defineReactive(data)console.log(data.name)console.log(data.firends[1])console.log(data.firends[0])console.log(Object.prototype.toString.call(data))
這個函數(shù)defineReactve
用來對Object.defineProperty
進(jìn)行封裝,從函數(shù)名可以看出,起作用就是定義一個響應(yīng)式數(shù)據(jù),封裝后只需要傳遞data,key和val就行
每當(dāng)從data中讀取key的時候觸發(fā)track函數(shù),往data的key中設(shè)置數(shù)據(jù)時,set函數(shù)中的trigger函數(shù)觸發(fā)
數(shù)組的響應(yīng)式
我們通過Array原型上的方法來改變數(shù)組的內(nèi)容不會觸發(fā)getter和setter
整理發(fā)現(xiàn)Array原型中可以改變數(shù)組自身內(nèi)容
的方法有7個,分別push
pop
shift
unshift
splice
sort
reverse
vue2 改寫了這這7種方法
實現(xiàn)方式:
以Array.propertype為原型創(chuàng)建一個arrayMethods對象,再使用Object.setPropertypeOf(o, arryMethods)
將o的__proto__指向arrayMethods
如何收集依賴
使用
<template><p>{{name}}</p></template>
該模板中使用數(shù)據(jù) name
, 我們要觀察數(shù)據(jù), 當(dāng)數(shù)據(jù)的屬性發(fā)生變化的時候, 可以通知哪些使用的地方,
這就是我們要先收集依賴,即把用到數(shù)據(jù)name的地方收集起來,然后等數(shù)據(jù)變化的時候,把之前收集好的依賴循環(huán)觸發(fā)一遍,總結(jié)來說就是getter中收集依賴,在setter中觸發(fā)依賴
使用proxy
Proxy對象用于創(chuàng)建一個對象的代理, 從而實現(xiàn)基本操作的攔截和定義(如屬性查找、賦值、枚舉、函數(shù)掉用等)
const p = new Proxy(target, handler)
-
target
-
要使用
Proxy
包裝的目標(biāo)對象(可以是任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個代理)。 -
handler
-
一個通常以函數(shù)作為屬性的對象,各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時代理
p
的行為。
reflect是一個內(nèi)置對象, 他提供攔截javascript操作的方法, 這些方法和Proxy handlers相同
Reflect.set將值分配給屬性的函數(shù)。返回一個Boolean 如果更新成功則返回true
Reflect.get獲取對象身上某個屬性的值,類似target[name]
如何實現(xiàn)劫持
const dinner = { meal:'111'}const handler = { get(target, prop) { console.log('get...', prop) return Reflect.get(...arguments) }, set(target, key, value) { console.log('get...', prop) console.log('set',key,value) return Reflect.set(...arguments) }}const proxy = new Proxy(dinner, handler)console.log(proxy.meal)console.log(proxy.meal)
代碼中dinner 對象代理到handler上
跟defineProperty
區(qū)別defineProperty
的屬性需要遍歷才能監(jiān)管所有屬性
使用proxy可以將對象所有屬性進(jìn)行代理
用proxy實現(xiàn)一個模擬響應(yīng)式
function reactive(obj) { const handler = { get(target, prop, receiver) { track(target, prop); const value = Reflect.get(...arguments); if(typeof value === 'Object') { reactive(value) }else { return value } }, set(target,key, value, receiver) { trigger(target,key, value); return Reflect.set(...arguments); }, }; return new Proxy(obj,handler)}function track(data, key) { console.log("sue set", data, key);}function trigger(data, key,value) { console.log("sue set", key,':',value);}const dinner = { name:'haochi1'}const proxy =reactive(dinner)proxy.name proxy.list = []proxy.list.push(1)
執(zhí)行后自動打印
思考:為啥只在get中使用遞歸,set不使用呢?
賦值也需要先get
簡單總結(jié):
- vue2 (淺響應(yīng)式)
- 遍歷data,使用defineProperty攔截所有屬性
- 當(dāng)用戶操作視圖,會觸發(fā)set攔截器
- set先改變當(dāng)前數(shù)據(jù), 再通知wartch, 讓watch去通知視圖更新
- 視圖重繪, 再次從get中獲取對應(yīng)的數(shù)據(jù)
- vue3 (深度響應(yīng)式) :
-
使用proxy 進(jìn)行代理;攔截data任意屬性的任意操作(13種), 包括屬性的讀寫, 屬性的添加, 屬性的刪除等等
-
使用Reflect進(jìn)行反射; 動態(tài)對被代理的對象的相應(yīng)屬性進(jìn)行特定的操作
-
代理對象(proxy)的反射對象(reflect)必須相互配合才能實現(xiàn)響應(yīng)式
兩者的不同
Proxy能劫持整個對象,而Object.defineProperty只能劫持對象的屬性; 前者遞歸返回屬性對應(yīng)的值的代理即可實現(xiàn)響應(yīng)式,后者需要深度遍歷每個屬性,后者對數(shù)組的操作很不友好.