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

淺談Vue數(shù)據(jù)響應(yīng)

 更新時間:2018年11月05日 09:44:28   作者:_邊城少年  
這篇文章主要介紹了淺談Vue數(shù)據(jù)響應(yīng),Vue的數(shù)據(jù)響應(yīng)主要是依賴了Object.defineProperty(),下面就具體來介紹一下如何實現(xiàn)數(shù)據(jù)響應(yīng)

Vue 中可以用 $watch 實例方法觀察一個字段,當(dāng)該字段的值發(fā)生變化時,會執(zhí)行指定的回調(diào)函數(shù)(即觀察者),實際上和 watch 選項作用相同。如下:

vm.$watch('box', () => {
  console.log('box變了')
})
vm.box = 'newValue' // 'box變了'

以上例切入,我想實現(xiàn)一個功能類似的方法 myWatch。

如何知道我觀察的屬性被修改了?

—— Object.defineProperty 方法

該方法可以為指定對象的指定屬性設(shè)置 getter-setter 函數(shù)對,通過這對 getter-setter 可以捕獲到對屬性的讀取和修改操作。示例如下:

const data = {
 box: 1
}
Object.defineProperty(data, 'box', {
 set () {
  console.log('修改了 box')
 },
 get () {
  console.log('讀取了 box')
 }
})

console.log(data.box) // '讀取了 box'
           // undefined
data.box = 2  // '修改了 box'
console.log(data.box) // '讀取了 box'
           // undefined

如此,便攔截到了對 box 屬性的修改和讀取操作。

但 res 為 undefined,data.box = 2 的修改操作也無效。

get 與 set 函數(shù)功能不健全

故修改如下:

const data = {
 box: 1
}
let value = data.box
Object.defineProperty(data, 'box', {
 set (newVal) {
  if (newVal === value) return
  value = newVal
  console.log('修改了 box')
 },
 get () {
  console.log('讀取了 box')
  return value
 }
})

console.log(data.box) // '讀取了 box'
           // 1

data.box = 2 // '修改了 box'
console.log(data.box) // '讀取了 box'
           // 2

有了這些, myWatch 方法便可實現(xiàn)如下:

const data = {
 box: 1
}
function myWatch(key, fn) {
 let value = data[key]
 Object.defineProperty(data, key, {
  set (newVal) {
   if (newVal === value) return
   value = newVal
   fn()
  },
  get () {
   return value
  }
 })
}
myWatch('box', () => {
  console.log('box變了')
})

data.box = 2 // 'box變了'

但存在一個問題,不能給同一屬性添加多個依賴(觀察者):

myWatch('box', () => {
 console.log('我是觀察者')
})
myWatch('box', () => {
 console.log('我是另一個觀察者')
})

data.box = 2 // '我是另一個觀察者'

后面的依賴(觀察者)會將前者覆蓋掉。

如何能夠添加多個依賴(觀察者)?

—— 定義一個數(shù)組,作為依賴收集器:

const data = {
 box: 1
}
const dep = []
function myWatch(key, fn) {
 dep.push(fn)
 let value = data[key]
 Object.defineProperty(data, key, {
  set (newVal) {
   if (newVal === value) return
   value = newVal
   dep.forEach((f) => {
    f()
   })
  },
  get () {
   return value
  }
 })
}

myWatch('box', () => {
 console.log('我是觀察者')
})
myWatch('box', () => {
 console.log('我是另一個觀察者')
})

data.box = 2 // '我是觀察者'
       // '我是另一個觀察者'

修改 data.box 后,兩個依賴(觀察者)都執(zhí)行了。

若上例 data 對象需新增兩個能夠響應(yīng)數(shù)據(jù)變化的屬性 foo bar:

const data = {
 box: 1,
 foo: 1,
 bar: 1
}

只需執(zhí)行以下代碼即可:

myWatch('foo', () => {
 console.log('我是foo的觀察者')
})
myWatch('bar', () => {
 console.log('我是bar的觀察者')
})

但問題是,不同屬性的依賴(觀察者)都被收集進了同一個 dep,修改任何一個屬性,都會觸發(fā)所有的依賴(觀察者):

data.box = 2 // '我是觀察者'
       // '我是另一個觀察者'
       // '我是foo的觀察者'
       // '我是bar的觀察者'

