欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3響應式對象是如何實現(xiàn)的(1)

 更新時間:2022年08月30日 10:33:20   作者:???????咕咕雞_  
這篇文章主要介紹了Vue3響應式對象是如何實現(xiàn)的,文章圍繞主題展開詳細的內容介紹具有一定的參考價值,需要的小伙伴可以參考一下

簡單的響應式實現(xiàn)

為了方便說明,先來看一個簡單的例子。

const obj = { text: 'hello vue' }
function effect() {
  document.body.innerText = obj.text
}

這段代碼中,如果obj是一個響應式數(shù)據,會產生什么效果呢?當obj.text中的內容改變時,document.body.innerText也會隨之改變,從而修改頁面上顯示的內容。因此,如果僅從這個簡單的例子出發(fā),在修改obj后,再次執(zhí)行effect(),就能夠實現(xiàn)響應式。此時,effect()被稱為副作用函數(shù),其副作用體現(xiàn)在document.body.innerText這個非effect()函數(shù)作用域內的值被修改了。

實際使用中,響應式實現(xiàn)要復雜的多。為了進一步實現(xiàn)響應式,讓我們基于上文的例子,再來捋一捋響應式實現(xiàn)需要什么。我們需要在修改響應式數(shù)據后觸發(fā)副作用函數(shù)。仔細思考這句話,這里其實要求了兩個功能:

  • obj讀取時,收集副作用函數(shù);
  • obj修改時,觸發(fā)收集的副作用函數(shù);

問題更清晰了,我們只需要攔截讀取和修改操作分別進行收集和觸發(fā),就能夠實現(xiàn)響應式了。這里我們可以使用Proxy

Proxy與響應式

為什么需要Proxy?

Vue3的核心特征之一就是響應式,而實現(xiàn)數(shù)據響應式依賴于底層的Proxy。因此,想要完成Vue的響應式功能,首先需要理解Proxy。

reactive為例,當想要創(chuàng)建一個響應式對象時,僅需要使用reactive對原始對象進行包裹。

例如:

const user = reactive({
  age: 25
})

在Vue3的官方文檔中,對reactive的描述是:返回對象的響應式副本。既然是響應式副本,就需要產生一個與原始對象含有相同內容的響應式對象,之后使用這個響應式對象替代原始對象,代替原始對象去做事情。這就體現(xiàn)了Proxy的價值——創(chuàng)建原始對象的代理。

Proxy創(chuàng)建的代理對象與原始對象有何不同?

先來看看如何使用Proxy創(chuàng)建代理對象。

let proxy = new Proxy(target, handler)

Proxy可以接收兩個參數(shù),其中,target表示被代理的原始對象,handler表示代理配置,代理配置中的捕捉器允許我們攔截重新定義針對代理對象的操作。因此,如果在Proxy中,不進行任何代理配置,Proxy僅會成為原始對象的一個透明包裝器(僅創(chuàng)建原始對象副本,但不產生任何其它功能)。因此,為了利用Proxy實現(xiàn)響應式對象,我們必須使用Proxy中的代理配置

在JavaScript規(guī)范中,存在一個描述JavaScript運行機制的“內部方法”。其中,讀取屬性的操作被稱為[[Get]],設置屬性的操作被稱為[[Set]]。通常,我們無法直接使用這些“內部方法”,但Proxy給我們提供了捕捉器,使得對讀取和設置操作的攔截成為可能。

const p = new Proxy(obj, {
  get(target, property, receiver) {/*攔截讀取屬性操作*/}
  set(target, property, value, receiver) {/*攔截設置屬性操作*/}
})

其中,target為被代理的原始對象,property為原始對象的屬性名,value為設置目標屬性的值receiver是屬性所在的this對象,即Proxy代理對象。

Proxy是由JavaSciprt引擎實現(xiàn)的ES6特性,因此,沒有ES5的polyfill,也無法使用Babel轉譯。這也是Vue3不支持低版本IE的主要原因。

let fn
const data = { text: 'hello vue' }
const obj = new Proxy(data, {
  get(target, key) {
    fn = effect
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    fn()
    return true
  }
})

多副作用函數(shù)的響應式實現(xiàn)

如果響應式數(shù)據有多個相關的副作用函數(shù)應該如何處理。

在上例中,存儲一個副作用函數(shù)我們可以使用一個fn進行緩存,多個副作用函數(shù),我們在收集時用數(shù)組把它裝起來就好,需要觸發(fā)時,再遍歷數(shù)組挨個觸發(fā)收集到的副作用函數(shù)。

const fns = []
const data = { text: 'hello vue' }
const obj = new Proxy(data, {
  get(target, key) {
    fns.push(effect)
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    fns.forEach(fn => fn())
    return true
  }
})

這樣可以嗎?可以是可以,功能上已經實現(xiàn)了。接下來讓我們來看看有沒有哪些地方可以優(yōu)化。

第一個可以優(yōu)化的點是,需要考慮被重復收集的函數(shù)。

