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

詳解Vue源碼學(xué)習(xí)之雙向綁定

 更新時(shí)間:2019年04月10日 15:47:26   作者:走音  
這篇文章主要介紹了Vue源碼學(xué)習(xí)之雙向綁定,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

原理

當(dāng)你把一個(gè)普通的 JavaScript 對(duì)象傳給 Vue 實(shí)例的 data 選項(xiàng),Vue 將遍歷此對(duì)象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉(zhuǎn)為 getter/setter。Object.defineProperty 是 ES5 中一個(gè)無(wú)法 shim 的特性,這也就是為什么 Vue 不支持 IE8 以及更低版本瀏覽器。

上面那段話是Vue官方文檔中截取的,可以看到是使用Object.defineProperty實(shí)現(xiàn)對(duì)數(shù)據(jù)改變的監(jiān)聽。Vue主要使用了觀察者模式來(lái)實(shí)現(xiàn)數(shù)據(jù)與視圖的雙向綁定。

function initData(vm) { //將data上數(shù)據(jù)復(fù)制到_data并遍歷所有屬性添加代理
 vm._data = vm.$options.data;
 const keys = Object.keys(vm._data); 
 let i = keys.length;
 while(i--) { 
  const key = keys[i];
  proxy(vm, `_data`, key);
 }
 observe(data, true /* asRootData */) //對(duì)data進(jìn)行監(jiān)聽
}

在第一篇數(shù)據(jù)初始化中,執(zhí)行new Vue()操作后會(huì)執(zhí)行initData()去初始化用戶傳入的data,最后一步操作就是為data添加響應(yīng)式。

實(shí)現(xiàn)

在Vue內(nèi)部存在三個(gè)對(duì)象:Observer、Dep、Watcher,這也是實(shí)現(xiàn)響應(yīng)式的核心。

Observer

Observer對(duì)象將data中所有的屬性轉(zhuǎn)為getter/setter形式,以下是簡(jiǎn)化版代碼,詳細(xì)代碼請(qǐng)看這里。

export function observe (value) {
 //遞歸子屬性時(shí)的判斷
 if (!isObject(value) || value instanceof VNode) {
  return
 }
 ...
 ob = new Observer(value)
}
export class Observer {
 constructor (value) {
  ... //此處省略對(duì)數(shù)組的處理
  this.walk(value)
 }

 walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
   defineReactive(obj, keys[i]) //為每個(gè)屬性創(chuàng)建setter/getter
  }
 }
 ...
}

//設(shè)置set/get
export function defineReactive (
 obj: Object,
 key: string,
 val: any
) {
 //利用閉包存儲(chǔ)每個(gè)屬性關(guān)聯(lián)的watcher隊(duì)列,當(dāng)setter觸發(fā)時(shí)依然能訪問(wèn)到
 const dep = new Dep()
 ...
 //如果屬性為對(duì)象也創(chuàng)建相應(yīng)observer
 let childOb = observe(val)
 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
   if (Dep.target) {
    dep.depend() //將當(dāng)前dep傳到對(duì)應(yīng)watcher中再執(zhí)行watcher.addDep將watcher添加到當(dāng)前dep.subs中
    if (childOb) { //如果屬性是對(duì)象則繼續(xù)收集依賴
     childOb.dep.depend()
     ...
    }
   }
   return value
  },
  set: function reactiveSetter (newVal) {
   ...
   childOb = observe(newVal) //如果設(shè)置的新值是對(duì)象,則為其創(chuàng)建observe
   dep.notify() //通知隊(duì)列中的watcher進(jìn)行更新
  }
 })
}

創(chuàng)建Observer對(duì)象時(shí),為data的每個(gè)屬性都執(zhí)行了一遍defineReactive方法,如果當(dāng)前屬性為對(duì)象,則通過(guò)遞歸進(jìn)行深度遍歷。該方法中創(chuàng)建了一個(gè)Dep實(shí)例,每一個(gè)屬性都有一個(gè)與之對(duì)應(yīng)的dep,存儲(chǔ)所有的依賴。然后為屬性設(shè)置setter/getter,在getter時(shí)收集依賴,setter時(shí)派發(fā)更新。這里收集依賴不直接使用addSub是為了能讓W(xué)atcher創(chuàng)建時(shí)自動(dòng)將自己添加到dep.subs中,這樣只有當(dāng)數(shù)據(jù)被訪問(wèn)時(shí)才會(huì)進(jìn)行依賴收集,可以避免一些不必要的依賴收集。

