這篇文章主要介紹了Vue3的響應(yīng)式機(jī)制怎么實(shí)現(xiàn)的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Vue3的響應(yīng)式機(jī)制怎么實(shí)現(xiàn)文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。
河?xùn)|ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書未來(lái)市場(chǎng)廣闊!成為成都創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
在javascript中的變量是沒(méi)有響應(yīng)式這么一個(gè)概念的,代碼的執(zhí)行邏輯都是自上而下的,而在Vue框架中,響應(yīng)式是特色功能之一。我們先看個(gè)例子
let num = 1; let double = num * 2; console.log(double); // 2 num = 2; console.log(double); // 2
可以很明顯看出來(lái)double這個(gè)變量和num這個(gè)變量的關(guān)系并不是響應(yīng)式的,如果我們將計(jì)算double的邏輯封裝成一個(gè)函數(shù),當(dāng)num這個(gè)變量的值改變,我們就重新執(zhí)行這個(gè)函數(shù),這樣double的值就會(huì)隨著num的改變而改變,也就是我們俗稱的響應(yīng)式的。
let num = 1; // 將計(jì)算過(guò)程封裝成一個(gè)函數(shù) let getDouble = (n) => n * 2; let double = getDouble(num); console.log(double); // 2 num = 2; // 重新計(jì)算double,這里當(dāng)然也沒(méi)有實(shí)現(xiàn)響應(yīng)式,只是說(shuō)明響應(yīng)式實(shí)現(xiàn)的時(shí)候這個(gè)函數(shù)應(yīng)該再執(zhí)行一次 double = getDouble(num); console.log(double); // 4
雖然實(shí)際開(kāi)發(fā)的過(guò)程中會(huì)比現(xiàn)在這樣簡(jiǎn)單的情況復(fù)雜很多,但是就是可以封裝成一個(gè)函數(shù)去實(shí)現(xiàn),現(xiàn)在的問(wèn)題就在于我們?nèi)绾问沟胐ouble的值會(huì)根據(jù)num變量的改變而重新計(jì)算呢?
如果每一次修改num變量的值,getDouble這個(gè)函數(shù)都能知道并且執(zhí)行,根據(jù)num變量的改變而給double也相應(yīng)的發(fā)生改變,這樣就是一個(gè)響應(yīng)式的雛形了。
在Vue中使用過(guò)三種響應(yīng)式解決方案,分別是defineProperty、 Proxy和value setter。在Vue2中是使用了 defineProperty API,在這之前的文章中有過(guò)較為詳細(xì)的描述,想了解Vue2響應(yīng)式的小伙伴戳這里--->vue響應(yīng)式原理 | vue2篇
在 Vue2 中核心部分就在于 defineProperty 這個(gè)數(shù)據(jù)劫持 API ,當(dāng)我們定義一個(gè)對(duì)象obj,使用 defineProperty 代理 num 屬性,讀取 num 屬性時(shí)執(zhí)行了 get 函數(shù),修改num屬性時(shí)執(zhí)行了 set 函數(shù),我們只需要將計(jì)算 double 的邏輯寫在 set 函數(shù)中,就可以使得每次 num 改變時(shí), double 被相應(yīng)的賦值,也就是響應(yīng)式。
let num = 1;
let detDouble = (n) => n * 2;
let obj = {}
let double = getDouble(num)
Object.defineProperty(obj,'num',{
get() {
return num;
}
set(val){
num = val;
double = getDouble(val)
}
})
console.log(double); // 2
obj.num = 2;
console.log(double); // 4defineProperty缺陷:當(dāng)我們刪除obj.num屬性時(shí),set函數(shù)不會(huì)執(zhí)行,所以在Vue2中我們需要一個(gè)$delete 一個(gè)專門的函數(shù)去刪除數(shù)據(jù)。并且obj對(duì)象中不存在的屬性無(wú)法被劫持,并且修改數(shù)組上的length屬性也是無(wú)效的。
單從 Proxy 的名字我們可以看出它是代理的意思,而 Proxy 的重要意義是解決了 Vue2 響應(yīng)式的缺陷。
Proxy用法:
var proxy = new Proxy(target, handler);
Proxy 對(duì)象的所有用法,都是上面這種形式,不同的只是handler參數(shù)的寫法。其中,new Proxy()表示生成一個(gè)Proxy實(shí)例,target參數(shù)表示所要攔截的目標(biāo)對(duì)象,handler參數(shù)也是一個(gè)對(duì)象,用來(lái)定制攔截行為。
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35在 Proxy 身上支持13種定制攔截
get(target, propKey, receiver):攔截對(duì)象屬性的讀取,比如proxy.foo和proxy['foo']。
set(target, propKey, value, receiver):攔截對(duì)象屬性的設(shè)置,比如proxy.foo = v或proxy['foo'] = v,返回一個(gè)布爾值。
has(target, propKey):攔截propKey in proxy的操作,返回一個(gè)布爾值。
deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個(gè)布爾值。
ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環(huán),返回一個(gè)數(shù)組。該方法返回目標(biāo)對(duì)象所有自身的屬性的屬性名,而Object.keys()的返回結(jié)果僅包括目標(biāo)對(duì)象自身的可遍歷屬性。
getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對(duì)象。
defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個(gè)布爾值。
preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個(gè)布爾值。
getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個(gè)對(duì)象。
isExtensible(target):攔截Object.isExtensible(proxy),返回一個(gè)布爾值。
setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個(gè)布爾值。如果目標(biāo)對(duì)象是函數(shù),那么還有兩種額外操作可以攔截。
apply(target, object, args):攔截 Proxy 實(shí)例作為函數(shù)調(diào)用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
construct(target, args):攔截 Proxy 實(shí)例作為構(gòu)造函數(shù)調(diào)用的操作,比如new proxy(...args)。
在ES6中官方新定義了 Reflect 對(duì)象,在ES6之前對(duì)象上的所有的方法都是直接掛載在對(duì)象這個(gè)構(gòu)造函數(shù)的原型身上,而未來(lái)對(duì)象可能還會(huì)有很多方法,如果全部掛載在原型上會(huì)顯得比較臃腫,而 Reflect 對(duì)象就是為了分擔(dān) Object的壓力。
(1) 將Object對(duì)象的一
些明顯屬于語(yǔ)言內(nèi)部的方法(比如Object.defineProperty),放到Reflect對(duì)象上現(xiàn)階段,某些方法同時(shí)在Object和Reflect對(duì)象上部署,未來(lái)的新方法將只部署在Reflect對(duì)象上。也就是說(shuō),從Reflect對(duì)象上可以拿到語(yǔ)言內(nèi)部的方法。
(2) 修改某些Object方法的返回結(jié)果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)在無(wú)法定義屬性時(shí),會(huì)拋出一個(gè)錯(cuò)誤,而Reflect.defineProperty(obj, name, desc)則會(huì)返回false。
// 老寫法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新寫法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}(3) 讓Object操作都變成函數(shù)行為。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函數(shù)行為。
// 老寫法 'assign' in Object // true // 新寫法 Reflect.has(Object, 'assign') // true
(4)Reflect對(duì)象的方法與Proxy對(duì)象的方法一一對(duì)應(yīng),只要是Proxy對(duì)象的方法,就能在Reflect對(duì)象上找到對(duì)應(yīng)的方法。這就讓Proxy對(duì)象可以方便地調(diào)用對(duì)應(yīng)的Reflect方法,完成默認(rèn)行為,作為修改行為的基礎(chǔ)。也就是說(shuō),不管Proxy怎么修改默認(rèn)行為,你總可以在Reflect上獲取默認(rèn)行為。
Proxy(target, {
set: function(target, name, value, receiver) {
var success = Reflect.set(target, name, value, receiver);
if (success) {
console.log('property ' + name + ' on ' + target + ' set to ' + value);
}
return success;
}
});所以我們?cè)谶@里會(huì)使用到 Proxy 和 Reflect 對(duì)象的方與 Proxy 一一對(duì)應(yīng)這一特性,來(lái)實(shí)現(xiàn)Vue3的響應(yīng)式原理。
在Vue3中響應(yīng)式的核心方法是
function reactive (target){
// 返回一個(gè)響應(yīng)式對(duì)象
return createReactiveObject(target);
}根據(jù)我們前面所做的鋪墊,所以我們會(huì)使用 Proxy 代理我們所需要的相應(yīng)的對(duì)象,同時(shí)使用 Reflect 對(duì)象來(lái)映射。所以我們先初步實(shí)現(xiàn)一下,再慢慢優(yōu)化,盡可能全面。
判斷是否為對(duì)象(方法不唯一,有多種方法)
function isObject(val){
return typeof val === 'object' && val !== null
}盡可能采用函數(shù)式編程,讓每一個(gè)函數(shù)只做一件事,邏輯更加清晰。
function createReactiveObject (target) {
// 首先由于Proxy所代理的是對(duì)象,所以我們需要判斷target,若是原始值直接返回
if(!isObject(target)) {
return target;
}
let handler = {
get(target, key, receiver) {
let res = Reflect.get(target, key, receiver); // 使用Reflect對(duì)象做映射,不修改原對(duì)象
console.log('獲取');
return res;
},
set(target, key, value, receiver) {
let res = Reflect.set(target, key, value, receiver);
console.log('修改');
return res
},
deleteProperty(target, key) {
let res = Reflect.deleteProperty(target, key)
console.log('刪除');
return res;
}
}
let ProxyObj = new Proxy(target,handler); // 被代理過(guò)的對(duì)象
return ProxyObj;
}
但是這樣會(huì)有一個(gè)問(wèn)題,如果我需要代理的對(duì)象是深層嵌套的對(duì)象呢?我們先看看效果

