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

一篇搞懂Vue2、Vue3響應(yīng)式源碼的原理

 更新時(shí)間:2023年01月08日 11:41:54   作者:既白biu  
這篇文章主要介紹了Vue2、Vue3響應(yīng)式源碼的原理,內(nèi)容很詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,有需要的朋友可以借鑒參考下,希望能夠有所幫助

前言

我們?cè)诰帉慥ue2,Vue3代碼的時(shí)候,經(jīng)常會(huì)在data中定義某些數(shù)據(jù),然后在template用到的時(shí)候,可能會(huì)在多處用到這些數(shù)據(jù),通過(guò)對(duì)這些數(shù)據(jù)的操作,可以達(dá)到改變視圖的作用,即所謂數(shù)據(jù)驅(qū)動(dòng)視圖。

我們可以通過(guò)Mustache 語(yǔ)法,讓data可以在頁(yè)面上顯示,隨著data的變化,視圖中也會(huì)隨之改變。

那么,這種響應(yīng)式操作在Vue2、Vue3中是怎么實(shí)現(xiàn)的呢?

Vue2響應(yīng)式操作

響應(yīng)式函數(shù)的封裝

在進(jìn)行響應(yīng)式操作前,我們需要簡(jiǎn)單大致封裝一個(gè)響應(yīng)式函數(shù),參數(shù)接收的是函數(shù),凡是傳入到響應(yīng)式函數(shù)的函數(shù),就是需要響應(yīng)式的,其他默認(rèn)定義的函數(shù)是不需要響應(yīng)式的。

我們需要用一個(gè)數(shù)組將他們收集起來(lái),(現(xiàn)在暫時(shí)使用函數(shù),最好的辦法是放入Set中,下文會(huì)講),代碼如下:

// 封裝一個(gè)響應(yīng)式的函數(shù)
let reactiveFns = []
function watchFn(fn) {
  reactiveFns.push(fn)
}

等到我們需要執(zhí)行這些函數(shù)的時(shí)候(什么時(shí)候需要執(zhí)行是后話,先簡(jiǎn)單提一下),可以遍歷這個(gè)數(shù)組然后執(zhí)行:

reactiveFns.forEach(fn => {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->
fn()
})

Depend類的封裝

我們需要封裝一個(gè)Depend類,這個(gè)類的作用是:這個(gè)類用于管理某一個(gè)對(duì)象的某一個(gè)屬性的所有響應(yīng)式函數(shù)。一個(gè)對(duì)象里面可能會(huì)有多個(gè)屬性并且有他們對(duì)應(yīng)的值,我們可能用到了這個(gè)對(duì)象里面多個(gè)屬性,所以我們要給這里的每個(gè)用到的屬性建立一個(gè)屬于自己的類,用來(lái)管理對(duì)這個(gè)屬性有依賴的所有函數(shù)。

所以我們得想辦法拿到剛才在響應(yīng)式函數(shù)里面?zhèn)鬟M(jìn)去的函數(shù),這里我們可以用activeReactiveFn暫時(shí)保存剛才傳進(jìn)去的函數(shù)。

所以我們對(duì)響應(yīng)式函數(shù)的封裝進(jìn)行重構(gòu)一下,如下:

// 保存當(dāng)前需要收集的響應(yīng)式函數(shù)
let activeReactiveFn = null

// 封裝一個(gè)響應(yīng)式的函數(shù)
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

因?yàn)槟硞€(gè)屬性可能會(huì)用多個(gè)函數(shù)進(jìn)行依賴,所有在這個(gè)類的內(nèi)部我們會(huì)定義一個(gè)Set, reactiveFns = new Set(),定義成Set而不是數(shù)組是因?yàn)镾et數(shù)據(jù)結(jié)構(gòu)沒有重復(fù)的數(shù)據(jù),從而防止了重復(fù)的操作。

這里定義了一個(gè)depend方法可以將activeReactiveFn在有值的情況下,放入reactiveFns中,notify函數(shù)就是將這些收集了的函數(shù)進(jìn)行執(zhí)行。

class Depend {
  constructor() {
    this.reactiveFns = new Set()
  }
  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}

監(jiān)聽對(duì)象的變化

在Vue2中使用的監(jiān)聽對(duì)象的變化使用的方法是:使用Object.defineProperty。

