欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

面試官問你Vue2的響應(yīng)式原理該如何回答?

 更新時間:2022年12月13日 15:33:13   作者:清風(fēng)丶  
可能很多小伙伴之前都了解過?Vue2實現(xiàn)響應(yīng)式的核心是利用了ES5的Object.defineProperty?但是面對面試官時如果只知道一些模糊的概念。只有深入底層了解響應(yīng)式的原理,才能在關(guān)鍵時刻對答如流,本文就來和大家詳細聊聊,感興趣的可以收藏一下

前言

可能很多小伙伴之前都了解過 Vue2實現(xiàn)響應(yīng)式的核心是利用了ES5的Object.defineProperty 但是面對面試官時如果只知道一些模糊的概念,回答肯定是虎頭蛇尾的,只有深入底層了解響應(yīng)式的原理,才能在關(guān)鍵時刻對答如流,百毒不侵。

響應(yīng)式對象

Object.defineProperty方法的官方解釋是可以直接在一個對象上定義一個新屬性或者修改一個對象的現(xiàn)有屬性

let object1 = {};

Object.defineProperty(object1, 'property1', {
  value: 42,
});
console.log(object1);
//{property1: 42}

經(jīng)過Object.defineProperty定義后,object1就有了一個property1屬性

并且通過這種方式能為屬性添加getset方法,

當(dāng)一個對象的屬性都擁有g(shù)et和set方法時,就可以稱這個對象為響應(yīng)式對象

let object1 ={}
Object.defineProperty(object1, "name", {
  get() {
    return 1;
  },
  set(x) {
    console.log("數(shù)據(jù)變化了",1+x)
  }
});
console.log(object1)
當(dāng)我們?yōu)閛bject1添加name屬性以及get和set方法時
//{
//  name:1
//  get name:function()...
//  set name:function()...
//}
console.log(object1.name)
//1
object1.name = 1
//數(shù)據(jù)變化了 2

當(dāng)我們讀取object1的name值會觸發(fā)get方法 這里會打印出1,

當(dāng)我們修改object1的name值會觸發(fā)set方法 這里會打印出 ”數(shù)據(jù)變化了 2“

響應(yīng)式開始的地方

vue源碼中在初始化data的方法initData有一句 observe(data)這就是夢開始的地方,讓我們看一下observe具體實現(xiàn)

function observe(value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else{
    ob = new Observer(value)
  }
  return ob
}

首先對傳入的值做了一些類型校驗,如果不是引用類型或者是vNode實例就直接return返回

接下來判斷對象下是否有_ob_屬性,如果有直接返回否則執(zhí)行new Observer(value)那么這個_ob_以及 Observer是什么東西呢?我們接著往下看

Observer

class Observer {
  value: any;
  dep: Dep;
  vmCount: number; 

  constructor(value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  walk(obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  observeArray(items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

Observer的邏輯也非常簡單,首先為傳入的這個引用類型new了一個dep,這個dep主要是為了后續(xù)的$set方法使用暫且不看并將當(dāng)前的observer實例儲存在目標(biāo)的_ob_里,上面說的observe方法會根據(jù)這個_ob_進行判斷,這樣是為了防止data里的屬性相互引用導(dǎo)致多次生成新實例接下來判斷如果是對象類型則對每個屬性執(zhí)行defineReactive方法

如果是數(shù)組類型則遍歷數(shù)組對每個子項執(zhí)行observe方法,observe方法上面我們說過,它會根據(jù)值的類型進行判斷如果是數(shù)組或者對象就執(zhí)行new Observer這一層的套娃實際上是對數(shù)組的層層解析,目的就是為了讓數(shù)組里的對象都執(zhí)行defineReactive方法

實現(xiàn)響應(yīng)式的defineReactive

vue2的源碼中是通過遞歸調(diào)用defineReactive方法將所有對象變?yōu)轫憫?yīng)式對象接下來我們簡單看一下defineReactive的主要邏輯

function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
 ...
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
         
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
     const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

首先為每一個對象屬性都添加的getset方法

并且為每個屬性都new一個dep,這個dep接下來會介紹,值得一提的是這句let childOb = !shallow && observe(val)observe我們上面說了它會對所有對象以及數(shù)組里嵌套的對象執(zhí)行defineReactive這段邏輯就是在遞歸調(diào)用defineReactive方法,這樣不管我們對象套了多少層,它都能實現(xiàn)響應(yīng)vue的響應(yīng)式實際上的經(jīng)典的觀察者模式,dep在get方法里實現(xiàn)對觀察者watcher進行收集,在set方法里通知每個觀察者watcher執(zhí)行 update 方法,想要了解過程,接下來我們重點看一下depwatcher的定義

dep

dep.js
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

dep充當(dāng)一個中間橋梁的作用,收集以及維護觀察者,在目標(biāo)屬性發(fā)生變化時調(diào)用自己的notify方法,對每個觀察者都執(zhí)行update方法通知觀察者需要更新

watcher

watcher.js
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  ...
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } 
      if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      this.value = this.get()
     }
    }
  }

  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
  }

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

  update () {
    if (this.computed) {
      if (this.dep.subs.length === 0) {
        this.dirty = true
      } else {
        this.getAndInvoke(() => {
          this.dep.notify()
        })
      }
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
}

watcher中的邏輯不適合單獨拆開解析,接下來我們結(jié)合流程分析,watcher首次創(chuàng)建實例的場景,這是在第一次渲染頁面組件的時候,我們傳入的expOrFn參數(shù)對應(yīng)的是updateComponent,updateComponent是vue定義的用于重新渲染頁面組件的函數(shù),在代碼中updateComponent又被賦值給了this.getter

  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true )

