中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue3的響應式機制怎么實現

發布時間:2022-12-27 09:24:53 來源:億速云 閱讀:138 作者:iii 欄目:編程語言

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

一、什么是響應式?

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

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

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

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

num = 2;
// 重新計算double,這里當然也沒有實現響應式,只是說明響應式實現的時候這個函數應該再執行一次
double = getDouble(num);
console.log(double); // 4

雖然實際開發的過程中會比現在這樣簡單的情況復雜很多,但是就是可以封裝成一個函數去實現,現在的問題就在于我們如何使得double的值會根據num變量的改變而重新計算呢?

如果每一次修改num變量的值,getDouble這個函數都能知道并且執行,根據num變量的改變而給double也相應的發生改變,這樣就是一個響應式的雛形了。

二、響應式原理

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

defineProperty API

在 Vue2 中核心部分就在于 defineProperty 這個數據劫持 API ,當我們定義一個對象obj,使用 defineProperty 代理 num 屬性,讀取 num 屬性時執行了 get 函數,修改num屬性時執行了 set 函數,我們只需要將計算 double 的邏輯寫在 set 函數中,就可以使得每次 num 改變時, double 被相應的賦值,也就是響應式。

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缺陷:當我們刪除obj.num屬性時,set函數不會執行,所以在Vue2中我們需要一個$delete 一個專門的函數去刪除數據。并且obj對象中不存在的屬性無法被劫持,并且修改數組上的length屬性也是無效的。

Proxy

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

Proxy用法:

var proxy = new Proxy(target, handler);

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

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) :攔截對象屬性的設置,比如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循環,返回一個數組。該方法返回目標對象所有自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性。

  • 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),返回一個布爾值。如果目標對象是函數,那么還有兩種額外操作可以攔截。

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

  • construct(target, args) :攔截 Proxy 實例作為構造函數調用的操作,比如new proxy(...args)

Reflect

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

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

(2) 修改某些Object方法的返回結果,讓其變得更合理。比如,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操作都變成函數行為。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)讓它們變成了函數行為。

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

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

(4)Reflect對象的方法與Proxy對象的方法一一對應,只要是Proxy對象的方法,就能在Reflect對象上找到對應的方法。這就讓Proxy對象可以方便地調用對應的Reflect方法,完成默認行為,作為修改行為的基礎。也就是說,不管Proxy怎么修改默認行為,你總可以在Reflect上獲取默認行為。

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 一一對應這一特性,來實現Vue3的響應式原理。

三、Vue3響應式原理的實現

在Vue3中響應式的核心方法是

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

根據我們前面所做的鋪墊,所以我們會使用 Proxy 代理我們所需要的相應的對象,同時使用 Reflect 對象來映射。所以我們先初步實現一下,再慢慢優化,盡可能全面。

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

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

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

初步實現

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的響應式機制怎么實現

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

Vue3的響應式機制怎么實現

當我們深層代理時,我們直接修改深層對象中的屬性并不會觸發 Proxy 對象中的 set 方法,那為什么我們可以修改呢?其實就是直接訪問原對象中深層對象的值并修改了,那我們如何優化這個問題呢?

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

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

解決代理對象內部有嵌套對象

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

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

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

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

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

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

// WeakMap-弱引用對象,一旦弱引用對象未被使用,會被垃圾回收機制回收
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中可以取到值,則說明已經被代理過了,直接返回被代理過的對象
    return ByProxy;
}
// 防止多層代理
if(toRow.get(target)) {
    return target
}
// 為了防止下面這種寫法(多層代理)
// let proxy2 = reactive(proxy);
// let proxy3 = reactive(proxy2);
// 其實本質上與下面這種寫法沒有區別(多次代理)
// reactive(proxy);
// reactive(proxy);
// reactive(proxy);

數組相應問題

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

Vue3的響應式機制怎么實現

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

因為更新視圖的函數會在set函數中調用,我們向數組中進行操作會觸發兩次更新視圖,所以我們需要做一些優化。

// 判斷屬性是否原本存在
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
 },

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

完整版代碼

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

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

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

// WeakMap-弱引用對象,一旦弱引用對象未被使用,會被垃圾回收機制回收
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中可以取到值,則說明已經被代理過了,直接返回被代理過的對象
    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 開發一個單頁應用的項目,需要考慮到兼容性問題,需要我們做額外的很多操作,才能使得IE11 以下的版本能夠兼容。

關于“Vue3的響應式機制怎么實現”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Vue3的響應式機制怎么實現”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

博白县| 沿河| 土默特右旗| 大方县| 徐州市| 无锡市| 龙泉市| 包头市| 侯马市| 会东县| 章丘市| 房产| 衡南县| 安平县| 江孜县| 九江县| 申扎县| 宁安市| 紫金县| 遂宁市| 大石桥市| 西峡县| 江川县| 丹东市| 界首市| 治县。| 大关县| 娄底市| 楚雄市| 聂荣县| 宜都市| 遂昌县| 鸡泽县| 康马县| 新化县| 民县| 东宁县| 门头沟区| 平谷区| 平乐县| 成安县|