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

首頁 > 編程 > JavaScript > 正文

這應該是最詳細的響應式系統講解了

2019-11-19 11:09:16
字體:
來源:轉載
供稿:網友

前言

本文從一個簡單的雙向綁定開始,逐步升級到由defineProperty和Proxy分別實現的響應式系統,注重入手思路,抓住關鍵細節,希望能對你有所幫助。

一、極簡雙向綁定

首先從最簡單的雙向綁定入手:

// html<input type="text" id="input"><span id="span"></span>
// jslet input = document.getElementById('input')let span = document.getElementById('span')input.addEventListener('keyup', function(e) { span.innerHTML = e.target.value})

以上似乎運行起來也沒毛病,但我們要的是數據驅動,而不是直接操作dom:

// 操作obj數據來驅動更新let obj = {}let input = document.getElementById('input')let span = document.getElementById('span')Object.defineProperty(obj, 'text', { configurable: true, enumerable: true, get() {  console.log('獲取數據了')  return obj.text }, set(newVal) {  console.log('數據更新了')  input.value = newVal  span.innerHTML = newVal }})input.addEventListener('keyup', function(e) { obj.text = e.target.value})

以上就是一個簡單的雙向數據綁定,但顯然是不足的,下面繼續升級。

二、以defineProperty實現響應系統

在Vue3版本來臨前以defineProperty實現的數據響應,基于發布訂閱模式,其主要包含三部分:Observer、Dep、Watcher。

1. 一個思路例子

// 需要劫持的數據let data = { a: 1, b: {  c: 3 }}// 劫持數據dataobserver(data)// 監聽訂閱數據data的屬性new Watch('a', () => {  alert(1)})new Watch('a', () => {  alert(2)})new Watch('b.c', () => {  alert(3)})

以上就是一個簡單的劫持和監聽流程,那對應的observer和Watch該如何實現?

2. Observer

observer的作用就是劫持數據,將數據屬性轉換為訪問器屬性,理一下實現思路:

①Observer需要將數據轉化為響應式的,那它就應該是一個函數(類),能接收參數。
②為了將數據變成響應式,那需要使用Object.defineProperty。
③數據不止一種類型,這就需要遞歸遍歷來判斷。

// 定義一個類供傳入監聽數據class Observer { constructor(data) {  let keys = Object.keys(data)  for (let i = 0; i < keys.length; i++) {   defineReactive(data, keys[i], data[keys[i]])  } }}// 使用Object.definePropertyfunction defineReactive (data, key, val) { // 每次設置訪問器前都先驗證值是否為對象,實現遞歸每個屬性 observer(val) // 劫持數據屬性 Object.defineProperty(data, key, {  configurable: true,  enumerable: true,  get () {   return val  },  set (newVal) {   if (newVal === val) {    return   } else {    data[key] = newVal    // 新值也要劫持    observer(newVal)   }  } })}// 遞歸判斷function observer (data) { if (Object.prototype.toString.call(data) === '[object, Object]') {  new Observer(data) } else {  return }}// 監聽objobserver(data)

3. Watcher

根據new Watch('a', () => {alert(1)})我們猜測Watch應該是這樣的:

