真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Vue3的響應(yīng)式機(jī)制怎么實現(xiàn)

這篇文章主要介紹了Vue3的響應(yīng)式機(jī)制怎么實現(xiàn)的相關(guān)知識,內(nèi)容詳細(xì)易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Vue3的響應(yīng)式機(jī)制怎么實現(xiàn)文章都會有所收獲,下面我們一起來看看吧。

河?xùn)|ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為成都創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!

一、什么是響應(yīng)式?

在javascript中的變量是沒有響應(yīng)式這么一個概念的,代碼的執(zhí)行邏輯都是自上而下的,而在Vue框架中,響應(yīng)式是特色功能之一。我們先看個例子

let num = 1;
let double = num * 2;
console.log(double); // 2
num = 2;
console.log(double); // 2

可以很明顯看出來double這個變量和num這個變量的關(guān)系并不是響應(yīng)式的,如果我們將計算double的邏輯封裝成一個函數(shù),當(dāng)num這個變量的值改變,我們就重新執(zhí)行這個函數(shù),這樣double的值就會隨著num的改變而改變,也就是我們俗稱的響應(yīng)式的。

let num = 1;
// 將計算過程封裝成一個函數(shù)
let getDouble = (n) => n * 2;
let double = getDouble(num);
console.log(double); // 2

num = 2;
// 重新計算double,這里當(dāng)然也沒有實現(xiàn)響應(yīng)式,只是說明響應(yīng)式實現(xiàn)的時候這個函數(shù)應(yīng)該再執(zhí)行一次
double = getDouble(num);
console.log(double); // 4

雖然實際開發(fā)的過程中會比現(xiàn)在這樣簡單的情況復(fù)雜很多,但是就是可以封裝成一個函數(shù)去實現(xiàn),現(xiàn)在的問題就在于我們?nèi)绾问沟胐ouble的值會根據(jù)num變量的改變而重新計算呢?

如果每一次修改num變量的值,getDouble這個函數(shù)都能知道并且執(zhí)行,根據(jù)num變量的改變而給double也相應(yīng)的發(fā)生改變,這樣就是一個響應(yīng)式的雛形了。

二、響應(yīng)式原理

在Vue中使用過三種響應(yīng)式解決方案,分別是definePropertyProxyvalue setter。在Vue2中是使用了 defineProperty API,在這之前的文章中有過較為詳細(xì)的描述,想了解Vue2響應(yīng)式的小伙伴戳這里--->vue響應(yīng)式原理 | vue2篇

defineProperty API

在 Vue2 中核心部分就在于 defineProperty 這個數(shù)據(jù)劫持 API ,當(dāng)我們定義一個對象obj,使用 defineProperty 代理 num 屬性,讀取 num 屬性時執(zhí)行了 get 函數(shù),修改num屬性時執(zhí)行了 set 函數(shù),我們只需要將計算 double 的邏輯寫在 set 函數(shù)中,就可以使得每次 num 改變時, 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); // 4

defineProperty缺陷:當(dāng)我們刪除obj.num屬性時,set函數(shù)不會執(zhí)行,所以在Vue2中我們需要一個$delete 一個專門的函數(shù)去刪除數(shù)據(jù)。并且obj對象中不存在的屬性無法被劫持,并且修改數(shù)組上的length屬性也是無效的。

Proxy

單從 Proxy 的名字我們可以看出它是代理的意思,而 Proxy 的重要意義是解決了 Vue2 響應(yīng)式的缺陷。

Proxy用法:

var proxy = new Proxy(target, handler);

Proxy 對象的所有用法,都是上面這種形式,不同的只是handler參數(shù)的寫法。其中,new Proxy()表示生成一個Proxy實例,target參數(shù)表示所要攔截的目標(biāo)對象,handler參數(shù)也是一個對象,用來定制攔截行為。

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):攔截對象屬性的讀取,比如proxy.fooproxy['foo']。

  • set(target, propKey, value, receiver):攔截對象屬性的設(shè)置,比如proxy.foo = vproxy['foo'] = v,返回一個布爾值。

  • has(target, propKey):攔截propKey in proxy的操作,返回一個布爾值。

  • deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個布爾值。

  • ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環(huán),返回一個數(shù)組。該方法返回目標(biāo)對象所有自身的屬性的屬性名,而Object.keys()的返回結(jié)果僅包括目標(biāo)對象自身的可遍歷屬性。

  • getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。

  • defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一個布爾值。

  • preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個布爾值。

  • getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個對象。

  • isExtensible(target):攔截Object.isExtensible(proxy),返回一個布爾值。

  • setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。如果目標(biāo)對象是函數(shù),那么還有兩種額外操作可以攔截。

  • apply(target, object, args):攔截 Proxy 實例作為函數(shù)調(diào)用的操作,比如proxy(...args)proxy.call(object, ...args)、proxy.apply(...)

  • construct(target, args):攔截 Proxy 實例作為構(gòu)造函數(shù)調(diào)用的操作,比如new proxy(...args)。