我們可以封裝一個(gè)reactive函數(shù),參數(shù)傳入一個(gè)對(duì)象,函數(shù)內(nèi)部對(duì)這個(gè)對(duì)象進(jìn)行監(jiān)聽,遍歷這個(gè)對(duì)象,獲取所有的屬性和屬性值,對(duì)每個(gè)屬性使用Object.defineProperty,在Object.defineProperty第三個(gè)參數(shù)中,get和set方法中,在set方法中,修改值為新的值之后,之前提到,每個(gè)屬性都要有屬于自己的Depend對(duì)象,那么如何獲取這個(gè)對(duì)象呢?

那這里還有個(gè)問(wèn)題,有不同的對(duì)象,對(duì)象里面又有多個(gè)屬性,那么這該如何解決呢?

可以定義一個(gè)WeakMap將各個(gè)對(duì)象保存成Map形式,然后在每個(gè)單一對(duì)象里面,我們可以用Map形式保存屬性的Depend類,如圖所示:

那么如何根據(jù)對(duì)象名,屬性名獲取depend呢?可以在getDepend函數(shù)實(shí)現(xiàn),參數(shù)傳入對(duì)象名,屬性名
,代碼如下:

// 封裝一個(gè)獲取depend函數(shù)
const targetMap = new WeakMap()

function getDepend(target, key) {
  // 根據(jù)target對(duì)象獲取map的過(guò)程
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  // 根據(jù)key獲取depend對(duì)象
  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}

獲取到屬性特定的depend后,回到原來(lái)的話題,那么在set方法中,修改值為新的值之后,獲取到屬性特定的depend后,要調(diào)用depend里面的notify方法,使對(duì)這個(gè)屬性有依賴的所有函數(shù)執(zhí)行,也就是對(duì)數(shù)據(jù)進(jìn)行更新。

在get方法中,在返回屬性值之前,要先獲取到屬性特定的depend后,調(diào)用depend里面的depend方法,將對(duì)此屬性依賴的函數(shù)保存下來(lái)。

代碼如下:

function reactive(obj) {
  Object.keys(obj).forEach(key => {
    let value = obj[key]
    Object.defineProperty(obj, key, {
      get: function() {
        const depend = getDepend(obj, key)
        depend.depend()
        return value
      },
      set: function(newValue) {
        value = newValue
        const depend = getDepend(obj, key)
        depend.notify()
      }
    })
  })
  return obj
}

至此,Vue2的響應(yīng)式操作就已經(jīng)實(shí)現(xiàn)了

所有代碼以及測(cè)試代碼如下:

// 保存當(dāng)前需要收集的響應(yīng)式函數(shù)
let activeReactiveFn = null
class Depend {
    constructor() {
        this.reactiveFns = new Set()
    }
    depend() {
        if (activeReactiveFn) {
            this.reactiveFns.add(activeReactiveFn)
        }
    }

    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}

// 封裝一個(gè)響應(yīng)式的函數(shù)
function watchFn(fn) {
    activeReactiveFn = fn
    fn()
    activeReactiveFn = null
}

// 封裝一個(gè)獲取depend函數(shù)
const targetMap = new WeakMap()

function getDepend(target, key) {
    // 根據(jù)target對(duì)象獲取map的過(guò)程
    let map = targetMap.get(target)
    if (!map) {
        map = new Map()
        targetMap.set(target, map)
    }

    // 根據(jù)key獲取depend對(duì)象
    let depend = map.get(key)
    if (!depend) {
        depend = new Depend()
        map.set(key, depend)
    }
    return depend
}

function reactive(obj) {
    Object.keys(obj).forEach(key => {
        let value = obj[key]
        Object.defineProperty(obj, key, {
            get: function() {
                const depend = getDepend(obj, key)
                depend.depend()
                return value
            },
            set: function(newValue) {
                value = newValue
                const depend = getDepend(obj, key)
                depend.notify()
            }
        })
    })
    return obj
}

// 監(jiān)聽對(duì)象的屬性變量: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive({
    name: "cy", // depend對(duì)象
    age: 18 // depend對(duì)象
})

const infoProxy = reactive({
    address: "安徽省",
    height: 1.88
})

