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

詳解如何編寫一個Vue3響應(yīng)式系統(tǒng)

 更新時間:2023年07月25日 15:24:51   作者:前端胖頭魚  
這篇文章主要為大家學(xué)習(xí)介紹了如何編寫一個Vue3響應(yīng)式系統(tǒng),文中的示例代碼講解詳細,具有一定的學(xué)習(xí)價值,感興趣的小伙伴可以了解一下

前言

都說今年是最慘工作年,大廠裁員,小廠跟風(fēng),簡歷投了幾百封回信的寥寥無幾,金三銀四怕是成了銅三鐵四,冷冷清清,凄凄慘慘。

但是今天的主角,小帥同學(xué)卻在逆風(fēng)環(huán)境中給了面試官當(dāng)頭一喝,秀了他一身,優(yōu)秀如他,到底經(jīng)歷了一場怎樣的面試?

文中的例子和代碼都可以點擊這里查看

1.題目亮相

面試官: 我看你簡歷寫的精通Vue3,并研究過其源碼? 小伙子很狂?。∧窃劬同F(xiàn)場秀一段如何?

說罷,面試官現(xiàn)場給了一道題...

<div id="app"></div>
<script>
  const $app = document.querySelector('#app')
  let state = {
    text: 'hello fatfish'
  }
  function effect() {
    $app.innerText = state.text
  }
  effect()
  setTimeout(() => {
    // 1秒后希望app的內(nèi)容變成hello Vue3
    state.text = 'hello Vue3'
  }, 1000)
</script>

小帥竊喜: 這個簡單,只要攔截state對象,在對text進行取值時,收集effect函數(shù)依賴,然后text設(shè)置值時,把收集的effect函數(shù)執(zhí)行一波就可以。

面試官: 口嗨我也會,別逼逼了,趕緊寫起來...

2 版本1:跑起來了,卻不通用,卒

2.1 源碼實現(xiàn)

小帥很快就寫出了第一版,核心只有兩步:

  • 第一步:收集依賴(effect函數(shù)),在讀取key時,將effect函數(shù)存儲起來
  • 第二步:設(shè)置值時,將依賴(effect函數(shù))執(zhí)行
const $app = document.querySelector('#app')
const bucket = new Set()
const state = new Proxy({ text: 'hello fatfish' }, {
  get (target, key) {
    const value = target[ key ]
    // 第一步:收集依賴,在讀取key時,將effect函數(shù)存儲起來
    bucket.add(effect)
    console.log(`get ${key}: ${value}`)
    return value
  },
  set (target, key, newValue) {
    console.log(`set ${key}: ${newValue}`)
    target[ key ] = newValue
    // 第二步:設(shè)置值時,將依賴執(zhí)行
    bucket.forEach((fn) => fn())
  }
})
function effect() {
  console.log('執(zhí)行了effect')
  $app.innerText = state.text
}
effect()
setTimeout(() => {
  state.text = 'hello Vue3'
}, 1000)

效果預(yù)覽

點擊預(yù)覽,噠噠噠,看起來很簡單哦,瞬間就完成啦!

2.2 面試官點評

面試官: 功能是實現(xiàn)了,但是我不太滿意,你這里收集依賴是寫死的函數(shù)名字effect,只要稍微變化一下題目,就不行了。

<div id="container">
  <div id="app1"></div>
  <div id="app2"></div>
</div>
const $app1 = document.querySelector('#app1')
const $app2 = document.querySelector('#app2')
const state = { 
  text: 'hello fatfish', 
  text2: 'hello fatfish2' 
}
// 改變app1的值
function effect1() {
  console.log('執(zhí)行了effect')
  $app1.innerText = state.text
}
// 改變app2的值
function effect2() {
  console.log('執(zhí)行了effect2')
  $app2.innerText = state.text2
}
// 1秒鐘之后兩個div的值要分別改變
setTimeout(() => {
  state.text = 'hello Vue3'
  state.text2 = 'hello Vue3-2'
}, 1000)

3 版本2: 支持多屬性響應(yīng)式修改和主動注冊

3.1 源碼實現(xiàn)

小帥心想: "大意了,我應(yīng)該把effect依賴函數(shù)通過某種機制,主動注冊到桶中,這樣無論你是匿名函數(shù)亦或者是具名函數(shù)都一視同仁"

機靈的他馬上就想到了答案。

