最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入淺出 Vue 系列 -- 數(shù)據(jù)劫持實(shí)現(xiàn)原理

 更新時(shí)間:2019年04月23日 08:43:52   作者:descire  
深入淺出 Vue 系列 -- 數(shù)據(jù)劫持實(shí)現(xiàn)原理

一、前言

數(shù)據(jù)雙向綁定作為 Vue 核心功能之一,其實(shí)現(xiàn)原理主要分為兩部分:

  1. 數(shù)據(jù)劫持
  2. 發(fā)布訂閱模式

本篇文章主要介紹 Vue 實(shí)現(xiàn)數(shù)據(jù)劫持的思路,下一篇?jiǎng)t會(huì)介紹發(fā)布訂閱模式的設(shè)計(jì)。

二、針對(duì) Object 類(lèi)型的劫持

對(duì)于 Object 類(lèi)型,主要劫持其屬性的讀取與設(shè)置操作。在 JavaScript 中對(duì)象的屬性主要由一個(gè)字符串類(lèi)型的“名稱(chēng)”以及一個(gè)“屬性描述符”組成,屬性描述符包括以下選項(xiàng):

  1. value: 該屬性的值;
  2. writable: 僅當(dāng)值為 true 時(shí)表示該屬性可以被改變;
  3. get: getter (讀取器);
  4. set: setter (設(shè)置器);
  5. configurable: 僅當(dāng)值為 true 時(shí),該屬性可以被刪除以及屬性描述符可以被改變;
  6. enumerable: 僅當(dāng)值為 true 時(shí),該屬性可以被枚舉。

上述 setter 和 getter 方法就是供開(kāi)發(fā)者自定義屬性的讀取與設(shè)置操作,而設(shè)置對(duì)象屬性的描述符則少不了 Object.defineProperty() 方法:

function defineReactive (obj, key) {
 let val = obj[key]
 Object.defineProperty(obj, key, {
  get () {
   console.log(' === 收集依賴(lài) === ')
   console.log(' 當(dāng)前值為:' + val)
   return val
  },
  set (newValue) {
   console.log(' === 通知變更 === ')
   console.log(' 當(dāng)前值為:' + newValue)
   val = newValue
  }
 })
}

const student = {
 name: 'xiaoming'
}

defineReactive(student, 'name') // 劫持 name 屬性的讀取和設(shè)置操作

上述代碼通過(guò) Object.defineProperty() 方法設(shè)置屬性的 setter 與 getter 方法,從而達(dá)到劫持 student 對(duì)象中的 name 屬性的讀取和設(shè)置操作的目的。

讀者可以發(fā)現(xiàn),該方法每次只能設(shè)置一個(gè)屬性,那么就需要遍歷對(duì)象來(lái)完成其屬性的配置:

 Object.keys(student).forEach(key => defineReactive(student, key))

另外還必須是一個(gè)具體的屬性,這也非常的致命。

假如后續(xù)需要擴(kuò)展該對(duì)象,那么就必須手動(dòng)為新屬性設(shè)置 setter 和 getter 方法,**這就是為什么不在 data 中聲明的屬性無(wú)法自動(dòng)擁有雙向綁定效果的原因 **。(這時(shí)需要調(diào)用 Vue.set() 手動(dòng)設(shè)置)

以上便是對(duì)象劫持的核心實(shí)現(xiàn),但是還有以下重要的細(xì)節(jié)需要注意:

1、屬性描述符 - configurable

在 JavaScript 中,對(duì)象通過(guò)字面量創(chuàng)建時(shí),其屬性描述符默認(rèn)如下:

const foo = {
 name: '123'
}
Object.getOwnPropertyDescriptor(foo, 'name') // { value: '123', writable: true, enumerable: true, configurable: true }

前面也提到了 configurable 的值如果為 false,則無(wú)法再修改該屬性的描述符,所以在設(shè)置 setter 和 getter 方法時(shí),需要注意 configurable 選項(xiàng)的取值,否則在使用 Object.defineProperty() 方法時(shí)會(huì)拋出異常:

// 部分重復(fù)代碼 這里就不再羅列了。
function defineReactive (obj, key) {
 // ...

 const desc = Object.getOwnPropertyDescriptor(obj, key)

 if (desc && desc.configurable === false) {
  return
 }

 // ...
}

