亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

深入淺出 Vue 系列 -- 數據劫持實現原理

2019-11-19 11:43:53
字體:
來源:轉載
供稿:網友

一、前言

數據雙向綁定作為 Vue 核心功能之一,其實現原理主要分為兩部分:

  1. 數據劫持
  2. 發布訂閱模式

本篇文章主要介紹 Vue 實現數據劫持的思路,下一篇則會介紹發布訂閱模式的設計。

二、針對 Object 類型的劫持

對于 Object 類型,主要劫持其屬性的讀取與設置操作。在 JavaScript 中對象的屬性主要由一個字符串類型的“名稱”以及一個“屬性描述符”組成,屬性描述符包括以下選項:

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

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

function defineReactive (obj, key) { let val = obj[key] Object.defineProperty(obj, key, {  get () {   console.log(' === 收集依賴 === ')   console.log(' 當前值為:' + val)   return val  },  set (newValue) {   console.log(' === 通知變更 === ')   console.log(' 當前值為:' + newValue)   val = newValue  } })}const student = { name: 'xiaoming'}defineReactive(student, 'name') // 劫持 name 屬性的讀取和設置操作

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

讀者可以發現,該方法每次只能設置一個屬性,那么就需要遍歷對象來完成其屬性的配置:

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

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

假如后續需要擴展該對象,那么就必須手動為新屬性設置 setter 和 getter 方法,**這就是為什么不在 data 中聲明的屬性無法自動擁有雙向綁定效果的原因 **。(這時需要調用 Vue.set() 手動設置)

以上便是對象劫持的核心實現,但是還有以下重要的細節需要注意:

1、屬性描述符 - configurable

在 JavaScript 中,對象通過字面量創建時,其屬性描述符默認如下:

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

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

// 部分重復代碼 這里就不再羅列了。function defineReactive (obj, key) { // ... const desc = Object.getOwnPropertyDescriptor(obj, key) if (desc && desc.configurable === false) {  return } // ...}

而在 JavaScript 中,導致 configurable 值為 false 的情況還是很多的:

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

2、默認 getter 和 setter 方法

另外,開發者可能已經為對象的屬性設置了 getter 和 setter 方法,對于這種情況,Vue 當然不能破壞開發者定義的方法,所以 Vue 中還要保護默認的 getter 和 setter 方法:

// 部分重復代碼 這里就不再羅列了function defineReactive (obj, key) { let val = obj[key] //.... // 默認 getter setter const getter = desc && desc.get const setter = desc && desc.set Object.defineProperty(obj, key, {  get () {   const value = getter ? getter.call(obj) : val // 優先執行默認的 getter   return value  },  set (newValue) {   const value = getter ? getter.call(obj) : val   // 如果值相同則沒必要更新 === 的坑點 NaN!!!!   if (newValue === value || (value !== value && newValue !== newValue)) {    return   }   if (getter && !setter) {    // 用戶未設置 setter    return   }   if (setter) {    // 調用默認的 setter 方法    setter.call(obj, newValue)   } else {    val = newValue   }  } })}

3、遞歸屬性值

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