watchFn(() => {
    console.log(infoProxy.address)
})

infoProxy.address = "北京市"

const foo = reactive({
    name: "foo"
})

watchFn(() => {
    console.log(foo.name)
})

foo.name = "aaa"
foo.name = "bbb"

// 安徽省
// 北京市
// foo
// aaa
// bbb

Vue3響應(yīng)式操作

Proxy、Reflect

Proxy:
在Vue2中,使用Object.defineProperty來(lái)監(jiān)聽對(duì)象的變化,但是這樣做有什么缺點(diǎn)呢?

首先,Object.defineProperty設(shè)計(jì)的初衷,不是為了去監(jiān)聽截止一個(gè)對(duì)象中所有的屬性的。
我們?cè)诙x某些屬性的時(shí)候,初衷其實(shí)是定義普通的屬性,但是后面我們強(qiáng)行將它變成了數(shù)據(jù)屬性描述符。
其次,如果我們想監(jiān)聽更加豐富的操作,比如新增屬性、刪除屬性,那么Object.defineProperty是無(wú)能為力的。
所以我們要知道,存儲(chǔ)數(shù)據(jù)描述符設(shè)計(jì)的初衷并不是為了去監(jiān)聽一個(gè)完整的對(duì)象

在ES6中,新增了一個(gè)Proxy類,這個(gè)類從名字就可以看出來(lái),是用于幫助我們創(chuàng)建一個(gè)代理的:

也就是說(shuō),如果我們希望監(jiān)聽一個(gè)對(duì)象的相關(guān)操作,那么我們可以先創(chuàng)建一個(gè)代理對(duì)象(Proxy對(duì)象);

之后對(duì)該對(duì)象的所有操作,都通過(guò)代理對(duì)象來(lái)完成,代理對(duì)象可以監(jiān)聽我們想要對(duì)原對(duì)象進(jìn)行哪些操作;

如果我們想要偵聽某些具體的操作,那么就可以在handler中添加對(duì)應(yīng)的捕捉器(Trap):
set函數(shù)有四個(gè)參數(shù):

target:目標(biāo)對(duì)象(偵聽的對(duì)象);
property:將被設(shè)置的屬性key;
value:新屬性值;
receiver:調(diào)用的代理對(duì)象;

get函數(shù)有三個(gè)參數(shù):

target:目標(biāo)對(duì)象(偵聽的對(duì)象);
property:被獲取的屬性key;
receiver:調(diào)用的代理對(duì)象

實(shí)例代碼如下;

const obj = {
  name: "cy",
  age: 18
}

const objProxy = new Proxy(obj, {
  // 獲取值時(shí)的捕獲器
  get: function(target, key) {
    console.log(`監(jiān)聽到對(duì)象的${key}屬性被訪問(wèn)了`, target)
    return target[key]
  },

  // 設(shè)置值時(shí)的捕獲器
  set: function(target, key, newValue) {
    console.log(`監(jiān)聽到對(duì)象的${key}屬性被設(shè)置值`, target)
    target[key] = newValue
  }
})

console.log(objProxy.name)
console.log(objProxy.age)

objProxy.name = "kobe"
objProxy.age = 30

console.log(obj.name)
console.log(obj.age)
// 監(jiān)聽到對(duì)象的name屬性被訪問(wèn)了 { name: 'cy', age: 18 }
// cy
// 監(jiān)聽到對(duì)象的age屬性被訪問(wèn)了 { name: 'cy', age: 18 }
// 18
// 監(jiān)聽到對(duì)象的name屬性被設(shè)置值 { name: 'cy', age: 18 }
// 監(jiān)聽到對(duì)象的age屬性被設(shè)置值 { name: 'kobe', age: 18 }
// kobe
// 30

Reflect:

Reflect也是ES6新增的一個(gè)API,它是一個(gè)對(duì)象,字面的意思是反射。
 

那么這個(gè)Reflect有什么用呢?

它主要提供了很多操作JavaScript對(duì)象的方法,有點(diǎn)像Object中操作對(duì)象的方法;
比如Reflect.getPrototypeOf(target)類似于 Object.getPrototypeOf();
比如Reflect.defineProperty(target, propertyKey, attributes)類似于Object.defineProperty() ;