我想可以這樣解決:

const data = {
 box: 1,
 foo: 1,
 bar: 1
}
const dep = {}
function myWatch(key, fn) {
 if (!dep[key]) {
  dep[key] = [fn]
 } else {
  dep[key].push(fn)
 }
 let value = data[key]
 Object.defineProperty(data, key, {
  set (newVal) {
   if (newVal === value) return
   value = newVal
   dep[key].forEach((f) => {
    f()
   })
  },
  get () {
   return value
  }
 })
}

myWatch('box', () => {
 console.log('我是box的觀察者')
})
myWatch('box', () => {
 console.log('我是box的另一個觀察者')
})
myWatch('foo', () => {
 console.log('我是foo的觀察者')
})
myWatch('bar', () => {
 console.log('我是bar的觀察者')
})

data.box = 2 // '我是box的觀察者'
       // '我是box的另一個觀察者'
data.foo = 2 // '我是foo的觀察者'
data.bar = 2 // '我是bar的觀察者'

但實際上這樣更好些:

const data = {
 box: 1,
 foo: 1,
 bar: 1
}
let target = null
for (let key in data) {
 const dep = []
 let value = data[key]
 Object.defineProperty(data, key, {
  set (newVal) {
   if (newVal === value) return
   value = newVal
   dep.forEach(f => {
    f()
   })
  },
  get () {
   dep.push(target)
   return value
  }
 })
}
function myWatch(key, fn) {
 target = fn
 data[key]
}
myWatch('box', () => {
 console.log('我是box的觀察者')
})
myWatch('box', () => {
 console.log('我是box的另一個觀察者')
})
myWatch('foo', () => {
 console.log('我是foo的觀察者')
})
myWatch('bar', () => {
 console.log('我是bar的觀察者')
})

data.box = 2 // '我是box的觀察者'
       // '我是box的另一個觀察者'
data.foo = 2 // '我是foo的觀察者'
data.bar = 2 // '我是bar的觀察者'

聲明 target 全局變量作為依賴(觀察者)的中轉(zhuǎn)站,myWatch 函數(shù)執(zhí)行時用 target 緩存依賴,然后調(diào)用 data[key] 觸發(fā)對應(yīng)的 get 函數(shù)以收集依賴,set 函數(shù)被觸發(fā)時會將 dep 里的依賴(觀察者)都執(zhí)行一遍。這里的 get set 函數(shù)形成閉包引用了上面的 dep 常量,這樣一來,data 對象的每個屬性都有了對應(yīng)的依賴收集器。

且這一實現(xiàn)方式不需要通過 myWatch 函數(shù)顯式地將 data 里的屬性一一轉(zhuǎn)為訪問器屬性。

但運行以下代碼,會發(fā)現(xiàn)仍有問題:

console.log(data.box)
data.box = 2 // '我是box的觀察者'
       // '我是box的另一個觀察者'
       // '我是bar的觀察者'

四個 myWatch 執(zhí)行完之后 target 緩存的值變成了最后一個 myWatch 方法調(diào)用時所傳遞的依賴(觀察者),故執(zhí)行 console.log(data.box) 讀取 box 屬性的值時,會將最后緩存的依賴存入 box 屬性所對應(yīng)的依賴收集器,故而再修改 box 的值時,會打印出 '我是bar的觀察者'。

我想可以在每次收集完依賴之后,將全局變量 target 設(shè)置為空函數(shù)來解決這問題:

const data = {
 box: 1,
 foo: 1,
 bar: 1
}
let target = null
for (let key in data) {
 const dep = []
 let value = data[key]
 Object.defineProperty(data, key, {
  set (newVal) {
   if (newVal === value) return
   value = newVal
   dep.forEach(f => {
    f()
   })
  },
  get () {
   dep.push(target)
   target = () => {}
   return value
  }
 })
}
function myWatch(key, fn) {
 target = fn
 data[key]
}
myWatch('box', () => {
 console.log('我是box的觀察者')
})
myWatch('box', () => {
 console.log('我是box的另一個觀察者')
})
myWatch('foo', () => {
 console.log('我是foo的觀察者')
})
myWatch('bar', () => {
 console.log('我是bar的觀察者')
})

