国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【Vue3響應(yīng)式原理#02】Proxy and Reflect

這篇具有很好參考價值的文章主要介紹了【Vue3響應(yīng)式原理#02】Proxy and Reflect。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

專欄分享:vue2源碼專欄,vue3源碼專欄,vue router源碼專欄,玩具項目專欄,硬核??推薦??

歡迎各位ITer關(guān)注點贊收藏??????

背景

以下是柏成根據(jù)Vue3官方課程整理的響應(yīng)式書面文檔 - 第二節(jié),課程鏈接在此:Proxy and Reflect - Vue 3 Reactivity | Vue Mastery

本篇文章將解決 上一篇文章 結(jié)尾遺留的問題:如何讓代碼自動實現(xiàn)響應(yīng)性? 換句話說就是,如何讓我們的 effect 自動保存 & 自動重新運行?

在 上一篇文章 中,我們最終運行的代碼長這樣

【Vue3響應(yīng)式原理#02】Proxy and Reflect

聰明的你會立馬發(fā)現(xiàn),我們現(xiàn)在仍要手動調(diào)用 track() 來保存 effect;手動調(diào)用 trigger() 來運行 effects,這不是脫褲子放屁么

我們想讓我們的響應(yīng)性引擎自動調(diào)用 track()trigger()。那么問題就來了,何時才是調(diào)用它們的最好時機呢?

從邏輯上來說,如果訪問了對象的屬性,就是我們調(diào)用 track() 去保存 effect 的最佳時機;如果對象的屬性改變了,就是我們調(diào)用 trigger() 來運行 effects 的最佳時機

所以問題變成了,我們該如何攔截對象屬性的訪問和賦值操作?

Proxy(代理)

在 MDN 上的 Proxy 對象是這樣定義的

Proxy 對象用于創(chuàng)建一個對象的代理,從而實現(xiàn)基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。

也可以理解為在操作目標對象前架設(shè)一層代理,將所有本該我們手動編寫的程序交由代理來處理,生活中也有許許多多的“proxy”, 如代購,中介,因為他們所有的行為都不會直接觸達到目標對象

語法

  • target: 要使用 Proxy 包裝的目標對象(可以是任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個代理)

  • handler: 一個通常以函數(shù)作為屬性的對象,用來定制攔截行為;它包含有 Proxy 的各個捕獲器(trap),例如 handler.get() / handler.set()

const p = new Proxy(target, handler)

常用方法

比較常用的兩個方法就是 get()set() 方法

方法 描述
handler.get(target, key, ?receiver) 屬性讀取操作的捕捉器
handler.set(target, key, value, ? receiver) 屬性設(shè)置操作的捕捉器

handler.get

用于代理目標對象的屬性讀取操作,其接受三個參數(shù) handler.get(target, propKey, ?receiver)

  • target: 目標對象
  • key: 屬性名
  • receiver: Proxy 本身或者繼承它的對象,后面會重點介紹

舉個栗子

const origin = {}
const obj = new Proxy(origin, {
  get: function (target, key, receiver) {
		return 10
  }
})

obj.a // 10
obj.b // 10
origin.a // undefined
origin.b // undefined

在這個栗子中,我們給一個空對象 origin 的 get 架設(shè)了一層代理,所有 get 操作都會直接返回我們定制的數(shù)字10

需要注意的是,代理只會對 proxy 對象生效,如訪問上方的 origin 對象就沒有任何效果

handler.set

用于代理目標對象的屬性設(shè)置操作,其接受四個參數(shù) handler.set(target, key, value, ?receiver)

  • target: 目標對象
  • key: 屬性名
  • value: 新屬性值
  • receiver: Proxy 本身或者繼承它的對象,后面會重點介紹
const obj = new Proxy({}, {
  set: function(target, key, value, receiver) {
    target[key] = value
    console.log('property set: ' + key + ' = ' + value)
    return true
  }
})

'a' in obj  // false
obj.a = 10  // "property set: a = 10"
'a' in obj  // true
obj.a       // 10

Reflect(反射)

在 MDN 上的 Reflect 對象是這樣定義的

Reflect 是一個內(nèi)建的對象,用來提供方法去攔截 JavaScript的操作。Reflect 不是一個函數(shù)對象,所以它是不可構(gòu)造的,也就是說你不能通過 new操作符去新建一個 Reflect對象或者將 Reflect對象作為一個函數(shù)去調(diào)用。Reflect的所有屬性和方法都是靜態(tài)的(就像Math對象)