舉個例子:

const effect1 = () => {
  document.body.innerText = obj.text
}
const effect2 = effect1

在這個例子中,effect1effect2都表示同一個函數(shù),但在收集時會被重復收集,執(zhí)行時也會被重復執(zhí)行。這些重復顯然是不必要的,因此,我們需要在收集副作用函數(shù)的時候,干掉重復的函數(shù)。去重可以考慮Set。

const fns = new Set()
const data = { text: 'hello vue' }
const obj = new Proxy(data, {
  get(target, key) {
    fns.push(effect)
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    fns.forEach(fn => fn())
    return true
  }
})

不知道大家是否注意到,為了演示方便,我們在收集元素時,都默認被收集的副作用函數(shù)名是effect。但實際開發(fā)中,函數(shù)名肯定不會是固定的或是有規(guī)律可循的,我們肯定不能按函數(shù)名一個個手動收集,因此,我們要想一種方式能夠不依賴于函數(shù)名進行副作用函數(shù)收集。為了實現(xiàn)這一點,我們將副作用函數(shù)進行包裹,用一個activeEffect統(tǒng)一注冊。

let activeEffect
function effect(fn) {
  activeEffect = fn
  fn()
}

const fns = new Set()
const data = { text: 'hello vue' }
const obj = new Proxy(data, {
  get(target, key) {
    if(activeEffect) {
      fns.add(activeEffect)
    }
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    fns.forEach(fn => fn())
    return true
  }
})

function fn() {
  document.body.innerText = obj.text
}
effect(fn)

到這里,我們已經把容易想到的優(yōu)化都處理了。但這還沒完,這里還有個隱蔽的點沒有考慮到。此時,我們的響應式粒度還不夠細,副作用函數(shù)的收集和觸發(fā)的最小單位是響應式對象,這會導致不必要的副作用函數(shù)更新。例如,我們給出一個obj上不存在的屬性obj.notExist,對其進行賦值操作。為了方便演示,我們在fn()中加一條打印語句,觀察結果。

function fn() {
  document.body.innerText = obj.text
  console.log('Done!')
}

可以看到,副作用函數(shù)被觸發(fā)了。這里obj.notExist屬性在obj上是不存在的,更談不上對應了哪個副作用函數(shù),因此,這里的副作用函數(shù)不應該被觸發(fā),或者換句話說,副作用函數(shù)僅能被對應的響應式對象的屬性影響,且僅在該屬性被修改時觸發(fā)。

更細粒度的觸發(fā)條件就要求我們收集副作用函數(shù)時,針對響應式對象的屬性進行收集。聽上去有點無從下手,讓我們再來捋一捋。既然是響應式對象,首先還是要保證利用響應式對象的Proxy來進行收集和觸發(fā),但在收集的時候,就不能一股腦的把所有屬性對應的副作用函數(shù)塞到一塊了,需要把誰是誰的分清楚(即一個屬性對應了哪些副作用函數(shù)),讓每個屬性擁有自己收集和觸發(fā)副作用函數(shù)的能力,具體對應關系如圖所示。

讓我們從后往前看。首先,屬性的副作用函數(shù)收集器我們可以沿用上文的思路,使用Set實現(xiàn)。再往前,每個屬性都需要配備一個副作用函數(shù)收集器,這是一個一對一的關系,我們可以使用將屬性值作為key,副作用收集器作為value,使用Map實現(xiàn),而這個Map就包含了單個響應式對象的全部內容。

回看我們上面的代碼,其實我們在進行副作用函數(shù)收集的時候,僅使用了一個全局的容器承載。在實際開發(fā)中,我們需要盡量避免大量出現(xiàn)全局定義。因此,我們將響應式對象放進一個全局容器中統(tǒng)一管理。我們需要將每一個對象和其對應的Map建立一對一的映射關系。整體關系如下圖所示:

 按圖中思路,我們在代碼中重新組織數(shù)據結構。

let activeEffect
function effect(fn) {
  activeEffect = fn
  fn()
}

const objsMap = new Map() // 全局容器
const data = { text: 'hello vue' }
const obj = new Proxy(data, {
  get(target, key) {
    if(!activeEffect) return
    let propsMap = objsMap.get(target) // 屬性容器
    if(!propsMap) {
      objsMap.set(target, (propsMap = new Map()))
    }
    let fns = propsMap.get(key) // 副作用函數(shù)收集器
    if(!fns) {
      propsMap.set(key, (fns = new Set()))
    }
    fns.add(activeEffect)
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    const propsMap = objsMap.get(target) // 屬性容器
    if(!propsMap) return
    const fns = propsMap.get(key) // 副作用函數(shù)收集器
    fns && fns.forEach(fn => fn())
    return true
  }
})

function fn() {
  document.body.innerText = obj.text
  console.log('Done!')
}
effect(fn)

執(zhí)行我們更新的代碼,我們就能避免無效更新,只觸發(fā)與屬性相關的更新了。