當(dāng)我們深層代理時(shí),我們直接修改深層對(duì)象中的屬性并不會(huì)觸發(fā) Proxy 對(duì)象中的 set 方法,那為什么我們可以修改呢?其實(shí)就是直接訪問(wèn)原對(duì)象中深層對(duì)象的值并修改了,那我們?nèi)绾蝺?yōu)化這個(gè)問(wèn)題呢?
那也需要用到遞歸操作,判斷深層對(duì)象是否被代理了,如果沒(méi)有再執(zhí)行reactive將內(nèi)部未被代理的對(duì)象代理。
那么我們?cè)?get 方法內(nèi)部就不能直接將映射之后的 res 返回出去了
get(target, key, receiver) {
let res = Reflect.get(target, key, receiver); // 使用Reflect對(duì)象做映射,不修改原對(duì)象
console.log('獲取');
// 判斷代理之后的對(duì)象是否內(nèi)部含有對(duì)象,如果有的話就遞歸一次
return isObject(res) ? reactive(res) : res;
}這樣我們就實(shí)現(xiàn)了對(duì)象的深層代理,并且只有當(dāng)我們?cè)L問(wèn)到內(nèi)部嵌套的對(duì)象時(shí)我們才 會(huì)去遞歸調(diào)用reactive ,這樣不僅可以實(shí)現(xiàn)深層代理,并且節(jié)約了性能,但是其實(shí)我們還沒(méi)有徹底完善,我們來(lái)看看下面這段代碼
let proxy = reactive({name: '寒月十九', message: { like: 'coding' }});
reactive(proxy);
reactive(proxy);
reactive(proxy);這樣是不是合法的,當(dāng)然是合法的,但是沒(méi)有必要也沒(méi)有意義,所以為了避免被代理過(guò)的對(duì)象,再次被代理,太浪費(fèi)性能,所以我們需要將被代理的對(duì)象打上標(biāo)記,這樣當(dāng)帶被代理過(guò)的對(duì)象訪問(wèn)到時(shí),直接將被代理過(guò)的對(duì)象返回,不需要再次代理。
在 Vue3 中,使用了hash表做映射,來(lái)記錄是否已經(jīng)被代理了。
// WeakMap-弱引用對(duì)象,一旦弱引用對(duì)象未被使用,會(huì)被垃圾回收機(jī)制回收
let toProxy = new WeakMap(); // 存放形式 { 原對(duì)象(key): 代理過(guò)的對(duì)象(value)}
let toRow = new WeakMap(); // 存放形式 { 代理過(guò)的對(duì)象(key): 原對(duì)象(value)}let ProxyObj = new Proxy(target,handler); // 被代理過(guò)的對(duì)象
toProxy.set(target,ProxyObj);
toRow.set(ProxyObj.target);
return ProxyObj;
let ByProxy = toProxy.get(target);
// 防止多次代理
if(ByProxy) { // 如果在WeakMap中可以取到值,則說(shuō)明已經(jīng)被代理過(guò)了,直接返回被代理過(guò)的對(duì)象
return ByProxy;
}
// 防止多層代理
if(toRow.get(target)) {
return target
}
// 為了防止下面這種寫法(多層代理)
// let proxy2 = reactive(proxy);
// let proxy3 = reactive(proxy2);
// 其實(shí)本質(zhì)上與下面這種寫法沒(méi)有區(qū)別(多次代理)
// reactive(proxy);
// reactive(proxy);
// reactive(proxy);
let arr = [1 ,2 ,3 ,4]; let proxy = reactive(arr); proxy.push(5); // 在set內(nèi)部其實(shí)會(huì)干兩件事,首先會(huì)將5這個(gè)值添加到數(shù)組下標(biāo)4的地方,并且會(huì)修改length的值