常用方法

Reflect對象掛載了很多靜態(tài)方法,所謂靜態(tài)方法,就是和 Math.round() 這樣,不需要 new 就可以直接使用的方法。
比較常用的兩個方法就是 get()set() 方法:

方法 描述
Reflect.get(target, key, ?receiver) 和 target[key] 類似,從對象中讀取屬性值
Reflect.set(target, key, value, ? receiver) 和 target[key] = value 類似,給對象的屬性設(shè)置一個新值

Reflect.get()

Reflect.get方法允許你從一個對象中取屬性值,返回值是這個屬性值

Reflect.set()

Reflect.set 方法允許你在對象上設(shè)置屬性,返回值是 Boolean 值,代表是否設(shè)置成功

  • target: 目標對象
  • key: 屬性名
  • value: 新屬性值
  • receiver: 后面會重點介紹
Reflect.get(target, key[, receiver])
// 等同于
target[key]

Reflect.set(target, key, value[, receiver])
// 等同于
target[key] = value

舉個栗子

let product = {price: 5, quantity: 2}

// 以下三種方法是等效的
product.quantity
product['quantity']
Reflect.get(product, 'quantity')

// 以下三種方法是等效的
product.quantity = 3
product['quantity'] = 3
Reflect.set(product, 'quantity', 3)

關(guān)于receiver參數(shù)

在 Proxy 和 Reflect 對象中 get/set() 方法的最后一個參數(shù)都是 receiver,它到底是個什么玩意?

receiver 是接受者的意思,譯為接收器

  1. 在 Proxy trap 的場景下(例如 handler.get() / handler.set()), receiver 永遠指向 Proxy 本身或者繼承它的對象,比方說下面這個例子
let origin = { a: 1 }

let p = new Proxy(origin, {
  get(target, key, receiver) {
    return receiver
  },
})

let child = Object.create(p)

p.getReceiver // Proxy {a: 1}
p.getReceiver === p // true
child.getReceiver // {}
child.getReceiver === child // true
  1. 在 Reflect.get / Reflect.set() 的場景下,receiver 可以改變計算屬性中 this 的指向
let target = {
  firstName: 'li',
  lastName: 'baicheng',
  get a() {
    return `${this.firstName}-${this.age}`
  },
  set b(val) {
    console.log('>>>this', this)
    this.firstName = val
  },
}

Reflect.get(target, 'a') // li-undefined
Reflect.get(target, 'a', { age: 24 }) // undefined-24

Reflect.set(target, 'b', 'huawei', { age: 24 })
// >>>this {age: 24}
// true

搭配Proxy

在 Proxy 里使用 Reflect,我們會有一個附加參數(shù),稱為 receiver (接收器),它將傳遞到我們的 Reflect調(diào)用中。它保證了當我們的對象有繼承自其它對象的值或函數(shù)時, this 指針能正確的指向?qū)ο螅@將避免一些我們在 vue2 中有的響應(yīng)式警告

let origin = { a: 1 }

let p = new Proxy(origin, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    return Reflect.set(target, key, value, receiver)
  },
})

Reflect對象經(jīng)常和Proxy代理一起使用,原因有三點:

  1. Reflect提供的所有靜態(tài)方法和Proxy第2個handle對象中的方法參數(shù)是一模一樣的,例如Reflect的 get/set() 方法需要的參數(shù)就是Proxy get/set() 方法的參數(shù)

  2. Proxy get/set() 方法需要的返回值正是Reflect的 get/set() 方法的返回值,可以天然配合使用,比直接對象賦值/獲取值要更方便和準確

  3. receiver 參數(shù)具有不可替代性?。?!

    在下面示例中,我們在頁面中訪問了 alias 對應(yīng)的值,稍后 name 變化了,要重新渲染么?

    target[key] 方式訪問 proxy.alias 時,獲取到 this.name,此時 this 指向 target,無法監(jiān)控到 name ,不能重新渲染

    Reflect 方式訪問 proxy.alias 時,獲取到 this.name,此時 this 指向 proxy,可監(jiān)控到 name ,可以重新渲染

const target = {
  name: '柏成',
  get alias() {
    console.log('this === target', this === target)
    console.log('this === proxy', this === proxy)
    return this.name
  },
}
const proxy = new Proxy(target, {
  get(target, key, receiver) {
    console.log('key:', key)
    return target[key]
    // return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    return Reflect.set(target, key, value, receiver)
  },
})
proxy.alias