class Watch { // 第一個參數為表達式,第二個參數為回調函數 constructor (exp, cb) {  this.exp = exp  this.cb = cb }}

那Watch和observer該如何關聯?想想它們之間有沒有關聯的點?似乎可以從exp下手,這是它們共有的點:

class Watch { // 第一個參數為表達式,第二個參數為回調函數 constructor (exp, cb) {  this.exp = exp  this.cb = cb  data[exp]  // 想想多了這句有什么作用 }}

data[exp]這句話是不是表示在取某個值,如果exp為a的話,那就表示data.a,在這之前data下的屬性已經被我們劫持為訪問器屬性了,那這就表明我們能觸發對應屬性的get函數,那這就與observer產生了關聯,那既然如此,那在觸發get函數的時候能不能把觸發者Watch給收集起來呢?此時就得需要一個橋梁Dep來協助了。

4. Dep

思路應該是data下的每一個屬性都有一個唯一的Dep對象,在get中收集僅針對該屬性的依賴,然后在set方法中觸發所有收集的依賴,這樣就搞定了,看如下代碼:

class Dep { constructor () {  // 定義一個收集對應屬性依賴的容器  this.subs = [] } // 收集依賴的方法 addSub () {  // Dep.target是個全局變量,用于存儲當前的一個watcher  this.subs.push(Dep.target) } // set方法被觸發時會通知依賴 notify () {  for (let i = 1; i < this.subs.length; i++) {   this.subs[i].cb()  } }}Dep.target = nullclass Watch { constructor (exp, cb) {  this.exp = exp  this.cb = cb  // 將Watch實例賦給全局變量Dep.target,這樣get中就能拿到它了  Dep.target = this  data[exp] }}

此時對應的defineReactive我們也要增加一些代碼:

function defineReactive (data, key, val) { observer() let dep = new Dep() // 新增:這樣每個屬性就能對應一個Dep實例了 Object.defineProperty(data, key, {  configurable: true,  enumerable: true,  get () {   dep.addSub() // 新增:get觸發時會觸發addSub來收集當前的Dep.target,即watcher   return val  },  set (newVal) {   if (newVal === val) {    return   } else {    data[key] = newVal    observer(newVal)    dep.notify() // 新增:通知對應的依賴   }  } })}

至此observer、Dep、Watch三者就形成了一個整體,分工明確。但還有一些地方需要處理,比如我們直接對被劫持過的對象添加新的屬性是監測不到的,修改數組的元素值也是如此。這里就順便提一下Vue源碼中是如何解決這個問題的:

對于對象:Vue中提供了Vue.set和vm.$set這兩個方法供我們添加新的屬性,其原理就是先判斷該屬性是否為響應式的,如果不是,則通過defineReactive方法將其轉為響應式。

對于數組:直接使用下標修改值還是無效的,Vue只hack了數組中的七個方法:pop','push','shift','unshift','splice','sort','reverse',使得我們用起來依舊是響應式的。其原理是:在我們調用數組的這七個方法時,Vue會改造這些方法,它內部同樣也會執行這些方法原有的邏輯,只是增加了一些邏輯:取到所增加的值,然后將其變成響應式,然后再手動出發dep.notify()

三、以Proxy實現響應系統

Proxy是在目標前架設一層"攔截",外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫,我們可以這樣認為,Proxy是Object.defineProperty的全方位加強版。

依舊是三大件:Observer、Dep、Watch,我們在之前的基礎再完善這三大件。

1. Dep

let uid = 0 // 新增:定義一個idclass Dep { constructor () {  this.id = uid++ // 新增:給dep添加id,避免Watch重復訂閱  this.subs = [] } depend() { // 新增:源碼中在觸發get時是先觸發depend方法再進行依賴收集的,這樣能將dep傳給Watch  Dep.target.addDep(this); } addSub () {  this.subs.push(Dep.target) } notify () {  for (let i = 1; i < this.subs.length; i++) {   this.subs[i].cb()  } }}

2. Watch

class Watch { constructor (exp, cb) {  this.depIds = {} // 新增:儲存訂閱者的id,避免重復訂閱  this.exp = exp  this.cb = cb  Dep.target = this  data[exp]  // 新增:判斷是否訂閱過該dep,沒有則存儲該id并調用dep.addSub收集當前watcher  addDep (dep) {    if (!this.depIds.hasOwnProperty(dep.id)) {    dep.addSub(this)    this.depIds[dep.id] = dep   }  }  // 新增:將訂閱者放入待更新隊列等待批量更新  update () {   pushQueue(this)  }  // 新增:觸發真正的更新操作  run () {   this.cb()  } }}

3. Observer

與Object.defineProperty監聽屬性不同,Proxy可以監聽(實際是代理)整個對象,因此就不需要遍歷對象的屬性依次監聽了,但是如果對象的屬性依然是個對象,那么Proxy也無法監聽,所以依舊使用遞歸套路即可。

function Observer (data) { let dep = new Dep() return new Proxy(data, {  get () {   // 如果訂閱者存在,進去depend方法   if (Dep.target) {    dep.depend()   }   // Reflect.get了解一下   return Reflect.get(data, key)  },  set (data, key, newVal) {   // 如果值未變,則直接返回,不觸發后續操作   if (Reflect.get(data, key) === newVal) {    return   } else {    // 設置新值的同時對新值判斷是否要遞歸監聽    Reflect.set(target, key, observer(newVal))    // 當值被觸發更改的時候,觸發Dep的通知方法    dep.notify(key)   }  } })}// 遞歸監聽function observer (data) { // 如果不是對象則直接返回 if (Object.prototype.toString.call(data) !== '[object, Object]') {  return data } // 為對象時則遞歸判斷屬性值 Object.keys(data).forEach(key => {  data[key] = observer(data[key]) }) return Observer(data)}// 監聽objObserver(data)

至此就基本完成了三大件了,同時其不需要hack也能對數組進行監聽。

四、觸發依賴收集與批量異步更新

完成了響應式系統,也順便提一下Vue源碼中是如何觸發依賴收集與批量異步更新的。

1. 觸發依賴收集

在Vue源碼中的$mount方法調用時會間接觸發了一段代碼:

vm._watcher = new Watcher(vm, () => { vm._update(vm._render(), hydrating)}, noop)

這使得new Watcher()會先對其傳入的參數進行求值,也就間接觸發了vm._render(),這其實就會觸發了對數據的訪問,進而觸發屬性的get方法而達到依賴的收集。

2. 批量異步更新

Vue在更新DOM時是異步執行的。只要偵聽到數據變化,Vue將開啟一個隊列,并緩沖在同一事件循環中發生的所有數據變更。如果同一個watcher被多次觸發,只會被推入到隊列中一次。這種在緩沖時去除重復數據對于避免不必要的計算和DOM操作是非常重要的。然后,在下一個的事件循環“tick”中,Vue刷新隊列并執行實際 (已去重的) 工作。Vue在內部對異步隊列嘗試使用原生的Promise.then、MutationObserver和setImmediate,如果執行環境不支持,則會采用setTimeout(fn, 0)代替。

根據以上這段官方文檔,這個隊列主要是異步和去重,首先我們來整理一下思路:

  1. 需要有一個隊列來存儲一個事件循環中的數據變更,且要對它去重。
  2. 將當前事件循環中的數據變更添加到隊列。
  3. 異步的去執行這個隊列中的所有數據變更。
// 使用Set數據結構創建一個隊列,這樣可自動去重let queue = new Set()// 在屬性出發set方法時會觸發watcher.update,繼而執行以下方法function pushQueue (watcher) { // 將數據變更添加到隊列 queue.add(watcher) // 下一個tick執行該數據變更,所以nextTick接受的應該是一個能執行queue隊列的函數 nextTick('一個能遍歷執行queue的函數')}// 用Promise模擬nextTickfunction nextTick('一個能遍歷執行queue的函數') { Promise.resolve().then('一個能遍歷執行queue的函數')}

以上已經有個大體的思路了,那接下來完成'一個能遍歷執行queue的函數':

// queue是一個數組,所以直接遍歷執行即可function flushQueue () { queue.forEach(watcher => {  // 觸發watcher中的run方法進行真正的更新操作  watcher.run() }) // 執行后清空隊列 queue = new Set()}

還有一個問題,那就是同一個事件循環中應該只要觸發一次nextTick即可,而不是每次添加隊列時都觸發:

// 設置一個是否觸發了nextTick的標識let waiting = falsefunction pushQueue (watcher) { queue.add(watcher) if (!waiting) {  // 保證nextTick只觸發一次  waiting = true  nextTick('一個能遍歷執行queue的函數') }}

完整代碼如下:

// 定義隊列let queue = new Set()// 供傳入nextTick中的執行隊列的函數function flushQueue () { queue.forEach(watcher => {  watcher.run() }) queue = new Set()}// nextTickfunction nextTick(flushQueue) { Promise.resolve().then(flushQueue)}// 添加到隊列并調用nextTicklet waiting = falsefunction pushQueue (watcher) { queue.add(watcher) if (!waiting) {  waiting = true  nextTick(flushQueue) }}

最后

以上就是響應式的一個大概原理,希望對大家的學習有所幫助,也希望大家多多支持武林網。

相關參考:

Vue源碼學習

實現雙向綁定Proxy比defineproperty優劣如何?

Vue.js源碼全方位深入解析

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美日韩在线视频一区| 欧美成人在线网站| 中文字幕亚洲综合久久筱田步美| 91精品国产九九九久久久亚洲| 97超视频免费观看| 久久五月情影视| 国产一区二区美女视频| 国产精品成人观看视频国产奇米| 国产一区二区三区在线看| 国产欧美韩国高清| 日韩国产高清污视频在线观看| 久久久97精品| 亚洲一区二区三区毛片| 国产精品久久久久av免费| 午夜剧场成人观在线视频免费观看| 国产精品综合久久久| 日韩美女免费线视频| 欧美黑人极品猛少妇色xxxxx| 亚洲va码欧洲m码| 亚洲高清福利视频| 亚洲美女福利视频网站| 午夜免费日韩视频| 久久亚洲影音av资源网| 亚洲男人的天堂在线播放| 亚洲性生活视频在线观看| 欧美孕妇与黑人孕交| 欧美成人精品在线视频| 午夜精品一区二区三区在线播放| 久久综合久久美利坚合众国| 欧美亚洲另类制服自拍| 成人性生交大片免费观看嘿嘿视频| 97视频在线观看视频免费视频| 欧美国产日韩在线| 91日本在线视频| 亚洲第一免费播放区| 亚洲女人初尝黑人巨大| 欧美专区在线观看| 日韩精品在线私人| 日韩中文字幕国产精品| 俺去亚洲欧洲欧美日韩| 久久国内精品一国内精品| 国产精品成av人在线视午夜片| 亚洲国产中文字幕在线观看| 日韩美女视频免费在线观看| 久久久亚洲国产天美传媒修理工| 91av视频在线| 亚洲国产精品免费| 精品视频一区在线视频| 亚洲精品乱码久久久久久金桔影视| 亚洲美女av电影| 亚洲无亚洲人成网站77777| 亚洲三级av在线| 欧日韩不卡在线视频| 日韩激情av在线免费观看| 日韩一区二区三区在线播放| 中文字幕免费精品一区| 国产精品白嫩美女在线观看| 亚洲丝袜在线视频| 亚洲国产高清高潮精品美女| 国产成人免费av| 久久久国产精品一区| 伊人伊人伊人久久| 欧美成人三级视频网站| 91精品国产自产91精品| 欧美日韩一区二区在线| 日韩在线播放一区| 亚洲精品xxx| 亚洲一区二区三区乱码aⅴ蜜桃女| 2024亚洲男人天堂| 久久精品一本久久99精品| 日韩av在线精品| 精品国内自产拍在线观看| 欧美人与性动交| 中文.日本.精品| 欧美电影第一页| 亚洲一区二区久久久| 国产99视频在线观看| 欧美超级乱淫片喷水| 懂色aⅴ精品一区二区三区蜜月| 欧美性视频精品| 国产精品视频永久免费播放| 国产精品久久久久久久午夜| 午夜精品一区二区三区在线播放| 26uuu久久噜噜噜噜| 亚洲国产精品va在线看黑人动漫| 亚洲精品按摩视频| 91av免费观看91av精品在线| 亚洲一区国产精品| 最近日韩中文字幕中文| 92看片淫黄大片看国产片| 亚洲电影免费观看高清完整版在线| 国产美女精彩久久| 国产精品羞羞答答| 久久精品夜夜夜夜夜久久| 中文字幕亚洲欧美| 欧美成年人在线观看| 亚洲精品福利在线| 欧美日韩视频在线| 亚洲午夜精品久久久久久久久久久久| 亚洲精品久久7777777| 欧美一乱一性一交一视频| 亚洲精品久久久久久久久久久| 亚洲tv在线观看| 色偷偷偷综合中文字幕;dd| 亚洲欧美在线第一页| 欧美有码在线观看视频| 欧美激情一区二区三区久久久| 欧美一区二区影院| 欧美性精品220| 亚洲а∨天堂久久精品喷水| 欧美激情视频免费观看| 成人精品视频99在线观看免费| 日韩在线精品视频| 日韩欧美在线观看| 亚洲日本aⅴ片在线观看香蕉| 97国产suv精品一区二区62| 日韩高清av在线| 爽爽爽爽爽爽爽成人免费观看| 国产69久久精品成人| 亚洲免费视频一区二区| 亚洲国产福利在线| 久久不射电影网| 精品免费在线视频| 日韩av毛片网| 欧美国产在线视频| 91精品久久久久久久久| 久久影视电视剧免费网站| 97久久精品人搡人人玩| 九九热精品视频| 日韩精品视频免费在线观看| 欧美孕妇性xx| 日韩精品免费在线播放| 国产一区二区三区在线观看网站| 欧美色道久久88综合亚洲精品| 永久免费精品影视网站| 91久久精品在线| 国产日韩在线精品av| 成人午夜高潮视频| 欧美极品欧美精品欧美视频| 欧美大人香蕉在线| 国产91免费观看| 国产精品极品美女在线观看免费| 亚洲精品一区二区三区婷婷月| 日本亚洲欧美三级| 精品国产91乱高清在线观看| 视频一区视频二区国产精品| 亚洲成人三级在线| 亚洲另类图片色| 日韩av大片在线| 国产精品久久久久福利| 日韩在线观看免费| 欧美日韩一区二区免费在线观看| 91久久久久久久久久久久久| 欧美色道久久88综合亚洲精品| 亚洲精品一区久久久久久| 亚洲精品在线观看www| 亚洲成avwww人| 亚洲精品日韩激情在线电影| 久久露脸国产精品| 亚洲香蕉成人av网站在线观看| 日韩精品极品在线观看播放免费视频| 国产99久久精品一区二区永久免费| 欧美精品18videos性欧美| 色综合影院在线|