與 Vue2 的數(shù)據(jù)劫持相比,Vue3 中的 Proxy 可以直接修改數(shù)組的長(zhǎng)度,但是這樣我們需要在 set 方法中判斷我們是要在代理對(duì)象身上添加屬性還是修改屬性。
因?yàn)楦乱晥D的函數(shù)會(huì)在set函數(shù)中調(diào)用,我們向數(shù)組中進(jìn)行操作會(huì)觸發(fā)兩次更新視圖,所以我們需要做一些優(yōu)化。
// 判斷屬性是否原本存在
function hasOwn(target,key) {
return target.hasOwnProperty(key);
}
set(target, key, value, receiver) {
let res = Reflect.set(target, key, value, receiver);
// 判斷是新增屬性還是修改屬性
let hadKey = hasOwn(target,key);
let oldValue = target[key];
if(!hadKey) { // 新增屬性
console.log('新增屬性');
}else if(oldValue !== value){
console.log('修改屬性');
}
return res
},避免多次更新視圖,比如修改的值與原來(lái)一致就不更新視圖,在上面兩個(gè)判斷條件中添加更新視圖的函數(shù),就不會(huì)多次更新視圖。
function isObject(val) {
return typeof val === 'object' && val !== null
}
function reactive(target) {
// 返回一個(gè)響應(yīng)式對(duì)象
return createReactiveObject(target);
}
// 判斷屬性是否原本存在
function hasOwn(target, key) {
return target.hasOwnProperty(key);
}
// WeakMap-弱引用對(duì)象,一旦弱引用對(duì)象未被使用,會(huì)被垃圾回收機(jī)制回收
let toProxy = new WeakMap(); // 存放形式 { 原對(duì)象(key): 代理過(guò)的對(duì)象(value)}
let toRow = new WeakMap(); // 存放形式 { 代理過(guò)的對(duì)象(key): 原對(duì)象(value)}
function createReactiveObject(target) {
// 首先由于Proxy所代理的是對(duì)象,所以我們需要判斷target,若是原始值直接返回
if (!isObject(target)) {
return target;
}
let ByProxy = toProxy.get(target);
// 防止多次代理
if (ByProxy) { // 如果在WeakMap中可以取到值,則說(shuō)明已經(jīng)被代理過(guò)了,直接返回被代理過(guò)的對(duì)象
return ByProxy;
}
// 防止多層代理
if (toRow.get(target)) {
return target
}
let handler = {
get(target, key, receiver) {
let res = Reflect.get(target, key, receiver); // 使用Reflect對(duì)象做映射,不修改原對(duì)象
console.log('獲取');
return isObject(res) ? reactive(res) : res;
},
set(target, key, value, receiver) {
let res = Reflect.set(target, key, value, receiver);
// 判斷是新增屬性還是修改屬性
let hadKey = hasOwn(target, key);
let oldValue = target[key];
if (!hadKey) { // 新增屬性
console.log('新增屬性');
} else if (oldValue !== value) {
console.log('修改屬性');
}
return res
},
deleteProperty(target, key) {
let res = Reflect.deleteProperty(target, key)
console.log('刪除');
return res;
}
}
let ProxyObj = new Proxy(target, handler); // 被代理過(guò)的對(duì)象
return ProxyObj;
}
// let proxy = reactive({name: '寒月十九'});
// proxy.name = '十九';
// console.log(proxy.name);
// delete proxy.name;
// console.log(proxy.name);
// let proxy = reactive({name: '寒月十九', message: { like: 'coding' }});
// proxy.message.like = 'writing';
// console.log('====================================');
// console.log(proxy.message.like);
// console.log('====================================');
let arr = [1, 2, 3, 4];
let proxy = reactive(arr);
proxy.push(5)在IE11以下的瀏覽器都不兼容,所以如果使用 Vue3 開(kāi)發(fā)一個(gè)單頁(yè)應(yīng)用的項(xiàng)目,需要考慮到兼容性問(wèn)題,需要我們做額外的很多操作,才能使得IE11 以下的版本能夠兼容。
關(guān)于“Vue3的響應(yīng)式機(jī)制怎么實(shí)現(xiàn)”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Vue3的響應(yīng)式機(jī)制怎么實(shí)現(xiàn)”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
當(dāng)前標(biāo)題:Vue3的響應(yīng)式機(jī)制怎么實(shí)現(xiàn)
URL分享:http://www.chinadenli.net/article48/pgcgep.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計(jì)、網(wǎng)站設(shè)計(jì)、品牌網(wǎng)站設(shè)計(jì)、商城網(wǎng)站、用戶體驗(yàn)、網(wǎng)站維護(hù)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)