到這結束了嗎?我們其實還能再做一點優(yōu)化。如果一個響應式對象沒有被引用時,說明這個對象不被使用,不被使用的對象應該被JavaScript的垃圾回收器回收,避免造成內存占用。但Map的特性(Map會被其key值持續(xù)引用)決定了,即使沒有任何引用,Map中的對象也不能被垃圾回收器回收。解決這個問題,只需要用WeakMap來代替Map,WeakMap的key是弱引用。到這里我們終于大功告成了,把功能實現(xiàn)完了。

功能實現(xiàn)完接下來要干嘛?對,要重構,要看有沒有能重構的地方。getset中的內容有點臃腫了,想想一開始說的,get時收集,set時觸發(fā)。最后,就讓我們來抽離封裝這兩部分。

let activeEffect
function effect(fn) {
  activeEffect = fn
  fn()
}

const objsMap = new WeakMap()
const data = { text: 'hello vue' }
const obj = new Proxy(data, {
  get(target, key) {
    track(target, key)
    return target[key]
  },
  set(target, key, newValue) {
    target[key] = newValue
    trigger(target, key)
    return true
  }
})
// 收集
function track(target, key) {
  if(!activeEffect) return
  let propsMap = objsMap.get(target)
  if(!propsMap) {
    objsMap.set(target, (propsMap = new Map()))
  }
  let fns = propsMap.get(key)
  if(!fns) {
    propsMap.set(key, (fns = new Set()))
  }
  fns.add(activeEffect)
}
// 觸發(fā)
function trigger(target, key) {
  const propsMap = objsMap.get(target)
  if(!propsMap) return
  const fns = propsMap.get(key)
  fns && fns.forEach(fn => fn())
}

function fn() {
  document.body.innerText = obj.text
  console.log('Done!')
}
effect(fn)

到此這篇關于Vue3響應式對象是如何實現(xiàn)的的文章就介紹到這了,更多相關Vue3響應式內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • vue大屏展示適配的方法

    vue大屏展示適配的方法

    這篇文章主要為大家詳細介紹了vue大屏展示適配,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • Vue導航守衛(wèi)使用教程詳解

    Vue導航守衛(wèi)使用教程詳解

    這篇文章主要介紹了Vue導航守衛(wèi)使用教程,可以向任意給定的導航守衛(wèi)傳遞next,但是next的使用過程容易出錯,需要確保next在任何給定的導航守衛(wèi)中都被嚴格調用一次
    2023-04-04
  • 關于vuex強刷數(shù)據丟失問題解析

    關于vuex強刷數(shù)據丟失問題解析

    這篇文章主要介紹了關于vuex強刷數(shù)據丟失問題解析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • Vue中使用axios調用后端接口的坑及解決

    Vue中使用axios調用后端接口的坑及解決

    這篇文章主要介紹了Vue中使用axios調用后端接口的坑及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • vue2實現(xiàn)數(shù)據請求顯示loading圖

    vue2實現(xiàn)數(shù)據請求顯示loading圖

    這篇文章主要為大家詳細介紹了vue2實現(xiàn)數(shù)據請求顯示loading圖,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • 詳細聊聊Vue.js中的MVVM

    詳細聊聊Vue.js中的MVVM

    MVVM旨在利用WPF中的數(shù)據綁定函數(shù),通過從視圖層中幾乎刪除所有GUI代碼(代碼隱藏),更好地促進視圖層開發(fā)與模式其余部分的分離,這篇文章主要給大家介紹了關于Vue.js中MVVM的相關資料,需要的朋友可以參考下
    2022-03-03
  • vue實現(xiàn)圖片下載點擊按鈕彈出本地窗口選擇自定義保存路徑功能

    vue實現(xiàn)圖片下載點擊按鈕彈出本地窗口選擇自定義保存路徑功能

    vue前端實現(xiàn)前端下載,并實現(xiàn)點擊按鈕彈出本地窗口,選擇自定義保存路徑,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2023-12-12
  • Vue WatchEffect函數(shù)創(chuàng)建高級偵聽器

    Vue WatchEffect函數(shù)創(chuàng)建高級偵聽器

    watchEffect傳入的函數(shù)會被立即執(zhí)行一次,并且在執(zhí)行的過程中會收集依賴;其次,只有收集的依賴發(fā)生變化時,watchEffect傳入的函數(shù)才會再次執(zhí)行
    2023-03-03
  • 基于vue.js實現(xiàn)的分頁

    基于vue.js實現(xiàn)的分頁

    本文主要給大家介紹基于vue的分頁原生寫法,代碼分為html部分和js部分,簡單易懂,非常不錯,具有參考借鑒價值,需要的朋友參考下吧
    2018-03-03
  • vue中的按鈕綁定事件小案例

    vue中的按鈕綁定事件小案例

    這篇文章主要介紹了vue中的按鈕綁定事件小案例,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11

最新評論