const bucket = new Set()
let activeEffect
// 變化點:
// 通過effect函數(shù)來主動收集依賴
const effect = function (fn) {
  // 每執(zhí)行一次,將當(dāng)前fn賦值給activeEffect,這樣在fn中觸發(fā)讀取操作時,就可以被收集進bucket中了
  activeEffect = fn
  // 主動執(zhí)行一次很重要,必不可少
  fn()
}
const state = new Proxy({ text: 'hello fatfish', text2: 'hello fatfish2' }, {
  get (target, key) {
    const value = target[ key ]
    // 變化點:由版本1的effect變成了activeEffect,從而不再依賴具體的函數(shù)名字
    bucket.add(activeEffect)
    console.log(`get ${key}: ${value}`)
    return value
  },
  set (target, key, newValue) {
    console.log(`set ${key}: ${newValue}`)
    target[ key ] = newValue
    bucket.forEach((fn) => fn())
  }
})
effect(function effect1 () {
  console.log('執(zhí)行了effect1')
  $app1.innerText = state.text
})
effect(function effect2() {
  console.log('執(zhí)行了effect2')
  $app2.innerText = state.text2
})
setTimeout(() => {
  state.text = 'hello Vue3'
  state.text2 = 'hello Vue3-2'
}, 1000)

效果預(yù)覽 可以看到,此時app1和app2在1秒后都變成了對應(yīng)值,目標(biāo)達成。

3.2 面試官點評

面試官:小伙子非常不錯,思路靈活,變通很快嘛!不過你有沒有想過一個問題?

state上增加一個之前不存在的屬性,你的bucket卻會把收集的依賴執(zhí)行一次,是不是有點浪費?

能否做到effect中依賴了state的什么值,其值改變了回調(diào)才會被執(zhí)行?

4 版本3:推倒重來,再次設(shè)計"桶"數(shù)據(jù)結(jié)構(gòu)

4.1 重新設(shè)計數(shù)據(jù)結(jié)構(gòu)

小帥: 心里有點沒底了,簡歷上寫精通Vue,深入研究過Vue源碼真TM巨坑??!

面試還得繼續(xù),苦思冥想之后終于明白了第二個版本的問題所在:

沒有在effect函數(shù)與被操作的目標(biāo)字段之間建立明確的聯(lián)系:

const state = new Proxy({ text: 'hello fatfish' }, {
  get (target, key) {
    const value = target[ key ]
    // 無論`state`上啥屬性被讀取了,都會執(zhí)行`get`然后被收集進`bucket`
    bucket.add(effect)
    return value
  },
  set (target, key, newValue) {
    target[ key ] = newValue
    // 無論`state`上啥值被修改了,都會觸發(fā)`set`,進而收集的依賴被執(zhí)行。
    bucket.forEach((fn) => fn())
  }
})

1. 新的映射關(guān)系

該如何設(shè)計bucket中存儲的值呢?咱們先來看看關(guān)鍵代碼

effect(function effectFn () {
  $app.innerText = state.text
})

