久久久精品一区ed2k-女人被男人叉到高潮的视频-中文字幕乱码一区久久麻豆樱花-俄罗斯熟妇真实视频

defineProperty和Proxy如何實(shí)現(xiàn)的響應(yīng)式系統(tǒng)

這篇文章主要介紹了defineProperty和Proxy如何實(shí)現(xiàn)的響應(yīng)式系統(tǒng),具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

創(chuàng)新互聯(lián)是一家專注于成都網(wǎng)站建設(shè)、成都網(wǎng)站制作與策劃設(shè)計(jì),遷西網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)10余年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:遷西等地區(qū)。遷西做網(wǎng)站價(jià)格咨詢:028-86922220

一、極簡(jiǎn)雙向綁定

首先從最簡(jiǎn)單的雙向綁定入手:

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

以上似乎運(yùn)行起來(lái)也沒毛病,但我們要的是數(shù)據(jù)驅(qū)動(dòng),而不是直接操作dom:

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

以上就是一個(gè)簡(jiǎn)單的雙向數(shù)據(jù)綁定,但顯然是不足的,下面繼續(xù)升級(jí)。

二、以defineProperty實(shí)現(xiàn)響應(yīng)系統(tǒng)

在Vue3版本來(lái)臨前以defineProperty實(shí)現(xiàn)的數(shù)據(jù)響應(yīng),基于發(fā)布訂閱模式,其主要包含三部分:Observer、Dep、Watcher。

1. 一個(gè)思路例子

// 需要劫持的數(shù)據(jù)
let data = {
 a: 1,
 b: {
  c: 3
 }
}

// 劫持?jǐn)?shù)據(jù)data
observer(data)

// 監(jiān)聽訂閱數(shù)據(jù)data的屬性
new Watch('a', () => {
  alert(1)
})
new Watch('a', () => {
  alert(2)
})
new Watch('b.c', () => {
  alert(3)
})

以上就是一個(gè)簡(jiǎn)單的劫持和監(jiān)聽流程,那對(duì)應(yīng)的observer和Watch該如何實(shí)現(xiàn)?

2. Observer

observer的作用就是劫持?jǐn)?shù)據(jù),將數(shù)據(jù)屬性轉(zhuǎn)換為訪問(wèn)器屬性,理一下實(shí)現(xiàn)思路:

①Observer需要將數(shù)據(jù)轉(zhuǎn)化為響應(yīng)式的,那它就應(yīng)該是一個(gè)函數(shù)(類),能接收參數(shù)。
②為了將數(shù)據(jù)變成響應(yīng)式,那需要使用Object.defineProperty。
③數(shù)據(jù)不止一種類型,這就需要遞歸遍歷來(lái)判斷。

// 定義一個(gè)類供傳入監(jiān)聽數(shù)據(jù)
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.defineProperty
function defineReactive (data, key, val) {
 // 每次設(shè)置訪問(wèn)器前都先驗(yàn)證值是否為對(duì)象,實(shí)現(xiàn)遞歸每個(gè)屬性
 observer(val)
 // 劫持?jǐn)?shù)據(jù)屬性
 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
 }
}

// 監(jiān)聽obj
observer(data)

3. Watcher

根據(jù)new Watch('a', () => {alert(1)})我們猜測(cè)Watch應(yīng)該是這樣的:

class Watch {
 // 第一個(gè)參數(shù)為表達(dá)式,第二個(gè)參數(shù)為回調(diào)函數(shù)
 constructor (exp, cb) {
  this.exp = exp
  this.cb = cb
 }
}

那Watch和observer該如何關(guān)聯(lián)?想想它們之間有沒有關(guān)聯(lián)的點(diǎn)?似乎可以從exp下手,這是它們共有的點(diǎn):

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

data[exp]這句話是不是表示在取某個(gè)值,如果exp為a的話,那就表示data.a,在這之前data下的屬性已經(jīng)被我們劫持為訪問(wèn)器屬性了,那這就表明我們能觸發(fā)對(duì)應(yīng)屬性的get函數(shù),那這就與observer產(chǎn)生了關(guān)聯(lián),那既然如此,那在觸發(fā)get函數(shù)的時(shí)候能不能把觸發(fā)者Watch給收集起來(lái)呢?此時(shí)就得需要一個(gè)橋梁Dep來(lái)協(xié)助了。

4. Dep

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

