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

首頁 > 編程 > JavaScript > 正文

詳解Vue源碼學習之雙向綁定

2019-11-19 11:49:14
字體:
來源:轉載
供稿:網友

原理

當你把一個普通的 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/getexport 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中主要清除上一輪中的依賴在新一輪中沒有重新收集的,也就是數據刷新后某些數據不再被渲染出來了,例如:

<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>

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

總結

依賴收集其實就是收集每個數據被哪些Watcher(渲染Watcher、computedWatcher等)所引用,當這些數據更新時,就去通知依賴它的Watcher去更新。

以上所述是小編給大家介紹的Vue源碼學習之雙向綁定詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對武林網網站的支持!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美理论电影在线播放| 国产在线98福利播放视频| 日韩国产中文字幕| 成人网中文字幕| 欧美一级高清免费| 欧美性猛交视频| 日韩av在线天堂网| 精品国产欧美成人夜夜嗨| 国产精品国产三级国产aⅴ浪潮| 日av在线播放中文不卡| 日本免费久久高清视频| 欧美在线xxx| 欧美黄色片视频| 久久资源免费视频| 国产一区二区三区在线播放免费观看| 成人网欧美在线视频| 韩国19禁主播vip福利视频| 欧美成人免费全部| 亚洲人在线观看| 亚洲精品女av网站| 日本中文字幕不卡免费| 欧美黄色片在线观看| 久久夜色精品国产欧美乱| 国产精品久久久久久久久久三级| 久久久久在线观看| 国产精品高潮视频| 91精品国产成人| 国产精品9999| 欧美乱妇高清无乱码| 国产suv精品一区二区三区88区| 欧美中文字幕在线观看| 中文字幕亚洲欧美一区二区三区| 久久久久久久久久久网站| 日韩精品日韩在线观看| 精品久久久久久久久久久久久久| 欧美成人全部免费| 中文字幕在线看视频国产欧美| 亚洲成人av中文字幕| 欧美电影免费观看大全| 国产精品久久激情| 97香蕉超级碰碰久久免费的优势| 亚洲免费影视第一页| 国产成人黄色av| 亚洲激情视频在线观看| 欧美日韩成人网| 精品自拍视频在线观看| 91美女福利视频高清| 岛国视频午夜一区免费在线观看| 亚洲性69xxxbbb| 日本欧美中文字幕| 97在线日本国产| 国产一区欧美二区三区| 中文字幕在线看视频国产欧美在线看完整| 亚洲激情免费观看| 国产成人精品免费久久久久| 久久久久久久一区二区| 韩日精品中文字幕| 91免费欧美精品| 欧美老肥婆性猛交视频| 久久网福利资源网站| 欧美日韩一区二区三区在线免费观看| 久久久久久久国产精品视频| 午夜精品三级视频福利| 国产亚洲精品va在线观看| 久久久精品国产网站| 国产精品美女999| 97视频网站入口| 78m国产成人精品视频| 久久中文字幕国产| 中文日韩在线观看| 亚洲人在线视频| 久久影院中文字幕| www.日韩免费| 96sao精品视频在线观看| 中文字幕一区二区精品| 久久久91精品| 懂色av影视一区二区三区| 操91在线视频| 欧美在线视频在线播放完整版免费观看| 亚洲成av人乱码色午夜| 久久久欧美一区二区| 亚洲第一天堂av| 国产亚洲精品综合一区91| 97在线视频免费| 久久在线精品视频| 国产精品视频999| 国产精品欧美在线| 亚洲尤物视频网| 中文字幕日韩av综合精品| 97涩涩爰在线观看亚洲| 久久久久中文字幕2018| 亚洲色图13p| 欧美激情亚洲综合一区| 国产日韩欧美在线观看| 日韩精品视频免费专区在线播放| 日韩亚洲精品视频| 日韩av综合中文字幕| 亚洲欧洲在线看| 欧美精品videofree1080p| 欧美日韩国产精品一区| 日韩精品视频免费专区在线播放| 日本成人黄色片| 久久精品视频在线观看| 日韩中文av在线| 欧美国产一区二区三区| 欧美重口另类videos人妖| 日韩免费看的电影电视剧大全| 亚洲人成绝费网站色www| 91精品国产沙发| 欧美亚洲一区在线| 欧美激情精品久久久久久| 欧美—级a级欧美特级ar全黄| 色久欧美在线视频观看| xxxxx91麻豆| 欧美精品电影在线| 亚洲成人精品久久久| 在线播放日韩欧美| 伊人一区二区三区久久精品| 91sao在线观看国产| 久久综合免费视频| 国产网站欧美日韩免费精品在线观看| …久久精品99久久香蕉国产| 色偷偷88888欧美精品久久久| 成人美女av在线直播| 亚洲色图欧美制服丝袜另类第一页| 日韩欧美在线观看| 中文字幕国产精品| 懂色av中文一区二区三区天美| 国产视频在线一区二区| 国产乱人伦真实精品视频| 色中色综合影院手机版在线观看| 91精品久久久久久久久不口人| 国产一区二区三区久久精品| 国产综合色香蕉精品| 久久影视电视剧免费网站清宫辞电视| 国产福利精品视频| 亚洲自拍在线观看| 亚洲一区二区中文| www.久久久久| 亚洲一区二区在线播放| 成人av番号网| 亚洲一区二区少妇| 欧美裸体xxxx极品少妇软件| 亚洲人成网站色ww在线| 成人欧美一区二区三区黑人孕妇| 欧美大片大片在线播放| 国产精品视频网站| 欧美丰满少妇xxxx| 国产成人97精品免费看片| 久久夜精品va视频免费观看| 国产女精品视频网站免费| 亚洲天堂精品在线| 日本精品在线视频| 日韩国产欧美精品在线| 美女久久久久久久| 国产精品一区二区久久精品| 欧美激情在线观看视频| 一本色道久久88精品综合| 久久精品国产清自在天天线| 欧美激情国内偷拍| 91精品国产乱码久久久久久蜜臀| 久久色精品视频| 亚洲人午夜精品| 国产美女被下药99|