Reflect

在ES6中官方新定義了 Reflect 對象,在ES6之前對象上的所有的方法都是直接掛載在對象這個構(gòu)造函數(shù)的原型身上,而未來對象可能還會有很多方法,如果全部掛載在原型上會顯得比較臃腫,而 Reflect 對象就是為了分擔(dān) Object的壓力。

(1) 將Object對象的一 些明顯屬于語言內(nèi)部的方法(比如Object.defineProperty),放到Reflect對象上現(xiàn)階段,某些方法同時在ObjectReflect對象上部署,未來的新方法將只部署在Reflect對象上。也就是說,從Reflect對象上可以拿到語言內(nèi)部的方法。

(2) 修改某些Object方法的返回結(jié)果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)在無法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)則會返回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 objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)讓它們變成了函數(shù)行為。

// 老寫法
'assign' in Object // true

// 新寫法
Reflect.has(Object, 'assign') // true

(4)Reflect對象的方法與Proxy對象的方法一一對應(yīng),只要是Proxy對象的方法,就能在Reflect對象上找到對應(yīng)的方法。這就讓Proxy對象可以方便地調(diào)用對應(yīng)的Reflect方法,完成默認(rèn)行為,作為修改行為的基礎(chǔ)。也就是說,不管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;
  }
});

所以我們在這里會使用到 Proxy 和 Reflect 對象的方與 Proxy 一一對應(yīng)這一特性,來實現(xiàn)Vue3的響應(yīng)式原理。

三、Vue3響應(yīng)式原理的實現(xiàn)

在Vue3中響應(yīng)式的核心方法是

function reactive (target){
    // 返回一個響應(yīng)式對象
    return createReactiveObject(target); 
}

根據(jù)我們前面所做的鋪墊,所以我們會使用 Proxy 代理我們所需要的相應(yīng)的對象,同時使用 Reflect 對象來映射。所以我們先初步實現(xiàn)一下,再慢慢優(yōu)化,盡可能全面。

判斷是否為對象(方法不唯一,有多種方法)

function isObject(val){
    return typeof val === 'object' && val !== null
}

盡可能采用函數(shù)式編程,讓每一個函數(shù)只做一件事,邏輯更加清晰。

初步實現(xiàn)

function createReactiveObject (target) {
    // 首先由于Proxy所代理的是對象,所以我們需要判斷target,若是原始值直接返回
    if(!isObject(target)) {
        return target;
    }
    
    let handler = {
        get(target, key, receiver) {
            let res = Reflect.get(target, key, receiver); // 使用Reflect對象做映射,不修改原對象
            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); // 被代理過的對象
    return ProxyObj;
}

Vue3的響應(yīng)式機(jī)制怎么實現(xiàn)

但是這樣會有一個問題,如果我需要代理的對象是深層嵌套的對象呢?我們先看看效果

Vue3的響應(yīng)式機(jī)制怎么實現(xiàn)

當(dāng)我們深層代理時,我們直接修改深層對象中的屬性并不會觸發(fā) Proxy 對象中的 set 方法,那為什么我們可以修改呢?其實就是直接訪問原對象中深層對象的值并修改了,那我們?nèi)绾蝺?yōu)化這個問題呢?

那也需要用到遞歸操作,判斷深層對象是否被代理了,如果沒有再執(zhí)行reactive將內(nèi)部未被代理的對象代理。

那么我們在 get 方法內(nèi)部就不能直接將映射之后的 res 返回出去了

解決代理對象內(nèi)部有嵌套對象

get(target, key, receiver) {
            let res = Reflect.get(target, key, receiver); // 使用Reflect對象做映射,不修改原對象
            console.log('獲取');
            // 判斷代理之后的對象是否內(nèi)部含有對象,如果有的話就遞歸一次
            return isObject(res) ? reactive(res) : res;
        }

解決對象重復(fù)代理(多次代理、多層代理)

這樣我們就實現(xiàn)了對象的深層代理,并且只有當(dāng)我們訪問到內(nèi)部嵌套的對象時我們才 會去遞歸調(diào)用reactive ,這樣不僅可以實現(xiàn)深層代理,并且節(jié)約了性能,但是其實我們還沒有徹底完善,我們來看看下面這段代碼