Dep

Dep就是一個(gè)發(fā)布者,負(fù)責(zé)收集依賴,當(dāng)數(shù)據(jù)更新是去通知訂閱者(watcher)。源碼地址

export default class Dep {
 static target: ?Watcher; //指向當(dāng)前watcher
 constructor () {
  this.subs = []
 }
 //添加watcher
 addSub (sub: Watcher) {
  this.subs.push(sub)
 }
 //移除watcher
 removeSub (sub: Watcher) {
  remove(this.subs, sub)
 }
 //通過(guò)watcher將自身添加到dep中
 depend () {
  if (Dep.target) {
   Dep.target.addDep(this)
  }
 }
 //派發(fā)更新信息
 notify () {
  ...
  for (let i = 0, l = subs.length; i < l; i++) {
   subs[i].update()
  }
 }
}

Watcher

源碼地址

//解析表達(dá)式(a.b),返回一個(gè)函數(shù)
export function parsePath (path: string): any {
 if (bailRE.test(path)) {
  return
 }
 const segments = path.split('.')
 return function (obj) {
  for (let i = 0; i < segments.length; i++) {
   if (!obj) return
   obj = obj[segments[i]]  //遍歷得到表達(dá)式所代表的屬性
  }
  return obj
 }
}
export default class Watcher {
 constructor (
  vm: Component,
  expOrFn: string | Function,
  cb: Function,
  options?: ?Object,
  isRenderWatcher?: boolean
 ) {
  this.vm = vm
  if (isRenderWatcher) {
   vm._watcher = this
  } 
  //對(duì)創(chuàng)建的watcher進(jìn)行收集,destroy時(shí)對(duì)這些watcher進(jìn)行銷毀
  vm._watchers.push(this)
  // options
  if (options) {
   ...
   this.before = options.before
  }
  ...
  //上一輪收集的依賴集合Dep以及對(duì)應(yīng)的id
  this.deps = []
  this.depIds = new Set()
  //新收集的依賴集合Dep以及對(duì)應(yīng)的id
  this.newDeps = []
  this.newDepIds = new Set()
  this.expression = process.env.NODE_ENV !== 'production'
   ? expOrFn.toString()
   : ''
  // parse expression for getter
  if (typeof expOrFn === 'function') {
   this.getter = expOrFn
  } else {
   this.getter = parsePath(expOrFn)
   ...
  }
  ...
  this.value = this.get()
 }

 /** * Evaluate the getter, and re-collect dependencies. */
 get () {
  pushTarget(this)
  let value
  const vm = this.vm
  try {
   value = this.getter.call(vm, vm)
  } catch (e) {
   if (this.user) {
    handleError(e, vm, `getter for watcher "${this.expression}"`)
   } else {
    throw e
   }
  } finally {
   // "touch" every property so they are all tracked as
   // dependencies for deep watching
   if (this.deep) {
    traverse(value)
   }
   popTarget()
   this.cleanupDeps() //清空上一輪的依賴
  }
  return value
 }

 /** * Add a dependency to this directive. */
 addDep (dep: Dep) {
  const id = dep.id
  if (!this.newDepIds.has(id)) { //同一個(gè)數(shù)據(jù)只收集一次
   this.newDepIds.add(id)
   this.newDeps.push(dep)
   if (!this.depIds.has(id)) {
    dep.addSub(this)
   }
  }
 }

 //每輪收集結(jié)束后去除掉上輪收集中不需要跟蹤的依賴
 cleanupDeps () {
  let i = this.deps.length
  while (i--) {
   const dep = this.deps[i]
   if (!this.newDepIds.has(dep.id)) {
    dep.removeSub(this)
   }
  }
  let tmp = this.depIds
  this.depIds = this.newDepIds
  this.newDepIds = tmp
  this.newDepIds.clear()
  tmp = this.deps
  this.deps = this.newDeps
  this.newDeps = tmp
  this.newDeps.length = 0
 },
 update () {
  ...
  //經(jīng)過(guò)一些優(yōu)化處理后,最終執(zhí)行this.get
  this.get();
 }
 // ...
}

依賴收集的觸發(fā)是在執(zhí)行render之前,會(huì)創(chuàng)建一個(gè)渲染W(wǎng)atcher:

updateComponent = () => {
 vm._update(vm._render(), hydrating) //執(zhí)行render生成VNode并更新dom
}
new Watcher(vm, updateComponent, noop, {
 before () {
  if (vm._isMounted) {
   callHook(vm, 'beforeUpdate')
  }
 }
}, true /* isRenderWatcher */)