經(jīng)測無誤。

但開發(fā)過程中,還常碰到需觀測嵌套對象的情形:

const data = {
 box: {
  gift: 'book'
 }
}

這時,上述實現(xiàn)未能觀測到 gift 的修改,顯出不足。

如何進行深度觀測?

——遞歸

通過遞歸將各級屬性均轉(zhuǎn)為響應(yīng)式屬性即可:

const data = {
 box: {
  gift: 'book'
 }
}
let target = null
function walk(data) {
 for (let key in data) {
  const dep = []
  let value = data[key]
  if (Object.prototype.toString.call(value) === '[object Object]') {
   walk(value)
  }
  Object.defineProperty(data, key, {
   set (newVal) {
    if (newVal === value) return
    value = newVal
    dep.forEach(f => {
     f()
    })
   },
   get () {
    dep.push(target)
    target = () => {}
    return value
   }
  })
 }
}
walk(data)
function myWatch(key, fn) {
 target = fn
 data[key]
}

myWatch('box', () => {
 console.log('我是box的觀察者')
})
myWatch('box.gift', () => {
 console.log('我是gift的觀察者')
})

data.box = {gift: 'basketball'} // '我是box的觀察者'
data.box.gift = 'guitar'

這時 gift 雖已是訪問器屬性,但 myWatch 方法執(zhí)行時 data[box.gift] 未能觸發(fā)相應(yīng) getter 以收集依賴, data[box.gift] 訪問不到 gift 屬性,data[box][gift] 才可以,故 myWatch 須改寫如下:

function myWatch(exp, fn) {
 target = fn
 let pathArr,
   obj = data
 if (/\./.test(exp)) {
  pathArr = exp.split('.')
  pathArr.forEach(p => {
   obj = obj[p]
  })
  return
 }
 data[exp]
}

如果要讀取的字段包括 . ,那么按照 . 將其分為數(shù)組,然后使用循環(huán)讀取嵌套對象的屬性值。

這時執(zhí)行代碼后發(fā)現(xiàn),data.box.gift = 'guitar' 還是未能觸發(fā)相應(yīng)的依賴,即打印出 '我是gift的觀察者' 這句信息。調(diào)試之后找到問題:

myWatch('box.gift', () => {
 console.log('我是gift的觀察者')
})

執(zhí)行以上代碼時,pathArr 即 ['box', 'gift'],循環(huán)內(nèi) obj = obj[p] 實際上就是 obj = data[box],讀取了一次 box,觸發(fā)了 box 對應(yīng)的 getter,收集了依賴:

() => {
 console.log('我是gift的觀察者')
}

收集完將全局變量 target 置為空函數(shù),而后,循環(huán)繼續(xù)執(zhí)行,又讀取了 gift 的值,但這時,target 已是空函數(shù),導(dǎo)致屬性 gift 對應(yīng)的 getter 收集了一個“空依賴”,故,data.box.gift = 'guitar' 的操作不能觸發(fā)期望的依賴。

以上代碼有兩個問題:

  • 修改 box 會觸發(fā)“我是gift的觀察者”這一依賴
  • 修改 gift 未能觸發(fā)“我是gift的觀察者”的依賴

第一個問題,讀取 gift 時,必然經(jīng)歷讀取 box 的過程,故觸發(fā) box 對應(yīng)的 getter 無可避免,那么,box 對應(yīng) getter 收集 gift 的依賴也就無可避免。但想想也算合理,因為 box 修改時,隸屬于 box 的 gift 也算作修改,從這一點看,問題一也不算作問題,劃去。

第二個問題,我想可以這樣解決:

function myWatch(exp, fn) {
 let pathArr,
   obj = data
 if (/\./.test(exp)) {
  pathArr = exp.split('.')
  pathArr.forEach(p => {
   target = fn
   obj = obj[p]
  })
  return
 }
 target = fn
 data[exp]
}

data.box.gift = 'guitar' // '我是gift的觀察者'
data.box = {gift: 'basketball'} // '我是box的觀察者'
                // '我是gift的觀察者'

保證屬性讀取時 target = fn 即可。

那么:

const data = {
 box: {
  gift: 'book'
 }
}
let target = null
function walk(data) {
 for (let key in data) {
  const dep = []
  let value = data[key]
  if (Object.prototype.toString.call(value) === '[object Object]') {
   walk(value)
  }
  Object.defineProperty(data, key, {
   set (newVal) {
    if (newVal === value) return
    value = newVal
    dep.forEach(f => {
     f()
    })
   },
   get () {
    dep.push(target)
    target = () => {}
    return value
   }
  })
 }
}
walk(data)
function myWatch(exp, fn) {
 let pathArr,
   obj = data
 if (/\./.test(exp)) {
  pathArr = exp.split('.')
  pathArr.forEach(p => {
   target = fn
   obj = obj[p]
  })
  return
 }
 target = fn
 data[exp]
}

myWatch('box', () => {
 console.log('我是box的觀察者')
})
myWatch('box.gift', () => {
 console.log('我是gift的觀察者')
})

現(xiàn)在我想,假如我有以下數(shù)據(jù):

const data = {
 player: 'James Harden',
 team: 'Houston Rockets'
}

執(zhí)行以下代碼:

function render() {
 document.body.innerText = `The last season's MVP is ${data.player}, he's from ${data.team}`
}
render()
myWatch('player', render)
myWatch('team', render)

data.player = 'Kobe Bryant'
data.team = 'Los Angeles Lakers'

是不是就可以將數(shù)據(jù)映射到頁面,并響應(yīng)數(shù)據(jù)的變化?

執(zhí)行代碼發(fā)現(xiàn),data.player = 'Kobe Bryant' 報錯,究其原因,render 方法執(zhí)行時,會去獲取 data.player 和 data.team 的值,但此時,target 為 null,那么讀取 player 時對應(yīng)的依賴收集器 dep 便收集了 null,導(dǎo)致 player 的 setter 調(diào)用依賴時報錯。

那么我想,在 render 執(zhí)行時便主動去收集依賴,就不會導(dǎo)致 dep 里收集了 null。

細看 myWatch,這方法做的事情其實就是幫助 getter 收集依賴,它的第一個參數(shù)就是要訪問的屬性,要觸發(fā)誰的 getter,第二個參數(shù)是相應(yīng)要收集的依賴。

這么看來,render 方法既可以幫助 getter 收集依賴(render 執(zhí)行時會讀取 player team),而且它本身就是要收集的依賴。那么,我能不能修改一下 myWatch 的實現(xiàn),以支持這樣的寫法:

myWatch(render, render)

第一個參數(shù)作為函數(shù)執(zhí)行一下便有了之前第一個參數(shù)的作用,第二個參數(shù)還是需要被收集的依賴,嗯,想來合理。

那么,myWatch 改寫如下:

function myWatch(exp, fn) {
 target = fn
 if (typeof exp === 'function') {
  exp()
  return
 }
 let pathArr,
   obj = data
 if (/\./.test(exp)) {
  pathArr = exp.split('.')
  pathArr.forEach(p => {
   target = fn
   obj = obj[p]
  })
  return
 }
 data[exp]
}

但,對 team 的修改未能觸發(fā)頁面更新,想來因為 render 執(zhí)行讀取 player 收集依賴后 target 變?yōu)榭蘸瘮?shù),導(dǎo)致讀取 team 收集依賴時收集到了空函數(shù)。這里大家的依賴都是 render,故可將 target = () => {} 這句刪去。

myWatch 這樣實現(xiàn)還有個好處,假如 data 中有許多屬性都需要通過 render 渲染至頁面,一句 myWatch(render, render) 便可,無須如此這般繁復(fù):

myWatch('player', render)
myWatch('team', render)
myWatch('number', render)
myWatch('height', render)
...

那么最終:

const data = {
 player: 'James Harden',
 team: 'Houston Rockets'
}
let target = null
function walk(data) {
 for (let key in data) {
  const dep = []
  let value = data[key]
  if (Object.prototype.toString.call(value) === '[object Object]') {
   walk(value)
  }
  Object.defineProperty(data, key, {
   set (newVal) {
    if (newVal === value) return
    value = newVal
    dep.forEach(f => {
     f()
    })
   },
   get () {
    dep.push(target)
    return value
   }
  })
 }
}
walk(data)
function myWatch(exp, fn) {
 target = fn
 if (typeof exp === 'function') {
  exp()
  return
 }
 let pathArr,
   obj = data
 if (/\./.test(exp)) {
  pathArr = exp.split('.')
  pathArr.forEach(p => {
   target = fn
   obj = obj[p]
  })
  return
 }
 data[exp]
}
function render() {
 document.body.innerText = `The last season's MVP is ${data.player}, he's from ${data.team}`
}

