老熟女激烈的高潮_日韩一级黄色录像_亚洲1区2区3区视频_精品少妇一区二区三区在线播放_国产欧美日产久久_午夜福利精品导航凹凸

重慶分公司,新征程啟航

為企業提供網站建設、域名注冊、服務器等服務

Vue如何實現雙向綁定

這篇文章主要介紹Vue如何實現雙向綁定,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

成都網站建設哪家好,找成都創新互聯!專注于網頁設計、成都網站建設、微信開發、小程序設計、集團企業網站制作等服務項目。核心團隊均擁有互聯網行業多年經驗,服務眾多知名企業客戶;涵蓋的客戶類型包括:成都假山制作等眾多領域,積累了大量豐富的經驗,同時也獲得了客戶的一致贊譽!

原理

當你把一個普通的 JavaScript 對象傳給 Vue 實例的 data 選項,Vue 將遍歷此對象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉為 getter/setter。Object.defineProperty 是 ES5 中一個無法 shim 的特性,這也就是為什么 Vue 不支持 IE8 以及更低版本瀏覽器。

上面那段話是Vue官方文檔中截取的,可以看到是使用Object.defineProperty實現對數據改變的監聽。Vue主要使用了觀察者模式來實現數據與視圖的雙向綁定。

function initData(vm) { //將data上數據復制到_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 */) //對data進行監聽
}

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

實現

在Vue內部存在三個對象:Observer、Dep、Watcher,這也是實現響應式的核心。

Observer

Observer對象將data中所有的屬性轉為getter/setter形式,以下是簡化版代碼,詳細代碼請看這里。

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

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

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

創建Observer對象時,為data的每個屬性都執行了一遍defineReactive方法,如果當前屬性為對象,則通過遞歸進行深度遍歷。該方法中創建了一個Dep實例,每一個屬性都有一個與之對應的dep,存儲所有的依賴。然后為屬性設置setter/getter,在getter時收集依賴,setter時派發更新。這里收集依賴不直接使用addSub是為了能讓Watcher創建時自動將自己添加到dep.subs中,這樣只有當數據被訪問時才會進行依賴收集,可以避免一些不必要的依賴收集。

Dep

Dep就是一個發布者,負責收集依賴,當數據更新是去通知訂閱者(watcher)。源碼地址

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

Watcher

源碼地址

//解析表達式(a.b),返回一個函數
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]]  //遍歷得到表達式所代表的屬性
  }
  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
  } 
  //對創建的watcher進行收集,destroy時對這些watcher進行銷毀
  vm._watchers.push(this)
  // options
  if (options) {
   ...
   this.before = options.before
  }
  ...
  //上一輪收集的依賴集合Dep以及對應的id
  this.deps = []
  this.depIds = new Set()
  //新收集的依賴集合Dep以及對應的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)) { //同一個數據只收集一次
   this.newDepIds.add(id)
   this.newDeps.push(dep)
   if (!this.depIds.has(id)) {
    dep.addSub(this)
   }
  }
 }

 //每輪收集結束后去除掉上輪收集中不需要跟蹤的依賴
 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 () {
  ...
  //經過一些優化處理后,最終執行this.get
  this.get();
 }
 // ...
}

依賴收集的觸發是在執行render之前,會創建一個渲染Watcher:

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

在渲染Watcher創建時會將Dep.target指向自身并觸發updateComponent也就是執行_render生成VNode并執行_update將VNode渲染成真實DOM,在render過程中會對模板進行編譯,此時就會對data進行訪問從而觸發getter,由于此時Dep.target已經指向了渲染Watcher,接著渲染Watcher會執行自身的addDep,做一些去重判斷然后執行dep.addSub(this)將自身push到屬性對應的dep.subs中,同一個屬性只會被添加一次,表示數據在當前Watcher中被引用。

當_render結束后,會執行popTarget(),將當前Dep.target回退到上一輪的指,最終又回到了null,也就是所有收集已完畢。之后執行cleanupDeps()將上一輪不需要的依賴清除。當數據變化是,觸發setter,執行對應Watcher的update屬性,去執行get方法又重新將Dep.target指向當前執行的Watcher觸發該Watcher的更新。

這里可以看到有deps,newDeps兩個依賴表,也就是上一輪的依賴和最新的依賴,這兩個依賴表主要是用來做依賴清除的。但在addDep中可以看到if (!this.newDepIds.has(id))已經對收集的依賴進行了唯一性判斷,不收集重復的數據依賴。為何又要在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中主要清除上一輪中的依賴在新一輪中沒有重新收集的,也就是數據刷新后某些數據不再被渲染出來了,例如:


 
   
       
    change      toggle    
       var vm = new Vue({    el: '#app',    data: {     flag: true,     msg1: 'msg1',     msg2: 'msg2'    }   })    

每次點擊change,msg1都會拼接一個1,此時就會觸發重新渲染。當我們點擊toggle時,由于flag改變,msg1不再被渲染,但當我們點擊change時,msg1發生了變化,但卻沒有觸發重新渲染,這就是cleanupDeps起的作用。如果去除掉cleanupDeps這個步驟,只是能防止添加相同的依賴,但是數據每次更新都會觸發重新渲染,又去重新收集依賴。這個例子中,toggle后,重新收集的依賴中并沒有msg1,因為它不需要被顯示,但是由于設置了setter,此時去改變msg1依然會觸發setter,如果沒有執行cleanupDeps,那么msg1的依賴依然存在依賴表里,又會去觸發重新渲染,這是不合理的,所以需要每次依賴收集完畢后清除掉一些不需要的依賴。

以上是“Vue如何實現雙向綁定”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注創新互聯行業資訊頻道!


網頁題目:Vue如何實現雙向綁定
網頁地址:http://www.xueling.net.cn/article/jjocse.html

其他資訊

在線咨詢
服務熱線
服務熱線:028-86922220
TOP
主站蜘蛛池模板: 久久久久极品 | 秀人顶级模特尤妮丝的最新视频 | 成年女人免费视频播放体验区 | 国产成人无码短视频 | 午夜精品福利一区二区三区蜜桃 | 国产精品国产三级国产普通 | 日韩亚洲欧美一区二区 | a点w片 | 亚洲AV成人无码久久精品老人 | 美女乱子伦高潮在线观看完整片 | 香蕉九九九 | 亚洲精品一区人人爽 | 日日射夜夜 | 亚洲精品高潮久久久久久久 | 少妇自慰浓密的p毛 | 亚洲欧美一区二区久久 | jk自慰到不停喷水 | 午夜爽爽爽男女免费观看影院 | 人av在线 | 免费在线亚洲 | a毛片在线观看 | 国产精品一级 | 日韩一级免费毛片 | 美州a亚洲一视本频v色道 | 99在线热播| 日韩在线视频观看免费网站 | 国产99精品 | 日韩精品精品 | 成人开心激情 | 中文字幕永久视频 | 一级黄色片免费播放 | 乱人伦中文字幕成人网站在线 | 亚洲搞av | 99国产精品久久久久久久 | 亚洲午夜久久久影院 | 亚洲国产精品va在线看黑人动漫 | 精品女同一区二区三区在线观看 | 久久国产亚洲精品 | 中文字幕婷婷日韩欧美亚洲 | 黑人巨大欧美一区二区视频 | 国产A级毛片久久影院 |