使用 target[key] 打印結(jié)果:

【Vue3響應(yīng)式原理#02】Proxy and Reflect

使用 Reflect 打印結(jié)果:

【Vue3響應(yīng)式原理#02】Proxy and Reflect

如何用(How)

讓我們創(chuàng)建一個稱為 reactive 的函數(shù),如果你使用過Composition API,你會感覺很熟悉。然后再封裝一下我們的 handler 方法,讓它長得更像 Vue3 的源代碼,最后我們將創(chuàng)建一個新的 Proxy對象

代碼如下

function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      let result = Reflect.get(target, key, receiver)
      // 保存effect
      track(target, key)
      return result
    },
    set(target, key, value, receiver) {
      let oldValue = target[key]
      let result = Reflect.set(target, key, value, receiver)
      if (oldValue !== value) {
        // 運行effect
        trigger(target, key)
      }
      return result
    },
  }
  
  return new Proxy(target, handler)
}

let product = reactive({ price: 5, quantity: 2 })

現(xiàn)在我們已經(jīng)不再需要手動調(diào)用 track()trigger()

【Vue3響應(yīng)式原理#02】Proxy and Reflect

讓我們分析一下上圖內(nèi)容

  1. 現(xiàn)在我們的響應(yīng)式函數(shù)返回一個 product 對象的代理,我們還有變量 total ,方法 effect()。

  2. 當我們運行 effect() ,試圖獲取 product.price 時,它將運行track(product, 'price')

  3. targetMap 里,它將為 product 對象創(chuàng)建一個新的映射,它的值是一個新的 depsMap ,這將映射 price 屬性得到一個新的 dep ,這個 dep就是一個 effects集(Set),把我們 total 的 effect加到這個集(Set)中

  4. 我們還會訪問 product.quantity ,這是另一個get請求。我們將會調(diào)用track(product, 'quantity')。這將訪問我們 product 對象的 depsMap,并添加一個 quantity 屬性到一個新的 dep 對象的映射

  5. 然后我們把 total 打印到控制臺是 10

  6. 然后我們運行product.quantity = 3,它會調(diào)用 trigger(product, 'quantity'),然后運行被存儲的所有 effect

  7. 調(diào)用 effect() , 就會訪問到 product.price ,觸發(fā)track(product, 'price');訪問到 product.quantity ,則觸發(fā)track(product, 'quantity')

ActiveEffect

我們每訪問一次Proxy實例屬性,都將會調(diào)用一次 track 函數(shù)。然后它會去歷遍 targetMap、depsMap,以確保當前 effect 會被記錄下來,這不合理,不需要多次添加 effect

這不是我們想要的,我們只應(yīng)該在 effect() 里調(diào)用 track 函數(shù)

console.log('Update quantity to = '+ product.quantity)
console.log('Update price to = '+ product.price)

為此,我們引入了 activeEffect 變量,它代表現(xiàn)在正在運行中的 effect, Vue3 也是這樣做的,代碼如下

let activeEffect = null
...
// 負責收集依賴
function effect(eff){ 
  activeEffect = eff 
  activeEffect() // 運行
  activeEffect = null //復位
}

// 我們用這個函數(shù)來計算total
effect(() => {
  total = product.price * product.quantity
})

現(xiàn)在我們需要新的 track() 函數(shù),讓它去使用這個新的 activeEffect 變量

function track(target, key){
  // 關(guān)鍵?。?!
  // 我們只想在我們有activeEffect時運行這段代碼
  if(!activeEffect) return

  let depsMap = targetMap.get(target) 
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map())) 
  }
  let dep = depsMap.get(key) 
  if (!dep) {
    depsMap.set(key, (dep = new Set())) 
  }
  //當我們添加依賴(dep)時我們要添加activeEffect
  dep.add(activeEffect)
}

這樣就保證了,如果不是通過 effect() 函數(shù)去訪問Proxy實例屬性,則這時的 activeEffect 為 null ,進入 track() 函數(shù)立即就被 return 掉了

完整代碼

這樣一來,我們就實現(xiàn)了 Vue3 基本的響應(yīng)性了。完整代碼如下

// The active effect running
let activeEffect = null

// For storing the dependencies for each reactive object
const targetMap = new WeakMap()