而在 JavaScript 中,導(dǎo)致 configurable 值為 false 的情況還是很多的:

  1. 可能該屬性在此之前已經(jīng)通過(guò) Object.defineProperty() 方法設(shè)置了 configurable 的值;
  2. 通過(guò) Object.seal() 方法設(shè)置該對(duì)象為密封對(duì)象,只能修改該屬性的值并且不能刪除該屬性以及修改屬性的描述符;
  3. 通過(guò) Object.freeze() 方法凍結(jié)該對(duì)象,相比較 Object.seal() 方法,它更為嚴(yán)格之處體現(xiàn)在不允許修改屬性的值。

2、默認(rèn) getter 和 setter 方法

另外,開(kāi)發(fā)者可能已經(jīng)為對(duì)象的屬性設(shè)置了 getter 和 setter 方法,對(duì)于這種情況,Vue 當(dāng)然不能破壞開(kāi)發(fā)者定義的方法,所以 Vue 中還要保護(hù)默認(rèn)的 getter 和 setter 方法:

// 部分重復(fù)代碼 這里就不再羅列了
function defineReactive (obj, key) {
 let val = obj[key]

 //....

 // 默認(rèn) getter setter
 const getter = desc && desc.get
 const setter = desc && desc.set

 Object.defineProperty(obj, key, {
  get () {
   const value = getter ? getter.call(obj) : val // 優(yōu)先執(zhí)行默認(rèn)的 getter
   return value
  },
  set (newValue) {
   const value = getter ? getter.call(obj) : val
   // 如果值相同則沒(méi)必要更新 === 的坑點(diǎn) NaN!!!!
   if (newValue === value || (value !== value && newValue !== newValue)) {
    return
   }

   if (getter && !setter) {
    // 用戶(hù)未設(shè)置 setter
    return
   }

   if (setter) {
    // 調(diào)用默認(rèn)的 setter 方法
    setter.call(obj, newValue)
   } else {
    val = newValue
   }
  }
 })
}

3、遞歸屬性值

最后一種比較重要的情況就是屬性的值可能也是一個(gè)對(duì)象,那么在處理對(duì)象的屬性時(shí),需要遞歸處理其屬性值:

function defineReactive (obj, key) {
 let val = obj[key]

 // ...

 // 遞歸處理其屬性值
 const childObj = observe(val)

 // ...
}

遞歸循環(huán)引用對(duì)象很容易出現(xiàn)遞歸爆棧問(wèn)題,對(duì)于這種情況,Vue 通過(guò)定義 ob 對(duì)象記錄已經(jīng)被設(shè)置過(guò) getter 和 setter 方法的對(duì)象,從而避免遞歸爆棧的問(wèn)題。

function isObject (val) {
 const type = val
 return val !== null && (type === 'object' || type === 'function')
}

function observe (value) {
 if (!isObject(value)) {
  return
 }

 let ob
 // 避免循環(huán)引用造成的遞歸爆棧問(wèn)題
 if (value.hasOwnProperty('__ob__') && value.__obj__ instanceof Observer) {
  ob = value.__ob__
 } else if (Object.isExtensible(value)) {
  // 后續(xù)需要定義諸如 __ob__ 這樣的屬性,所以需要能夠擴(kuò)展
  ob = new Observer(value)
 }

 return ob
}

上述代碼中提到了對(duì)象的可擴(kuò)展性,在 JavaScript 中所有對(duì)象默認(rèn)都是可擴(kuò)展的,但同時(shí)也提供了相應(yīng)的方法允許對(duì)象不可擴(kuò)展:

const obj = { name: 'xiaoming' }
Object.preventExtensions(obj)
obj.age = 20
console.log(obj.age) // undefined

除了上述方法,還有前面提到的 Object.seal() 和 Object.freeze() 方法。

三、針對(duì) Array 類(lèi)型的劫持

數(shù)組是一種特殊的對(duì)象,其下標(biāo)實(shí)際上就是對(duì)象的屬性,所以理論上是可以采用 Object.defineProperty() 方法處理數(shù)組對(duì)象。

但是 Vue 并沒(méi)有采用上述方法劫持?jǐn)?shù)組對(duì)象,筆者猜測(cè)主要由于以下兩點(diǎn):(讀者有更好的見(jiàn)解,歡迎留言。)

1、特殊的 length 屬性

數(shù)組對(duì)象的 length 屬性的描述符天生獨(dú)特:

