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

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

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

前言

我們在編寫Vue2,Vue3代碼的時候,經(jīng)常會在data中定義某些數(shù)據(jù),然后在template用到的時候,可能會在多處用到這些數(shù)據(jù),通過對這些數(shù)據(jù)的操作,可以達到改變視圖的作用,即所謂數(shù)據(jù)驅(qū)動視圖。

我們可以通過Mustache 語法,讓data可以在頁面上顯示,隨著data的變化,視圖中也會隨之改變。

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

Vue2響應(yīng)式操作

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

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

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

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

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

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

Depend類的封裝

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

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

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

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

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

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

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

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

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

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

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

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

那這里還有個問題,有不同的對象,對象里面又有多個屬性,那么這該如何解決呢?

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

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

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

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

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

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

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

代碼如下:

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)實現(xiàn)了

所有代碼以及測試代碼如下:

// 保存當(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()
        })
    }
}

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

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

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

    // 根據(jù)key獲取depend對象
    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)聽對象的屬性變量: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive({
    name: "cy", // depend對象
    age: 18 // depend對象
})

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來監(jiān)聽對象的變化,但是這樣做有什么缺點呢?

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

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

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

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

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

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

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

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

實例代碼如下;

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

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

  // 設(shè)置值時的捕獲器
  set: function(target, key, newValue) {
    console.log(`監(jiān)聽到對象的${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)聽到對象的name屬性被訪問了 { name: 'cy', age: 18 }
// cy
// 監(jiān)聽到對象的age屬性被訪問了 { name: 'cy', age: 18 }
// 18
// 監(jiān)聽到對象的name屬性被設(shè)置值 { name: 'cy', age: 18 }
// 監(jiān)聽到對象的age屬性被設(shè)置值 { name: 'kobe', age: 18 }
// kobe
// 30

Reflect:

Reflect也是ES6新增的一個API,它是一個對象,字面的意思是反射。
 

那么這個Reflect有什么用呢?

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

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

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

Reflect中常見的方法:

那么我們可以將之前Proxy案例中對原對象的操作,都修改為Reflect來操作;

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

如果我們的源對象(obj)有setter、getter的訪問器屬性,那么可以通過receiver來改變里面的this

Vue3響應(yīng)式

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

function reactive(obj) {
  return new Proxy(obj, {
    get: function(target, key, receiver) {
      // 根據(jù)target.key獲取對應(yīng)的depend
      const depend = getDepend(target, key)
      // 給depend對象中添加響應(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)實現(xiàn)了

所有代碼以及測試代碼如下:

// 保存當(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()
        })
    }
}

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

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

    // 根據(jù)key獲取depend對象
    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獲取對應(yīng)的depend
            const depend = getDepend(target, key)
                // 給depend對象中添加響應(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)聽對象的屬性變量: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive({
    name: "cy", // depend對象
    age: 18 // depend對象
})

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)式源碼的原理的詳細內(nèi)容,更多關(guān)于Vue2、Vue3響應(yīng)式源碼的原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論