function defineReactive (obj, key) { let val = obj[key] // ... // 遞歸處理其屬性值 const childObj = observe(val) // ...}

遞歸循環引用對象很容易出現遞歸爆棧問題,對于這種情況,Vue 通過定義 ob 對象記錄已經被設置過 getter 和 setter 方法的對象,從而避免遞歸爆棧的問題。

function isObject (val) { const type = val return val !== null && (type === 'object' || type === 'function')}function observe (value) { if (!isObject(value)) {  return } let ob // 避免循環引用造成的遞歸爆棧問題 if (value.hasOwnProperty('__ob__') && value.__obj__ instanceof Observer) {  ob = value.__ob__ } else if (Object.isExtensible(value)) {  // 后續需要定義諸如 __ob__ 這樣的屬性,所以需要能夠擴展  ob = new Observer(value) } return ob}

上述代碼中提到了對象的可擴展性,在 JavaScript 中所有對象默認都是可擴展的,但同時也提供了相應的方法允許對象不可擴展:

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

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

三、針對 Array 類型的劫持

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

但是 Vue 并沒有采用上述方法劫持數組對象,筆者猜測主要由于以下兩點:(讀者有更好的見解,歡迎留言。)

1、特殊的 length 屬性

數組對象的 length 屬性的描述符天生獨特:

const arr = [1, 2, 3]Object.getOwnPropertyDescriptor(arr, 'length').configurable // false

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

相比較對象的屬性,數組下標變化地相對頻繁,并且改變數組長度的方法也比較靈活,一旦數組的長度發生變化,那么在無法自動感知的情況下,開發者只能手動更新新增的數組下標,這可是一個很繁瑣的工作。

2、數組的操作場景

數組主要的操作場景還是遍歷,而對于每一個元素都掛載一個 get 和 set 方法,恐怕也是不小的性能負擔。

3、數組方法的劫持

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

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

數組方法的劫持涉及到原型相關的知識,首先數組實例大部分方法都是來源于 Array.prototype 對象。

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

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

拿到新的原型對象之后,再重寫這些常用的操作方法:

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) {   // 對于新增的元素,繼續劫持   // ob.observeArray(inserted)  }  // 通知變化  return result }})

最后,更新劫持數組實例的原型,在 ES6 之前,可以通過瀏覽器私有屬性 proto 指定原型,之后,便可以采用如下方法:

Object.setPrototypeOf(arr, injackingPrototype)

順便提一下,采用 Vue.set() 方法設置數組元素時,Vue 內部實際上是調用劫持后的 splice() 方法來觸發更新。

四、總結

由上述內容可知,Vue 中的數據劫持分為兩大部分:

  1. 針對 Object 類型,采用 Object.defineProperty() 方法劫持屬性的讀取和設置方法;
  2. 針對 Array 類型,采用原型相關的知識劫持常用的函數,從而知曉當前數組發生變化。

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

  1. 每次只能設置一個具體的屬性,導致需要遍歷對象來設置屬性,同時也導致了無法探測新增屬性;
  2. 屬性描述符 configurable 對其的影響是致命的。

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

以上所述是小編給大家介紹的數據劫持實現原理詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對武林網網站的支持!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲va国产va天堂va久久| 一本色道久久88综合日韩精品| 国产精品自拍偷拍视频| 久久久91精品| 91久久国产精品91久久性色| 亚洲三级黄色在线观看| 日本不卡视频在线播放| 国产精品青草久久久久福利99| 另类天堂视频在线观看| 7m第一福利500精品视频| 亚洲肉体裸体xxxx137| 欧美黑人国产人伦爽爽爽| 亚洲激情电影中文字幕| 亚洲va男人天堂| 免费不卡在线观看av| 欧美成人网在线| 亚洲国产中文字幕在线观看| 日韩欧美国产一区二区| 国产免费一区视频观看免费| 亚洲情综合五月天| 日韩精品视频在线观看免费| 日韩精品在线免费观看视频| 孩xxxx性bbbb欧美| 亚洲精品福利视频| 久久久国产一区二区三区| 97涩涩爰在线观看亚洲| 在线日韩欧美视频| 美女精品久久久| 久久久电影免费观看完整版| 精品久久久免费| www.久久草.com| 国产91热爆ts人妖在线| 2019中文字幕在线| 九九热r在线视频精品| 97人洗澡人人免费公开视频碰碰碰| 欧美日韩一区二区三区| 精品国产欧美一区二区五十路| 久久亚洲国产精品成人av秋霞| 欧美精品激情在线观看| 91国产精品视频在线| 亚洲免费电影一区| 国产91av在线| 国产网站欧美日韩免费精品在线观看| 4388成人网| 欧美日韩中文字幕综合视频| 亚洲精品av在线| 欧美午夜精品久久久久久久| 精品国产拍在线观看| 国产精品精品视频一区二区三区| 亚洲精品乱码久久久久久金桔影视| 福利微拍一区二区| 国产成人精品a视频一区www| 久久久999精品视频| 欧美激情精品久久久久久蜜臀| 影音先锋欧美在线资源| 欧美视频一区二区三区…| 国产精自产拍久久久久久| 欧美精品在线视频观看| 久久久午夜视频| 国产做受高潮69| 亚洲激情在线观看视频免费| 九九九热精品免费视频观看网站| 成人欧美一区二区三区在线| 91九色国产在线| 亚洲乱码一区av黑人高潮| 久久成年人免费电影| 欧美激情中文网| 国产精品高潮在线| 欧美性理论片在线观看片免费| 国产精品网红福利| 国产亚洲精品久久久久动| 亚洲女人被黑人巨大进入al| 91视频免费网站| 中文字幕精品久久| 久久视频在线观看免费| 亚洲欧洲日产国码av系列天堂| 在线精品高清中文字幕| 欧美另类高清videos| 成人a在线观看| 91在线免费视频| 午夜精品福利电影| 精品偷拍各种wc美女嘘嘘| 国产精品成人va在线观看| 国产精品99免视看9| 97视频在线观看免费| 国内精品一区二区三区四区| 伊人亚洲福利一区二区三区| 国产精品96久久久久久又黄又硬| 国产精品久久久久aaaa九色| 亚洲欧洲日韩国产| 亚洲欧美制服丝袜| 国产精品久久av| 91经典在线视频| 国产91av在线| 色偷偷偷亚洲综合网另类| 亚洲成人av在线播放| 欧美激情网友自拍| 国产精品午夜一区二区欲梦| 91伊人影院在线播放| 亚洲精品国产精品久久清纯直播| 午夜免费久久久久| 国产欧美一区二区三区四区| 亚洲免费精彩视频| 日韩精品在线观看一区| 亚洲欧美制服中文字幕| 欧美性猛交xxx| 亚洲网站在线观看| 欧美富婆性猛交| 欧美韩日一区二区| 日韩国产中文字幕| 97碰碰碰免费色视频| 亚洲欧美视频在线| 亚洲人成在线观| 久久99久久99精品免观看粉嫩| 91国自产精品中文字幕亚洲| 亚洲片国产一区一级在线观看| 久久亚洲综合国产精品99麻豆精品福利| 亚洲欧美日韩中文在线制服| 成人黄色片在线| 亚洲2020天天堂在线观看| 日韩av免费看网站| 欧美亚洲日本网站| 欧美精品情趣视频| 北条麻妃在线一区二区| 久久夜色精品国产亚洲aⅴ| 国产一区二区三区中文| 欧美日韩成人在线观看| 精品综合久久久久久97| 成人免费淫片视频软件| 欧美寡妇偷汉性猛交| 久久99精品久久久久久琪琪| yellow中文字幕久久| 秋霞成人午夜鲁丝一区二区三区| 欧美日韩美女视频| 日韩电影免费在线观看| 免费91麻豆精品国产自产在线观看| 国内成人精品视频| 最新中文字幕亚洲| 亚洲精品自拍第一页| 国产精品福利网站| 不卡在线观看电视剧完整版| 精品亚洲一区二区三区| 亚洲色图狂野欧美| 国产午夜精品全部视频在线播放| 日韩av电影免费观看高清| 国产日韩在线免费| 国产精品吊钟奶在线| 国产97在线亚洲| 久久久综合免费视频| 91在线无精精品一区二区| 亚洲aaaaaa| 日韩美女视频在线观看| 欧美日韩中文字幕在线视频| 久久激情视频免费观看| 91精品久久久久久久久不口人| 国产一区玩具在线观看| 欧美专区第一页| 日日噜噜噜夜夜爽亚洲精品| 97涩涩爰在线观看亚洲| 91久久嫩草影院一区二区| 国产精品女人网站| 亚洲伊人久久大香线蕉av| 国产亚洲美女精品久久久| 91精品视频免费观看|