在渲染W(wǎng)atcher創(chuàng)建時(shí)會(huì)將Dep.target指向自身并觸發(fā)updateComponent也就是執(zhí)行_render生成VNode并執(zhí)行_update將VNode渲染成真實(shí)DOM,在render過(guò)程中會(huì)對(duì)模板進(jìn)行編譯,此時(shí)就會(huì)對(duì)data進(jìn)行訪問(wèn)從而觸發(fā)getter,由于此時(shí)Dep.target已經(jīng)指向了渲染W(wǎng)atcher,接著渲染W(wǎng)atcher會(huì)執(zhí)行自身的addDep,做一些去重判斷然后執(zhí)行dep.addSub(this)將自身push到屬性對(duì)應(yīng)的dep.subs中,同一個(gè)屬性只會(huì)被添加一次,表示數(shù)據(jù)在當(dāng)前Watcher中被引用。

當(dāng)_render結(jié)束后,會(huì)執(zhí)行popTarget(),將當(dāng)前Dep.target回退到上一輪的指,最終又回到了null,也就是所有收集已完畢。之后執(zhí)行cleanupDeps()將上一輪不需要的依賴清除。當(dāng)數(shù)據(jù)變化是,觸發(fā)setter,執(zhí)行對(duì)應(yīng)Watcher的update屬性,去執(zhí)行g(shù)et方法又重新將Dep.target指向當(dāng)前執(zhí)行的Watcher觸發(fā)該Watcher的更新。

這里可以看到有deps,newDeps兩個(gè)依賴表,也就是上一輪的依賴和最新的依賴,這兩個(gè)依賴表主要是用來(lái)做依賴清除的。但在addDep中可以看到if (!this.newDepIds.has(id))已經(jīng)對(duì)收集的依賴進(jìn)行了唯一性判斷,不收集重復(fù)的數(shù)據(jù)依賴。為何又要在cleanupDeps中再作一次判斷呢?

while (i--) {
   const dep = this.deps[i]
   if (!this.newDepIds.has(dep.id)) {
    dep.removeSub(this)
   }
  }
  let tmp = this.depIds
  this.depIds = this.newDepIds
  this.newDepIds = tmp
  this.newDepIds.clear()
  tmp = this.deps
  this.deps = this.newDeps
  this.newDeps = tmp
  this.newDeps.length = 0

在cleanupDeps中主要清除上一輪中的依賴在新一輪中沒有重新收集的,也就是數(shù)據(jù)刷新后某些數(shù)據(jù)不再被渲染出來(lái)了,例如:

<body>
 <div id="app">
  <div v-if='flag'> </div>   
  <div v-else> </div> 
  <button @click="msg1 += '1'">change</button>   
  <button @click="flag = !flag">toggle</button>  
 </div> 
  <script type="text/javascript">
  var vm = new Vue({
   el: '#app',
   data: {
    flag: true,
    msg1: 'msg1',
    msg2: 'msg2'
   }
  })
  </script> 
</body>

每次點(diǎn)擊change,msg1都會(huì)拼接一個(gè)1,此時(shí)就會(huì)觸發(fā)重新渲染。當(dāng)我們點(diǎn)擊toggle時(shí),由于flag改變,msg1不再被渲染,但當(dāng)我們點(diǎn)擊change時(shí),msg1發(fā)生了變化,但卻沒有觸發(fā)重新渲染,這就是cleanupDeps起的作用。如果去除掉cleanupDeps這個(gè)步驟,只是能防止添加相同的依賴,但是數(shù)據(jù)每次更新都會(huì)觸發(fā)重新渲染,又去重新收集依賴。這個(gè)例子中,toggle后,重新收集的依賴中并沒有msg1,因?yàn)樗恍枰伙@示,但是由于設(shè)置了setter,此時(shí)去改變msg1依然會(huì)觸發(fā)setter,如果沒有執(zhí)行cleanupDeps,那么msg1的依賴依然存在依賴表里,又會(huì)去觸發(fā)重新渲染,這是不合理的,所以需要每次依賴收集完畢后清除掉一些不需要的依賴。

總結(jié)

依賴收集其實(shí)就是收集每個(gè)數(shù)據(jù)被哪些Watcher(渲染W(wǎng)atcher、computedWatcher等)所引用,當(dāng)這些數(shù)據(jù)更新時(shí),就去通知依賴它的Watcher去更新。