// 負責收集依賴
function effect(eff) {
  activeEffect = eff
  activeEffect() // 運行
  activeEffect = null //復位
}

// Save this code
function track(target, key) {
  // 關(guān)鍵?。?!
  // 我們只想在我們有activeEffect時運行這段代碼
  if (!activeEffect) return

  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  console.log('>>>track', target, key)
  //當我們添加依賴(dep)時我們要添加activeEffect
  dep.add(activeEffect)
}

// Run all the code I've saved
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    return
  }
  let dep = depsMap.get(key)
  if (dep) {
    console.log('>>>trigger', target, key)
    dep.forEach(eff => {
      eff()
    })
  }
}

// 響應(yīng)式代理
function reactive(target) {
  // 如果不是對象或數(shù)組
  // 拋出警告,并返回目標對象
  if (!target || typeof target !== 'object') {
    console.warn(`value cannot be made reactive: ${String(target)}`)
    return target
  }
  const handler = {
    get(target, key, receiver) {
      let result = Reflect.get(target, key, receiver)
      track(target, key)

      // 遞歸創(chuàng)建并返回
      if (typeof target[key] === 'object' && target[key] !== null) {
        return reactive(target[key])
      }
      return result
    },
    set(target, key, value, receiver) {
      let oldValue = target[key]
      let result = Reflect.set(target, key, value, receiver)
      if (oldValue !== value) {
        trigger(target, key)
      }
      return result
    },
  }
  return new Proxy(target, handler)
}

let product = reactive({ price: 5, quantity: 2, rate: { value: 0.9 } })
let total = 0

effect(() => {
  total = product.price * product.quantity * product.rate.value
})

控制臺打印結(jié)果如下

【Vue3響應(yīng)式原理#02】Proxy and Reflect

參考資料

  • ES6的代理模式 | Proxy | Vue3

  • Proxy是代理,Reflect是干嘛用的? ? 張鑫旭-鑫空間-鑫生活

  • Proxy和Reflect中的receiver到底是個什么東西 - 掘金

  • Proxy 和 Reflect 中的 receiver 到底是什么? · Issue #52 · sl1673495/notes文章來源地址http://www.zghlxwxcb.cn/news/detail-745221.html