class Dep {
 constructor () {
  // 定義一個(gè)收集對(duì)應(yīng)屬性依賴的容器
  this.subs = []
 }
 // 收集依賴的方法
 addSub () {
  // Dep.target是個(gè)全局變量,用于存儲(chǔ)當(dāng)前的一個(gè)watcher
  this.subs.push(Dep.target)
 }
 // set方法被觸發(fā)時(shí)會(huì)通知依賴
 notify () {
  for (let i = 1; i < this.subs.length; i++) {
   this.subs[i].cb()
  }
 }
}

Dep.target = null

class Watch {
 constructor (exp, cb) {
  this.exp = exp
  this.cb = cb
  // 將Watch實(shí)例賦給全局變量Dep.target,這樣get中就能拿到它了
  Dep.target = this
  data[exp]
 }
}

此時(shí)對(duì)應(yīng)的defineReactive我們也要增加一些代碼:

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

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

對(duì)于對(duì)象:Vue中提供了Vue.set和vm.$set這兩個(gè)方法供我們添加新的屬性,其原理就是先判斷該屬性是否為響應(yīng)式的,如果不是,則通過(guò)defineReactive方法將其轉(zhuǎn)為響應(yīng)式。

對(duì)于數(shù)組:直接使用下標(biāo)修改值還是無(wú)效的,Vue只hack了數(shù)組中的七個(gè)方法:pop','push','shift','unshift','splice','sort','reverse',使得我們用起來(lái)依舊是響應(yīng)式的。其原理是:在我們調(diào)用數(shù)組的這七個(gè)方法時(shí),Vue會(huì)改造這些方法,它內(nèi)部同樣也會(huì)執(zhí)行這些方法原有的邏輯,只是增加了一些邏輯:取到所增加的值,然后將其變成響應(yīng)式,然后再手動(dòng)出發(fā)dep.notify()

三、以Proxy實(shí)現(xiàn)響應(yīng)系統(tǒng)

Proxy是在目標(biāo)前架設(shè)一層"攔截",外界對(duì)該對(duì)象的訪問(wèn),都必須先通過(guò)這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫,我們可以這樣認(rèn)為,Proxy是Object.defineProperty的全方位加強(qiáng)版。

依舊是三大件:Observer、Dep、Watch,我們?cè)谥暗幕A(chǔ)再完善這三大件。

1. Dep

let uid = 0 // 新增:定義一個(gè)id
class Dep {
 constructor () {
  this.id = uid++ // 新增:給dep添加id,避免Watch重復(fù)訂閱
  this.subs = []
 }
 depend() { // 新增:源碼中在觸發(fā)get時(shí)是先觸發(fā)depend方法再進(jìn)行依賴收集的,這樣能將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 = {} // 新增:儲(chǔ)存訂閱者的id,避免重復(fù)訂閱
  this.exp = exp
  this.cb = cb
  Dep.target = this
  data[exp]
  // 新增:判斷是否訂閱過(guò)該dep,沒有則存儲(chǔ)該id并調(diào)用dep.addSub收集當(dāng)前watcher
  addDep (dep) { 
   if (!this.depIds.hasOwnProperty(dep.id)) {
    dep.addSub(this)
    this.depIds[dep.id] = dep
   }
  }
  // 新增:將訂閱者放入待更新隊(duì)列等待批量更新
  update () {
   pushQueue(this)
  }
  // 新增:觸發(fā)真正的更新操作
  run () {
   this.cb()
  }
 }
}

3. Observer

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

function Observer (data) {
 let dep = new Dep()
 return new Proxy(data, {
  get () {
   // 如果訂閱者存在,進(jìn)去depend方法
   if (Dep.target) {
    dep.depend()
   }
   // Reflect.get了解一下
   return Reflect.get(data, key)
  },
  set (data, key, newVal) {
   // 如果值未變,則直接返回,不觸發(fā)后續(xù)操作
   if (Reflect.get(data, key) === newVal) {
    return
   } else {
    // 設(shè)置新值的同時(shí)對(duì)新值判斷是否要遞歸監(jiān)聽
    Reflect.set(target, key, observer(newVal))
    // 當(dāng)值被觸發(fā)更改的時(shí)候,觸發(fā)Dep的通知方法
    dep.notify(key)
   }
  }
 })
}