let proxy = reactive({name: '寒月十九', message: { like: 'coding' }});
reactive(proxy);
reactive(proxy);
reactive(proxy);

這樣是不是合法的,當(dāng)然是合法的,但是沒有必要也沒有意義,所以為了避免被代理過的對象,再次被代理,太浪費性能,所以我們需要將被代理的對象打上標(biāo)記,這樣當(dāng)帶被代理過的對象訪問到時,直接將被代理過的對象返回,不需要再次代理。

在 Vue3 中,使用了hash表做映射,來記錄是否已經(jīng)被代理了。

// WeakMap-弱引用對象,一旦弱引用對象未被使用,會被垃圾回收機(jī)制回收
let toProxy = new WeakMap();  // 存放形式 { 原對象(key): 代理過的對象(value)}
let toRow = new WeakMap();  // 存放形式 { 代理過的對象(key): 原對象(value)}

let ProxyObj = new Proxy(target,handler); // 被代理過的對象
toProxy.set(target,ProxyObj);
toRow.set(ProxyObj.target);
return ProxyObj;
let ByProxy = toProxy.get(target);
// 防止多次代理
if(ByProxy) { // 如果在WeakMap中可以取到值,則說明已經(jīng)被代理過了,直接返回被代理過的對象
    return ByProxy;
}
// 防止多層代理
if(toRow.get(target)) {
    return target
}
// 為了防止下面這種寫法(多層代理)
// let proxy2 = reactive(proxy);
// let proxy3 = reactive(proxy2);
// 其實本質(zhì)上與下面這種寫法沒有區(qū)別(多次代理)
// reactive(proxy);
// reactive(proxy);
// reactive(proxy);

數(shù)組相應(yīng)問題

let arr = [1 ,2 ,3 ,4];
let proxy = reactive(arr);
proxy.push(5);
// 在set內(nèi)部其實會干兩件事,首先會將5這個值添加到數(shù)組下標(biāo)4的地方,并且會修改length的值

Vue3的響應(yīng)式機(jī)制怎么實現(xiàn)

與 Vue2 的數(shù)據(jù)劫持相比,Vue3 中的 Proxy 可以直接修改數(shù)組的長度,但是這樣我們需要在 set 方法中判斷我們是要在代理對象身上添加屬性還是修改屬性。

因為更新視圖的函數(shù)會在set函數(shù)中調(diào)用,我們向數(shù)組中進(jìn)行操作會觸發(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
 },

避免多次更新視圖,比如修改的值與原來一致就不更新視圖,在上面兩個判斷條件中添加更新視圖的函數(shù),就不會多次更新視圖。

完整版代碼

function isObject(val) {
  return typeof val === 'object' && val !== null
}

function reactive(target) {
  // 返回一個響應(yīng)式對象
  return createReactiveObject(target);
}

// 判斷屬性是否原本存在
function hasOwn(target, key) {
  return target.hasOwnProperty(key);
}

// WeakMap-弱引用對象,一旦弱引用對象未被使用,會被垃圾回收機(jī)制回收
let toProxy = new WeakMap();  // 存放形式 { 原對象(key): 代理過的對象(value)}
let toRow = new WeakMap();  // 存放形式 { 代理過的對象(key): 原對象(value)}

function createReactiveObject(target) {
  // 首先由于Proxy所代理的是對象,所以我們需要判斷target,若是原始值直接返回
  if (!isObject(target)) {
    return target;
  }

  let ByProxy = toProxy.get(target);
  // 防止多次代理
  if (ByProxy) { // 如果在WeakMap中可以取到值,則說明已經(jīng)被代理過了,直接返回被代理過的對象
    return ByProxy;
  }
  // 防止多層代理
  if (toRow.get(target)) {
    return target
  }

  let handler = {
    get(target, key, receiver) {
      let res = Reflect.get(target, key, receiver); // 使用Reflect對象做映射,不修改原對象
      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); // 被代理過的對象
  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)

Proxy的缺陷

在IE11以下的瀏覽器都不兼容,所以如果使用 Vue3 開發(fā)一個單頁應(yīng)用的項目,需要考慮到兼容性問題,需要我們做額外的很多操作,才能使得IE11 以下的版本能夠兼容。

關(guān)于“Vue3的響應(yīng)式機(jī)制怎么實現(xiàn)”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“Vue3的響應(yīng)式機(jī)制怎么實現(xiàn)”知識都有一定的了解,大家如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


標(biāo)題名稱:Vue3的響應(yīng)式機(jī)制怎么實現(xiàn)
文章位置:http://weahome.cn/article/pgcgep.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部