接著往下看,由于當(dāng)前不是計算屬性所以this.computed是false,執(zhí)行this.value = this.get()在執(zhí)行g(shù)et()的時候會執(zhí)行到pushTarget方法

Dep.target = null
function pushTarget (_target: ?Watcher) {
  ...
  Dep.target = _target
}

這個方法實際上是將當(dāng)前watcher賦值給了Dep.target,Dep.target是一個全局變量,為啥要這么干呢

因為會有組件嵌套的情況,所以可能會有多個渲染watcher,但是通過這種方式就這樣保證了 Dep.target指向的是最新創(chuàng)建的watcher,接下來執(zhí)行了value = this.getter.call(vm, vm),上面說了這個this.getter就是傳入的updateComponent,這個updateComponent就是頁面組件重新渲染的方法,
流程分別是:生成vnode->根據(jù)vnode樹生成真實的dome樹->掛載到頁面上,在使用rander生成vnode的時候就會讀取到模版語法中的值,當(dāng)訪問到值時就觸發(fā)了我們通過defineReactive方法添加的get方法,就觸發(fā)了依賴收集過程。

依賴收集

function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
 ...
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
        }
      }
      return value
    },
    ...
  })
}

剛剛我們說到Dep.target就是當(dāng)前的watcher,在上面????看dep的定義中,dep.depend就是調(diào)用當(dāng)前watcheraddDep方法,將當(dāng)前的watcher觀察者收集到dep的subs數(shù)組,這句childOb.dep.depend()是為$set量身定做的,事先將當(dāng)前的watcher收集到Observer實例里的dep的subs數(shù)組中后續(xù)當(dāng)我們使用$set方法時,只需要調(diào)用引用對象保存在_ob_的Observer實例中的dep的notify方法就能實現(xiàn)手動派發(fā)更新

 watcher.js
  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)
      }
    }
  }

this在這里是指向當(dāng)前創(chuàng)建的watcher實例,這樣這個watcher就被收集到該屬性的dep對象的subs數(shù)組中了,到這里一依賴收集的主干流程就完成了

派發(fā)更新

function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
 ...
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    ...
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

觸發(fā)set方法時就會進行派發(fā)更新,在set方法里我們首先對修改后的值和原來的值進行對比,如果相同就return,如果不相同就繼續(xù)執(zhí)行下面的邏輯,將新值賦值給舊值,childOb = !shallow && observe(newVal)這一段的邏輯是如果新值也是對象的話就對新值執(zhí)行defineReactive將新值變?yōu)?strong>響應(yīng)式對象,接下來我們看一下dep.notify()的定義

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }

notify方法會遍歷當(dāng)前收集的watcher,調(diào)用每個watcher的update方法

update () {
    /* istanbul ignore else */
    if (this.computed) {
      ...
    } else if (this.sync) {
      ...
    } else {
      queueWatcher(this)
    }
  }

update方法分別判斷了是否是計算屬性以及是否是同步的watcher,在當(dāng)前執(zhí)行的環(huán)境下都為false,所以執(zhí)行了queueWatcher(this)

const queue: Array<Watcher> = []
const activatedChildren: Array<Component> = []
let has: { [key: number]: ?true } = {}
let circular: { [key: number]: number } = {}
let waiting = false
let flushing = false
function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue)
    }
  }
}

因為多個dep有可能收集的是同一個watcher,當(dāng)多個dep收集的同一個watcher時這個watcher只需要更新一次,所以通過這種方式去除重復(fù)watcher,由于flushingwaiting初始值都為false,所以會將經(jīng)過過濾watcherpush進queue數(shù)組,然后會在下一個tick內(nèi)執(zhí)行flushSchedulerQueue

function flushSchedulerQueue () {
  flushing = true
  let watcher, id
  queue.sort((a, b) => a.id - b.id)
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    ...

}

首先會根據(jù)id進行一次排序因為如果有嵌套組件的情況出現(xiàn)的話,是先更新父組件再更新子組件,所以進行了一次由小到大的排序,后續(xù)對每個watcher都調(diào)用了run方法

  run () {
    if (this.active) {
      this.getAndInvoke(this.cb)
    }
  }

active的默認(rèn)值是true所以run方法里調(diào)用了getAndInvoke

getAndInvoke (cb: Function) {
    const value = this.get()
   ....
  }

繞了這么大一圈最后還是執(zhí)行的watcher中的get方法,get方法在上面講解watcher的時候就已經(jīng)說過,這個get會執(zhí)行this.getter而這個this.getter是創(chuàng)建watcher時傳入的updateComponentupdateComponent的執(zhí)行會導(dǎo)致重新渲染頁面組件,也就是在屬性被修改時觸發(fā)了set方法,而這個set方法會將依賴當(dāng)前屬性的頁面組件重新渲染,從而達到數(shù)據(jù)驅(qū)動的效果。

總結(jié)

vue用Object.defineProperty重寫屬性的get和set方法

  • 在get中new了一個dep收集當(dāng)前的渲染watcher
  • 在set方法中遍歷收集的渲染watcher執(zhí)行update方法
  • vue在new渲染watcher的時候會將組件掛載更新的方法(updateComponent)傳入,存儲在渲染watcher中,觸發(fā)渲染watcher的update方法時實際上是觸發(fā)這個組件掛載更新方法 也就是在屬性被修改時觸發(fā)了set方法,而這個set方法會將依賴當(dāng)前屬性的頁面重新渲染,從而達到數(shù)據(jù)驅(qū)動的效果。

以上就是面試官問你Vue2的響應(yīng)式原理該如何回答?的詳細內(nèi)容,更多關(guān)于Vue2響應(yīng)式原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論