const arr = [1, 2, 3]

Object.getOwnPropertyDescriptor(arr, 'length').configurable // false

這就意味著無(wú)法通過(guò) Object.defineProperty() 方法劫持 length 屬性的讀取和設(shè)置方法。

相比較對(duì)象的屬性,數(shù)組下標(biāo)變化地相對(duì)頻繁,并且改變數(shù)組長(zhǎng)度的方法也比較靈活,一旦數(shù)組的長(zhǎng)度發(fā)生變化,那么在無(wú)法自動(dòng)感知的情況下,開(kāi)發(fā)者只能手動(dòng)更新新增的數(shù)組下標(biāo),這可是一個(gè)很繁瑣的工作。

2、數(shù)組的操作場(chǎng)景

數(shù)組主要的操作場(chǎng)景還是遍歷,而對(duì)于每一個(gè)元素都掛載一個(gè) get 和 set 方法,恐怕也是不小的性能負(fù)擔(dān)。

3、數(shù)組方法的劫持

最終 Vue 選擇劫持一些常用的數(shù)組操作方法,從而知曉數(shù)組的變化情況:

const methods = [
 'push',
 'pop',
 'shift',
 'unshift',
 'sort',
 'reverse',
 'splice'
]

數(shù)組方法的劫持涉及到原型相關(guān)的知識(shí),首先數(shù)組實(shí)例大部分方法都是來(lái)源于 Array.prototype 對(duì)象。

但是這里不能直接篡改 Array.prototype 對(duì)象,這樣會(huì)影響所有的數(shù)組實(shí)例,為了避免這種情況,需要采用原型繼承得到一個(gè)新的原型對(duì)象:

const arrayProto = Array.prototype
const injackingPrototype = Object.create(arrayProto)

拿到新的原型對(duì)象之后,再重寫(xiě)這些常用的操作方法:

methods.forEach(method => {
 const originArrayMethod = arrayProto[method]
 injackingPrototype[method] = function (...args) {
  const result = originArrayMethod.apply(this, args)
  let inserted
  switch (method) {
   case 'push':
   case 'unshift':
    inserted = args
    break
   case 'splice':
    inserted = args.slice(2)
    break
  }
  if (inserted) {
   // 對(duì)于新增的元素,繼續(xù)劫持
   // ob.observeArray(inserted)
  }
  // 通知變化
  return result
 }
})

最后,更新劫持?jǐn)?shù)組實(shí)例的原型,在 ES6 之前,可以通過(guò)瀏覽器私有屬性 proto 指定原型,之后,便可以采用如下方法:

Object.setPrototypeOf(arr, injackingPrototype)

順便提一下,采用 Vue.set() 方法設(shè)置數(shù)組元素時(shí),Vue 內(nèi)部實(shí)際上是調(diào)用劫持后的 splice() 方法來(lái)觸發(fā)更新。

四、總結(jié)

由上述內(nèi)容可知,Vue 中的數(shù)據(jù)劫持分為兩大部分:

  1. 針對(duì) Object 類(lèi)型,采用 Object.defineProperty() 方法劫持屬性的讀取和設(shè)置方法;
  2. 針對(duì) Array 類(lèi)型,采用原型相關(guān)的知識(shí)劫持常用的函數(shù),從而知曉當(dāng)前數(shù)組發(fā)生變化。

并且 Object.defineProperty() 方法存在以下缺陷:

  1. 每次只能設(shè)置一個(gè)具體的屬性,導(dǎo)致需要遍歷對(duì)象來(lái)設(shè)置屬性,同時(shí)也導(dǎo)致了無(wú)法探測(cè)新增屬性;
  2. 屬性描述符 configurable 對(duì)其的影響是致命的。

而 ES6 中的 Proxy 可以完美的解決這些問(wèn)題(目前兼容性是個(gè)大問(wèn)題),這也是 Vue3.0 中的一個(gè)大動(dòng)作,有興趣的讀者可以查閱相關(guān)的資料。