// 遞歸監(jiān)聽
function observer (data) {
 // 如果不是對(duì)象則直接返回
 if (Object.prototype.toString.call(data) !== '[object, Object]') {
  return data
 }
 // 為對(duì)象時(shí)則遞歸判斷屬性值
 Object.keys(data).forEach(key => {
  data[key] = observer(data[key])
 })
 return Observer(data)
}

// 監(jiān)聽obj
Observer(data)

至此就基本完成了三大件了,同時(shí)其不需要hack也能對(duì)數(shù)組進(jìn)行監(jiān)聽。

四、觸發(fā)依賴收集與批量異步更新

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

1. 觸發(fā)依賴收集

在Vue源碼中的$mount方法調(diào)用時(shí)會(huì)間接觸發(fā)了一段代碼:

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

這使得new Watcher()會(huì)先對(duì)其傳入的參數(shù)進(jìn)行求值,也就間接觸發(fā)了vm._render(),這其實(shí)就會(huì)觸發(fā)了對(duì)數(shù)據(jù)的訪問(wèn),進(jìn)而觸發(fā)屬性的get方法而達(dá)到依賴的收集。

2. 批量異步更新

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

根據(jù)以上這段官方文檔,這個(gè)隊(duì)列主要是異步和去重,首先我們來(lái)整理一下思路:

  1. 需要有一個(gè)隊(duì)列來(lái)存儲(chǔ)一個(gè)事件循環(huán)中的數(shù)據(jù)變更,且要對(duì)它去重。

  2. 將當(dāng)前事件循環(huán)中的數(shù)據(jù)變更添加到隊(duì)列。

  3. 異步的去執(zhí)行這個(gè)隊(duì)列中的所有數(shù)據(jù)變更。

// 使用Set數(shù)據(jù)結(jié)構(gòu)創(chuàng)建一個(gè)隊(duì)列,這樣可自動(dòng)去重
let queue = new Set()

// 在屬性出發(fā)set方法時(shí)會(huì)觸發(fā)watcher.update,繼而執(zhí)行以下方法
function pushQueue (watcher) {
 // 將數(shù)據(jù)變更添加到隊(duì)列
 queue.add(watcher)
 // 下一個(gè)tick執(zhí)行該數(shù)據(jù)變更,所以nextTick接受的應(yīng)該是一個(gè)能執(zhí)行queue隊(duì)列的函數(shù)
 nextTick('一個(gè)能遍歷執(zhí)行queue的函數(shù)')
}

// 用Promise模擬nextTick
function nextTick('一個(gè)能遍歷執(zhí)行queue的函數(shù)') {
 Promise.resolve().then('一個(gè)能遍歷執(zhí)行queue的函數(shù)')
}

以上已經(jīng)有個(gè)大體的思路了,那接下來(lái)完成'一個(gè)能遍歷執(zhí)行queue的函數(shù)':

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

還有一個(gè)問(wèn)題,那就是同一個(gè)事件循環(huán)中應(yīng)該只要觸發(fā)一次nextTick即可,而不是每次添加隊(duì)列時(shí)都觸發(fā):

// 設(shè)置一個(gè)是否觸發(fā)了nextTick的標(biāo)識(shí)
let waiting = false
function pushQueue (watcher) {
 queue.add(watcher)
 if (!waiting) {
  // 保證nextTick只觸發(fā)一次
  waiting = true
  nextTick('一個(gè)能遍歷執(zhí)行queue的函數(shù)')
 }
}

完整代碼如下:

// 定義隊(duì)列
let queue = new Set()

// 供傳入nextTick中的執(zhí)行隊(duì)列的函數(shù)
function flushQueue () {
 queue.forEach(watcher => {
  watcher.run()
 })
 queue = new Set()
}

// nextTick
function nextTick(flushQueue) {
 Promise.resolve().then(flushQueue)
}

// 添加到隊(duì)列并調(diào)用nextTick
let waiting = false
function pushQueue (watcher) {
 queue.add(watcher)
 if (!waiting) {
  waiting = true
  nextTick(flushQueue)
 }
}

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“defineProperty和Proxy如何實(shí)現(xiàn)的響應(yīng)式系統(tǒng)”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!

分享文章:defineProperty和Proxy如何實(shí)現(xiàn)的響應(yīng)式系統(tǒng)
本文網(wǎng)址:http://sd-ha.com/article28/ggiocp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供面包屑導(dǎo)航、品牌網(wǎng)站制作搜索引擎優(yōu)化、建站公司電子商務(wù)、微信公眾號(hào)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站