這段代碼中有幾個角色:

  • 被操作(讀?。┑拇韺ο?code>state
  • 被操作的(讀取)的字段名text
  • 使用effect函數(shù)注冊的effectFn函數(shù)

那么他們之間的關(guān)系可以用一顆樹來表述

state
    |__key
       |__effectFn

2. 場景1:有兩個effectFn讀取同一個對象的屬性值

effect(function effectFn1 () {
  // 讀取text
  state.text
})
effect(function effectFn2 () {
  // 讀取text
  state.text
})

那么按照上面樹形結(jié)構(gòu),現(xiàn)在表示如下: text屬性應(yīng)該要和effectFn1effectFn2建立聯(lián)系

state
    |__text
       |__effectFn1
       |__effectFn2

3. 場景2:effectFn中讀取了同一個對象的多個不同屬性

effect(function effectFn1 () {
  // 讀取text1和text2
  state.text
  state.text2
})

texttext2屬性應(yīng)該要和effectFn1建立聯(lián)系

state
    |__text
       |__effectFn1
    |__text2
       |__effectFn1 

4. 場景3:不同的effectFn中讀取了不同對象的不同屬性

 effect(function effectFn1 () {
   // 讀取text1
   state1.text1
 })
 effect(function effectFn2 () {
   // 讀取text2
   state2.text2
 })

對應(yīng)的關(guān)系表示如下:

state1
     |__text1
        |__effectFn1

state2
     |__text2
        |__effectFn2

看到這里,相信聰明的你一定明白了,當(dāng)我們改變了state2.text2的值時,只有effectFn2函數(shù)會被重新執(zhí)行,而effectFn1卻不會。當(dāng)然了新增一個以往不存在的屬性時,effectFn1和effectFn2都不會被執(zhí)行。

5. 畫一個數(shù)據(jù)結(jié)構(gòu)圖來理解一下存儲關(guān)系:

4.2 源碼實現(xiàn)

6: 新版源碼實現(xiàn)

const $app = document.querySelector('#app')
// 重新定義bucket數(shù)據(jù)類型為WeakMap
const bucket = new WeakMap()
let activeEffect
const effect = function (fn) {
  activeEffect = fn
  fn()
}
const state = new Proxy({ name: 'fatfish', age: 100 }, {
  get (target, key) {
    const value = target[ key ]
    // activeEffect無值意味著沒有執(zhí)行effect函數(shù),無法收集依賴,直接return掉
    if (!activeEffect) {
      return
    }
    // 每個target在bucket中都是一個Map類型: key => effects
    let depsMap = bucket.get(target)
    // 第一次攔截,depsMap不存在,先創(chuàng)建聯(lián)系
    if (!depsMap) {
      bucket.set(target, (depsMap = new Map()))
    }
    // 根據(jù)當(dāng)前讀取的key,嘗試讀取key的effects函數(shù)
    let deps = depsMap.get(key)
    if (!deps) {
      // deps本質(zhì)是個Set結(jié)構(gòu),即一個key可以存在多個effect函數(shù),被多個effect所依賴
      depsMap.set(key, (deps = new Set()))
    }
    // 將激活的effectFn存進桶中
    deps.add(activeEffect)
    console.log(`get ${key}: ${value}`)
    return value
  },
  set (target, key, newValue) {
    console.log(`set ${key}: ${newValue}`)
    // 設(shè)置屬性值
    target[ key ] = newValue
    // 讀取depsMap 其結(jié)構(gòu)是 key => effects
    const depsMap = bucket.get(target)
    if (!depsMap) {
      return
    }
    // 真正讀取依賴當(dāng)前屬性值key的effects
    const effects = depsMap.get(key)
    // 挨個執(zhí)行即可
    effects && effects.forEach((fn) => fn())
  }
})
effect(() => {
  console.log('執(zhí)行了effect')
  $app.innerText = `hello ${ state.name }, are you ${state.age} years old?`
})
setTimeout(() => {
  state.name = 'Vue3'
  state.age = 18
}, 1000)

效果預(yù)覽

點擊查看

可以看到我們給state新增了一個屬性text但是effect并不會被執(zhí)行,修改了name屬性為juejin之后才被執(zhí)行了,而視圖層也更新了。

4.3 面試官點評

牛,差點給我整懵逼,小弟佩服!

不過能不能再進一步,你這只能對state一個對象進行響應(yīng)式處理,能不能再封裝一下,像Vue3里面使用reactive一樣使用?

5 版本4:reactive抽象,有點Vue3的味道了

5.1 源碼實現(xiàn)

小帥心想:你一定是不想讓我面試通過,故意刁難我,不過你是面試官你最大。搞就搞。

前面我們已經(jīng)實現(xiàn)了基本的響應(yīng)式功能,不過為了通用化,我們可以進一步封裝。

const bucket = new WeakMap()
// 重新定義bucket數(shù)據(jù)類型為WeakMap
let activeEffect
const effect = function (fn) {
  activeEffect = fn
  fn()
}
// track表示追蹤的意思
function track (target, key) {
  // activeEffect無值意味著沒有執(zhí)行effect函數(shù),無法收集依賴,直接return掉
  if (!activeEffect) {
    return
  }
  // 每個target在bucket中都是一個Map類型: key => effects
  let depsMap = bucket.get(target)
  // 第一次攔截,depsMap不存在,先創(chuàng)建聯(lián)系
  if (!depsMap) {
    bucket.set(target, (depsMap = new Map()))
  }
  // 根據(jù)當(dāng)前讀取的key,嘗試讀取key的effects函數(shù)  
  let deps = depsMap.get(key)
  if (!deps) {
    // deps本質(zhì)是個Set結(jié)構(gòu),即一個key可以存在多個effect函數(shù),被多個effect所依賴
    depsMap.set(key, (deps = new Set()))
  }
  // 將激活的effectFn存進桶中
  deps.add(activeEffect)
}
// trigger執(zhí)行依賴
function trigger (target, key) {
  // 讀取depsMap 其結(jié)構(gòu)是 key => effects
  const depsMap = bucket.get(target)
  if (!depsMap) {
    return
  }
  // 真正讀取依賴當(dāng)前屬性值key的effects
  const effects = depsMap.get(key)
  // 挨個執(zhí)行即可
  effects && effects.forEach((fn) => fn())
}
// 統(tǒng)一對外暴露響應(yīng)式函數(shù)
function reactive (state) {
  return new Proxy(state, {
    get (target, key) {
      const value = target[ key ]
      track(target, key)
      console.log(`get ${key}: ${value}`)
      return value
    },
    set (target, key, newValue) {
      console.log(`set ${key}: ${newValue}`)
      // 設(shè)置屬性值
      target[ key ] = newValue
      trigger(target, key)
    }
  })
}

有了上面的封裝咱們使用起來就真的有點Vue3的感覺啦!

const $app = document.querySelector('#app')
const nameObj = reactive({
  name: 'fatfish'
})
const ageObj = reactive({
  age: 100
})
effect(() => {
  console.log('執(zhí)行了effect')
  $app.innerText = `hello ${ nameObj.name }, are you ${ageObj.age} years old?`
})
setTimeout(() => {
  nameObj.name = 'Vue3'
}, 1000)
setTimeout(() => {
  ageObj.age = 18
}, 2000)

效果預(yù)覽

點擊預(yù)覽

可以看到咱們通過reactive定義了兩個響應(yīng)式數(shù)據(jù),在1秒后修改了nameObj的值,視圖也馬上更新了,2秒后修改了ageObj的值,視圖也馬上更新了。這下夠通用了吧!完美

到此這篇關(guān)于詳解如何編寫一個Vue3響應(yīng)式系統(tǒng)的文章就介紹到這了,更多相關(guān)Vue3響應(yīng)式系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vuepress實現(xiàn)自定義首頁的樣式風(fēng)格

    vuepress實現(xiàn)自定義首頁的樣式風(fēng)格

    這篇文章主要介紹了vuepress實現(xiàn)自定義首頁的樣式風(fēng)格,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • Vue.js表單控件綁定示例盤點

    Vue.js表單控件綁定示例盤點

    這篇文章主要為大家介紹了一些Vue.js表單控件綁定示例盤點,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • 一篇文章教會你部署vue項目到docker

    一篇文章教會你部署vue項目到docker

    在前端開發(fā)中,部署項目是我們經(jīng)常發(fā)生的事情,下面這篇文章主要給大家介紹了關(guān)于部署vue項目到docker的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-04-04
  • vue之prop與$emit的用法說明

    vue之prop與$emit的用法說明

    這篇文章主要介紹了vue之prop與$emit的用法說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • Vue中使用jsencrypt進行RSA非對稱加密的操作方法

    Vue中使用jsencrypt進行RSA非對稱加密的操作方法

    這篇文章主要介紹了Vue中使用jsencrypt進行RSA非對稱加密,在這里需要注意要加密的數(shù)據(jù)必須是字符串,對Vue?RSA非對稱加密相關(guān)知識感興趣的朋友一起看看吧
    2022-04-04
  • vue?iview?導(dǎo)航高亮動態(tài)設(shè)置方式

    vue?iview?導(dǎo)航高亮動態(tài)設(shè)置方式

    這篇文章主要介紹了vue?iview?導(dǎo)航高亮動態(tài)設(shè)置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • Vue3的路由傳參方法超全匯總

    Vue3的路由傳參方法超全匯總

    vue路由傳參的使用場景一般都是應(yīng)用在父路由跳轉(zhuǎn)到子路由時,攜帶參數(shù)跳轉(zhuǎn),下面這篇文章主要給大家介紹了關(guān)于Vue3路由傳參方法的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-04-04
  • vue項目中Eslint校驗代碼報錯的解決方案

    vue項目中Eslint校驗代碼報錯的解決方案

    這篇文章主要介紹了vue項目中Eslint校驗代碼報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • vue實現(xiàn)虛擬列表功能的代碼

    vue實現(xiàn)虛擬列表功能的代碼

    這篇文章主要介紹了vue實現(xiàn)虛擬列表,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • vue使用svg文件補充-svg放大縮小操作(使用d3.js)

    vue使用svg文件補充-svg放大縮小操作(使用d3.js)

    這篇文章主要介紹了vue使用svg文件補充-svg放大縮小操作(使用d3.js),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09

最新評論