myWatch(render, render)

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • vue 自動化路由實現(xiàn)代碼

    vue 自動化路由實現(xiàn)代碼

    這篇文章主要介紹了vue 自動化路由實現(xiàn)代碼,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-09-09
  • vue的路由映射問題及解決方案

    vue的路由映射問題及解決方案

    這篇文章主要介紹了vue的路由映射問題及解決方案,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-10-10
  • 淺談vue-cli5關(guān)于yarn的一個小坑

    淺談vue-cli5關(guān)于yarn的一個小坑

    本文主要介紹了vue-cli5關(guān)于yarn的一個小坑,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • 使用Vue構(gòu)建可重用的分頁組件

    使用Vue構(gòu)建可重用的分頁組件

    分頁組件在web項目中是十分常見的組件,讓我們使用Vue構(gòu)建可重用的分頁組件,關(guān)于基本結(jié)構(gòu)和相關(guān)事件監(jiān)聽大家參考下本文
    2018-03-03
  • 面試官常問Vue和React區(qū)別解析

    面試官常問Vue和React區(qū)別解析

    Vue和React都是流行的前端框架,它們有很多相似之處,如組件化和使用虛擬DOM,但是,它們在設(shè)計理念、組件存在形式、diff優(yōu)化和中文文檔方面存在一些不同,這篇文章給大家介紹面試官常問Vue和React區(qū)別,感興趣的朋友一起看看吧
    2025-02-02
  • 詳解Vue組件插槽的使用以及調(diào)用組件內(nèi)的方法

    詳解Vue組件插槽的使用以及調(diào)用組件內(nèi)的方法

    在本篇文章里我們給大家分享了Vue組件插槽的使用以及調(diào)用組件內(nèi)的方法相關(guān)知識點,有需要的朋友們參考下。
    2018-11-11
  • vue中iframe使用以及結(jié)合postMessage實現(xiàn)跨域通信

    vue中iframe使用以及結(jié)合postMessage實現(xiàn)跨域通信

    這篇文章主要介紹了vue中iframe使用以及結(jié)合postMessage實現(xiàn)跨域通信方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • vue的指令和插值問題匯總

    vue的指令和插值問題匯總

    Vue?是一套用于構(gòu)建用戶界面的漸進式框架,Vue?的核心庫只關(guān)注視圖層,不僅易于上手,還便于與第三方庫或既有項目整合,這篇文章主要介紹了vue的指令和插值總結(jié),需要的朋友可以參考下
    2022-10-10
  • vue v-for出來的列表,點擊某個li使得當(dāng)前被點擊的li字體變紅操作

    vue v-for出來的列表,點擊某個li使得當(dāng)前被點擊的li字體變紅操作

    這篇文章主要介紹了vue v-for出來的列表,點擊某個li使得當(dāng)前被點擊的li字體變紅操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-07-07
  • 詳解如何在 vue 項目里正確地引用 jquery 和 jquery-ui的插件

    詳解如何在 vue 項目里正確地引用 jquery 和 jquery-ui的插件

    本篇文章主要介紹了詳解如何在 vue 項目里正確地引用 jquery 和 jquery-ui的插件,具有一定的參考價值,有興趣的可以了解一下
    2017-06-06

最新評論

泰州市| 阿勒泰市| 吴旗县| 昭通市| 和林格尔县| 原平市| 梨树县| 锡林郭勒盟| 崇仁县| 永兴县| 芜湖市| 酉阳| 且末县| 惠水县| 达州市| 饶平县| 彭泽县| 长乐市| 浦东新区| 佳木斯市| 屯留县| 泸水县| 青州市| 五大连池市| 镶黄旗| 始兴县| 门源| 莱阳市| 深州市| 辉县市| 同仁县| 林口县| 略阳县| 涟水县| 庆城县| 宜城市| 若尔盖县| 盐山县| 龙江县| 靖安县| 广南县|