如果我們有Object可以做這些操作,那么為什么還需要有Reflect這樣的新增對(duì)象呢?

這是因?yàn)樵谠缙诘腅CMA規(guī)范中沒有考慮到這種對(duì) 對(duì)象本身 的操作如何設(shè)計(jì)會(huì)更加規(guī)范,所以將這些API放到了Object上面;
但是Object作為一個(gè)構(gòu)造函數(shù),這些操作實(shí)際上放到它身上并不合適;
另外還包含一些類似于 in、delete操作符,讓JS看起來(lái)是會(huì)有一些奇怪的;
所以在ES6中新增了Reflect,讓我們這些操作都集中到了Reflect對(duì)象上;

Reflect中常見的方法:

那么我們可以將之前Proxy案例中對(duì)原對(duì)象的操作,都修改為Reflect來(lái)操作;

我們發(fā)現(xiàn)在使用getter、setter的時(shí)候有一個(gè)receiver的參數(shù),它的作用是什么呢?

如果我們的源對(duì)象(obj)有setter、getter的訪問(wèn)器屬性,那么可以通過(guò)receiver來(lái)改變里面的this

Vue3響應(yīng)式

Vue3響應(yīng)式使用的是Proxy,我們需要在Vue2的reactive函數(shù)里面進(jìn)行一些改變:

function reactive(obj) {
  return new Proxy(obj, {
    get: function(target, key, receiver) {
      // 根據(jù)target.key獲取對(duì)應(yīng)的depend
      const depend = getDepend(target, key)
      // 給depend對(duì)象中添加響應(yīng)函數(shù)
      depend.depend()
  
      return Reflect.get(target, key, receiver)
    },
    set: function(target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver)
      // depend.notify()
      const depend = getDepend(target, key)
      depend.notify()
    }
  })
}

其他方面的代碼同Vue2基本沒啥變化,Vue3的響應(yīng)式操作就已經(jīng)實(shí)現(xiàn)了

所有代碼以及測(cè)試代碼如下:

// 保存當(dāng)前需要收集的響應(yīng)式函數(shù)
let activeReactiveFn = null
class Depend {
    constructor() {
        this.reactiveFns = new Set()
    }
    depend() {
        if (activeReactiveFn) {
            this.reactiveFns.add(activeReactiveFn)
        }
    }

    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}

// 封裝一個(gè)響應(yīng)式的函數(shù)
function watchFn(fn) {
    activeReactiveFn = fn
    fn()
    activeReactiveFn = null
}
// 封裝一個(gè)獲取depend函數(shù)
const targetMap = new WeakMap()

function getDepend(target, key) {
    // 根據(jù)target對(duì)象獲取map的過(guò)程
    let map = targetMap.get(target)
    if (!map) {
        map = new Map()
        targetMap.set(target, map)
    }

    // 根據(jù)key獲取depend對(duì)象
    let depend = map.get(key)
    if (!depend) {
        depend = new Depend()
        map.set(key, depend)
    }
    return depend
}

function reactive(obj) {
    return new Proxy(obj, {
        get: function(target, key, receiver) {
            // 根據(jù)target.key獲取對(duì)應(yīng)的depend
            const depend = getDepend(target, key)
                // 給depend對(duì)象中添加響應(yīng)函數(shù)
            depend.depend()

            return Reflect.get(target, key, receiver)
        },
        set: function(target, key, newValue, receiver) {
            Reflect.set(target, key, newValue, receiver)
                // depend.notify()
            const depend = getDepend(target, key)
            depend.notify()
        }
    })
}

// 監(jiān)聽對(duì)象的屬性變量: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive({
    name: "cy", // depend對(duì)象
    age: 18 // depend對(duì)象
})

const infoProxy = reactive({
    address: "安徽省",
    height: 1.88
})

watchFn(() => {
    console.log(infoProxy.address)
})

infoProxy.address = "北京市"

const foo = reactive({
    name: "foo"
})

watchFn(() => {
    console.log(foo.name)
})

foo.name = "bar"
// 安徽省
// 北京市
// foo
// bar

以上就是一篇搞懂Vue2、Vue3響應(yīng)式源碼的原理的詳細(xì)內(nèi)容,更多關(guān)于Vue2、Vue3響應(yīng)式源碼的原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論