以上所述是小編給大家介紹的Vue源碼學(xué)習(xí)之雙向綁定詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

  • Vue中vue-router路由使用示例詳解

    Vue中vue-router路由使用示例詳解

    Vue Router是Vue提供的路由管理器,將組件與路由一一對(duì)應(yīng)起來(lái),這種對(duì)應(yīng)關(guān)系就路由,這篇文章主要介紹了Vue中vue-router路由使用,需要的朋友可以參考下
    2024-06-06
  • vue項(xiàng)目中axios配置方式(代理配置)

    vue項(xiàng)目中axios配置方式(代理配置)

    這篇文章主要介紹了vue項(xiàng)目中axios配置方式(代理配置),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • vue3項(xiàng)目中使用tinymce的方法

    vue3項(xiàng)目中使用tinymce的方法

    這篇文章主要介紹了vue3使用tinymce的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • vue修改數(shù)據(jù)視圖更新原理學(xué)習(xí)

    vue修改數(shù)據(jù)視圖更新原理學(xué)習(xí)

    這篇文章主要為大家介紹了vue修改數(shù)據(jù)視圖更新原理學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • 深入了解Vue之組件的生命周期流程

    深入了解Vue之組件的生命周期流程

    每個(gè)Vue實(shí)例在創(chuàng)建時(shí)都要經(jīng)過(guò)一系列初始化,?例如設(shè)置數(shù)據(jù)監(jiān)聽、編譯模板、將實(shí)例掛載到DOM并在數(shù)據(jù)變化時(shí)更新DOM等,同時(shí),?也會(huì)運(yùn)行一些叫作生命周期鉤子的函數(shù),接下來(lái)讓我們一起來(lái)探索Vue實(shí)例被創(chuàng)建時(shí)都經(jīng)歷了什么,感興趣的同學(xué)跟著小編一起來(lái)探討吧
    2023-05-05
  • 詳解Vue中生命周期鉤子函數(shù)的使用示例

    詳解Vue中生命周期鉤子函數(shù)的使用示例

    這篇文章主要為大家詳細(xì)介紹了Vue中常見的生命周期鉤子函數(shù)的使用,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-11-11
  • Vue Axios異步與數(shù)據(jù)類型轉(zhuǎn)換問(wèn)題淺析

    Vue Axios異步與數(shù)據(jù)類型轉(zhuǎn)換問(wèn)題淺析

    總的來(lái)說(shuō)這并不是一道難題,那為什么要拿出這道題介紹?拿出這道題真正想要傳達(dá)的是解題的思路,以及不斷優(yōu)化探尋最優(yōu)解的過(guò)程。希望通過(guò)這道題能給你帶來(lái)一種解題優(yōu)化的思路,Axios是一個(gè)開源的可以用在瀏覽器端和Node JS的異步通信框架,主要作用就是實(shí)現(xiàn)AJAX異步通信
    2023-01-01
  • vue實(shí)現(xiàn)監(jiān)聽localstorage值變化

    vue實(shí)現(xiàn)監(jiān)聽localstorage值變化

    這篇文章主要介紹了vue實(shí)現(xiàn)監(jiān)聽localstorage值變化,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 5個(gè)Vue3阻止事件冒泡的方法

    5個(gè)Vue3阻止事件冒泡的方法

    在?Vue3?項(xiàng)目開發(fā)中,事件冒泡經(jīng)常會(huì)導(dǎo)致一些意想不到的問(wèn)題,本文為大家整理了五個(gè)Vue3中阻止事件冒泡的方法,希望對(duì)大家有一定的幫助
    2024-11-11
  • Vue實(shí)現(xiàn)文字上下滾動(dòng)動(dòng)畫的示例代碼

    Vue實(shí)現(xiàn)文字上下滾動(dòng)動(dòng)畫的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何使用Vue實(shí)現(xiàn)超酷文字上下滾動(dòng)動(dòng)畫,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下
    2024-03-03

最新評(píng)論

黎川县| 蒙山县| 自贡市| 永州市| 墨脱县| 东安县| 五寨县| 怀宁县| 雅江县| 搜索| 淮安市| 普宁市| 祁门县| 星座| 赤峰市| 绥宁县| 西丰县| 洛阳市| 禄劝| 岢岚县| 越西县| 盐源县| 永定县| 乌什县| 分宜县| 大庆市| 鞍山市| 会东县| 嘉祥县| 高邑县| 普宁市| 南岸区| 洪泽县| 商河县| 定结县| 岚皋县| 福建省| 门头沟区| 武胜县| 旬阳县| 江山市|