到了這里,關(guān)于【Vue3響應(yīng)式原理#02】Proxy and Reflect的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • vue3響應(yīng)式原理

    vue3響應(yīng)式原理

    Vue 3 中的響應(yīng)式原理是通過使用 ES6 的 Proxy 對象來實現(xiàn)的。在 Vue 3 中,每個組件都有一個響應(yīng)式代理對象,當組件中的數(shù)據(jù)發(fā)生變化時,代理對象會立即響應(yīng)并更新視圖。 具體來說,當一個組件被創(chuàng)建時,Vue 會為組件的 data 對象創(chuàng)建一個響應(yīng)式代理對象。這個代理對象可以

    2024年02月15日
    瀏覽(36)
  • Vue3 數(shù)據(jù)響應(yīng)式原理

    核心: 通過Proxy(代理): 攔截對data任意屬性的任意(13種)操作, 包括屬性值的讀寫, 屬性的添加, 屬性的刪除等… 通過 Reflect(反射): 動態(tài)對被代理對象的相應(yīng)屬性進行特定的操作 Vue3的響應(yīng)式比Vue2好在哪里? 效率更高了,Vue2中假設(shè)監(jiān)聽某個對象,該對象中有一萬個屬性,他要循

    2024年02月11日
    瀏覽(20)
  • Vue3響應(yīng)式原理 私

    Vue3響應(yīng)式原理 私

    響應(yīng)式的本質(zhì):當數(shù)據(jù)變化后會自動執(zhí)行某個函數(shù)映射到組件,自動觸發(fā)組件的重新渲染。 響應(yīng)式的實現(xiàn)方式就是劫持數(shù)據(jù),Vue3的reactive就是通過Proxy劫持數(shù)據(jù),由于劫持的是整個對象,所以可以檢測到任何對象的修改,彌補了2.0的不足。 名詞解釋: **副作用函數(shù):**函數(shù)的

    2024年02月10日
    瀏覽(21)
  • 手寫Vue3響應(yīng)式數(shù)據(jù)原理

    手寫Vue3響應(yīng)式數(shù)據(jù)原理

    我們想要對一個對象數(shù)據(jù)進行處理,從而實現(xiàn)更改dom。但如何更改對一個對象數(shù)據(jù)進行更改呢? vue2 的雙向數(shù)據(jù)綁定是利?ES5 的?個 API ,Object.defineProperty()對數(shù)據(jù)進?劫持 結(jié)合 發(fā)布訂閱模式的?式來實現(xiàn)的。 vue3 中使?了 ES6 的 ProxyAPI 對數(shù)據(jù)代理,通過 reactive() 函數(shù)給每?

    2024年02月11日
    瀏覽(19)
  • Vue3.0中的響應(yīng)式原理

    Vue3.0中的響應(yīng)式原理

    實現(xiàn)原理: - 對象類型:通過 ``Object.defineProperty()``對屬性的讀取、修改進行攔截(數(shù)據(jù)劫持)。 - 數(shù)組類型:通過重寫更新數(shù)組的一系列方法來實現(xiàn)攔截。(對數(shù)組的變更方法進行了包裹)。 存在問題: - 新增屬性、刪除屬性, 界面不會更新。 - 直接通過下標修改數(shù)組, 界面

    2023年04月17日
    瀏覽(25)
  • 【Vue3響應(yīng)式原理#01】Reactivity

    【Vue3響應(yīng)式原理#01】Reactivity

    專欄分享:vue2源碼專欄,vue3源碼專欄,vue router源碼專欄,玩具項目專欄,硬核??推薦?? 歡迎各位ITer關(guān)注點贊收藏?????? 以下是柏成根據(jù)Vue3官方課程整理的響應(yīng)式書面文檔 - 第一節(jié),課程鏈接在此:Vue 3 Reactivity - Vue 3 Reactivity | Vue Mastery,本文檔可作為課程的輔助材料,

    2024年02月08日
    瀏覽(17)
  • 【源碼系列#05】Vue3響應(yīng)式原理(Ref)

    ref: 接受一個參數(shù)值并返回一個響應(yīng)式且可改變的 ref 對象。ref 對象擁有一個指向內(nèi)部值的單一屬性 .value 可以將 ref 看成 reactive 的一個變形版本,這是由于 reactive 內(nèi)部采用 Proxy 來實現(xiàn),而 Proxy 只接受對象作為入?yún)?,這才有了 ref 來解決值類型的數(shù)據(jù)響應(yīng), 如果傳入 ref 的

    2024年02月03日
    瀏覽(61)
  • Vue2和Vue3響應(yīng)式原理實現(xiàn)的核心

    Vue.js 是一個開源的漸進式 JavaScript 前端框架,主要用于構(gòu)建用戶界面和單頁應(yīng)用程序(SPA)。Vue.js 可以輕松地與其他庫或現(xiàn)有項目集成使用,并被認為是開發(fā)響應(yīng)式數(shù)據(jù)驅(qū)動的現(xiàn)代 Web 應(yīng)用的一種有效方式。 Vue.js 的核心特點: 響應(yīng)式數(shù)據(jù)綁定:Vue.js 可以通過對數(shù)據(jù)進行雙

    2024年02月08日
    瀏覽(39)
  • 【手撕源碼】vue3響應(yīng)式原理解析(文末抽獎)

    【手撕源碼】vue3響應(yīng)式原理解析(文末抽獎)

    ?? 個人主頁: 不叫貓先生 ???♂? 作者簡介:2022年度博客之星前端領(lǐng)域TOP 2,前端領(lǐng)域優(yōu)質(zhì)作者、阿里云專家博主,專注于前端各領(lǐng)域技術(shù),共同學習共同進步,一起加油呀! ??優(yōu)質(zhì)專欄:vue3從入門到精通、TypeScript從入門到實踐 ?? 資料領(lǐng)取:前端進階資料以及文中源

    2024年02月03日
    瀏覽(21)
  • vue2、vue3、react響應(yīng)式原理、組件聲明周期闡述與對比

    響應(yīng)式原理: Vue.js 的響應(yīng)式原理是通過使用 Object.defineProperty 函數(shù)來實現(xiàn)的。在 Vue.js 中,當一個對象被傳入 Vue 實例的 data 選項中時,Vue.js 會將這個對象的屬性轉(zhuǎn)換為 getter 和 setter,以便在屬性被訪問或修改時能夠觸發(fā)相應(yīng)的更新。 具體來說,Vue.js 會在實例化過程中遞歸

    2024年02月06日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包