以上所述是小編給大家介紹的數(shù)據(jù)劫持實(shí)現(xiàn)原理詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

  • Vue中Pinia的各種詳細(xì)說(shuō)明和使用示例

    Vue中Pinia的各種詳細(xì)說(shuō)明和使用示例

    Pinia是Vue 3的專(zhuān)屬狀態(tài)管理庫(kù),旨在替代Vuex,提供更簡(jiǎn)單、直觀的狀態(tài)管理解決方案,它支持組合式API和選項(xiàng)式API,允許跨組件或頁(yè)面共享狀態(tài),避免了Vuex中的許多復(fù)雜概念,本文介紹Vue中Pinia的各種詳細(xì)說(shuō)明和使用示例,感興趣的朋友一起看看吧
    2025-01-01
  • 詳解Vue依賴(lài)收集引發(fā)的問(wèn)題

    詳解Vue依賴(lài)收集引發(fā)的問(wèn)題

    這篇文章主要介紹了Vue依賴(lài)收集引發(fā)的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Vue3環(huán)境安裝以及項(xiàng)目搭建全過(guò)程

    Vue3環(huán)境安裝以及項(xiàng)目搭建全過(guò)程

    Vue工程化項(xiàng)目環(huán)境配置還是比較麻煩的,下面這篇文章主要給大家介紹了關(guān)于Vue3環(huán)境安裝以及項(xiàng)目搭建的相關(guān)資料,文中通過(guò)圖文以及代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • 如何在Vue3中正確使用ElementPlus,親測(cè)有效,避坑

    如何在Vue3中正確使用ElementPlus,親測(cè)有效,避坑

    這篇文章主要介紹了如何在Vue3中正確使用ElementPlus,親測(cè)有效,避坑!具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • 導(dǎo)致VUE頁(yè)面不刷新的問(wèn)題分析及解決方法

    導(dǎo)致VUE頁(yè)面不刷新的問(wèn)題分析及解決方法

    由于 Vue 會(huì)在初始化實(shí)例時(shí)對(duì) property 執(zhí)行 getter/setter 轉(zhuǎn)化,所以 property 必須在 data 對(duì)象上存在才能讓 Vue 將它轉(zhuǎn)換為響應(yīng)式的,這篇文章主要介紹了導(dǎo)致VUE頁(yè)面不刷新的問(wèn)題分析及解決方法,需要的朋友可以參考下
    2024-04-04
  • Vue使用watch同時(shí)監(jiān)聽(tīng)多個(gè)值的實(shí)現(xiàn)方法示例

    Vue使用watch同時(shí)監(jiān)聽(tīng)多個(gè)值的實(shí)現(xiàn)方法示例

    這篇文章主要為大家介紹了Vue中使用watch同時(shí)監(jiān)聽(tīng)多個(gè)值的實(shí)現(xiàn)方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • 如何利用vue-cli監(jiān)測(cè)webpack打包與啟動(dòng)時(shí)長(zhǎng)

    如何利用vue-cli監(jiān)測(cè)webpack打包與啟動(dòng)時(shí)長(zhǎng)

    這篇文章主要給大家介紹了關(guān)于如何利用vue-cli監(jiān)測(cè)webpack打包與啟動(dòng)時(shí)長(zhǎng)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-02-02
  • Vue中XMLHttpRequest的使用方法詳解

    Vue中XMLHttpRequest的使用方法詳解

    Vue中使用XMLHttpRequest(XHR)可以獲取數(shù)據(jù)的方式與傳統(tǒng)的HTML頁(yè)面相同,本文主要來(lái)和大家講講它的正確使用方法,希望對(duì)大家有所幫助
    2023-05-05
  • Vue2.0 事件的廣播與接收(觀察者模式)

    Vue2.0 事件的廣播與接收(觀察者模式)

    這篇文章主要介紹了Vue2.0 事件的廣播與接收(觀察者模式),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • vue 組件內(nèi)獲取actions的response方式

    vue 組件內(nèi)獲取actions的response方式

    今天小編就為大家分享一篇vue 組件內(nèi)獲取actions的response方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-11-11

最新評(píng)論

昭觉县| 石嘴山市| 鄂托克旗| 广元市| 弥渡县| 揭西县| 利川市| 左云县| 昌乐县| 屯留县| 庆云县| 应用必备| 丹棱县| 边坝县| 威海市| 梧州市| 荃湾区| 固原市| 建宁县| 普定县| 桦南县| 沅江市| 平邑县| 西峡县| 丹江口市| 仙桃市| 兰西县| 塔城市| 盐山县| 娱乐| 玛纳斯县| 乐亭县| 汶川县| 颍上县| 满洲里市| 大连市| 龙陵县| 安义县| 天津市| 义马市| 越西县|