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

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

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

前言

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

響應(yīng)式對(duì)象

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

let object1 = {};

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

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

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

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

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方法時(shí)
//{
//  name:1
//  get name:function()...
//  set name:function()...
//}
console.log(object1.name)
//1
object1.name = 1
//數(shù)據(jù)變化了 2

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

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

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

vue源碼中在初始化data的方法initData有一句 observe(data)這就是夢(mèng)開始的地方,讓我們看一下observe具體實(shí)現(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
}

首先對(duì)傳入的值做了一些類型校驗(yàn),如果不是引用類型或者是vNode實(shí)例就直接return返回

接下來判斷對(duì)象下是否有_ob_屬性,如果有直接返回否則執(zhí)行new Observer(value)那么這個(gè)_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的邏輯也非常簡單,首先為傳入的這個(gè)引用類型new了一個(gè)dep這個(gè)dep主要是為了后續(xù)的$set方法使用暫且不看并將當(dāng)前的observer實(shí)例儲(chǔ)存在目標(biāo)的_ob_里,上面說的observe方法會(huì)根據(jù)這個(gè)_ob_進(jìn)行判斷,這樣是為了防止data里的屬性相互引用導(dǎo)致多次生成新實(shí)例接下來判斷如果是對(duì)象類型則對(duì)每個(gè)屬性執(zhí)行defineReactive方法

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

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

vue2的源碼中是通過遞歸調(diào)用defineReactive方法將所有對(duì)象變?yōu)轫憫?yīng)式對(duì)象接下來我們簡單看一下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()
    }
  })
}

首先為每一個(gè)對(duì)象屬性都添加的getset方法

并且為每個(gè)屬性都new一個(gè)dep,這個(gè)dep接下來會(huì)介紹,值得一提的是這句let childOb = !shallow && observe(val)observe我們上面說了它會(huì)對(duì)所有對(duì)象以及數(shù)組里嵌套的對(duì)象執(zhí)行defineReactive這段邏輯就是在遞歸調(diào)用defineReactive方法,這樣不管我們對(duì)象套了多少層,它都能實(shí)現(xiàn)響應(yīng)vue的響應(yīng)式實(shí)際上的經(jīng)典的觀察者模式,dep在get方法里實(shí)現(xiàn)對(duì)觀察者watcher進(jìn)行收集,在set方法里通知每個(gè)觀察者watcher執(zhí)行 update 方法,想要了解過程,接下來我們重點(diǎn)看一下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)一個(gè)中間橋梁的作用,收集以及維護(hù)觀察者,在目標(biāo)屬性發(fā)生變化時(shí)調(diào)用自己的notify方法,對(duì)每個(gè)觀察者都執(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中的邏輯不適合單獨(dú)拆開解析,接下來我們結(jié)合流程分析,watcher首次創(chuàng)建實(shí)例的場(chǎng)景,這是在第一次渲染頁面組件的時(shí)候,我們傳入的expOrFn參數(shù)對(duì)應(yīng)的是updateComponent,updateComponent是vue定義的用于重新渲染頁面組件的函數(shù),在代碼中updateComponent又被賦值給了this.getter

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

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

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

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

因?yàn)闀?huì)有組件嵌套的情況,所以可能會(huì)有多個(gè)渲染watcher,但是通過這種方式就這樣保證了 Dep.target指向的是最新創(chuàng)建的watcher,接下來執(zhí)行了value = this.getter.call(vm, vm),上面說了這個(gè)this.getter就是傳入的updateComponent,這個(gè)updateComponent就是頁面組件重新渲染的方法,
流程分別是:生成vnode->根據(jù)vnode樹生成真實(shí)的dome樹->掛載到頁面上,在使用rander生成vnode的時(shí)候就會(huì)讀取到模版語法中的值,當(dāng)訪問到值時(shí)就觸發(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實(shí)例里的dep的subs數(shù)組中后續(xù)當(dāng)我們使用$set方法時(shí),只需要調(diào)用引用對(duì)象保存在_ob_的Observer實(shí)例中的dep的notify方法就能實(shí)現(xiàn)手動(dòng)派發(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實(shí)例,這樣這個(gè)watcher就被收集到該屬性的dep對(duì)象的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方法時(shí)就會(huì)進(jìn)行派發(fā)更新,在set方法里我們首先對(duì)修改后的值和原來的值進(jìn)行對(duì)比,如果相同就return,如果不相同就繼續(xù)執(zhí)行下面的邏輯,將新值賦值給舊值,childOb = !shallow && observe(newVal)這一段的邏輯是如果新值也是對(duì)象的話就對(duì)新值執(zhí)行defineReactive將新值變?yōu)?strong>響應(yīng)式對(duì)象,接下來我們看一下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方法會(huì)遍歷當(dāng)前收集的watcher,調(diào)用每個(gè)watcher的update方法

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

update方法分別判斷了是否是計(jì)算屬性以及是否是同步的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)
    }
  }
}

因?yàn)槎鄠€(gè)dep有可能收集的是同一個(gè)watcher,當(dāng)多個(gè)dep收集的同一個(gè)watcher時(shí)這個(gè)watcher只需要更新一次,所以通過這種方式去除重復(fù)watcher,由于flushingwaiting初始值都為false,所以會(huì)將經(jīng)過過濾watcherpush進(jìn)queue數(shù)組,然后會(huì)在下一個(gè)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()
    ...

}

首先會(huì)根據(jù)id進(jìn)行一次排序因?yàn)槿绻?strong>嵌套組件的情況出現(xiàn)的話,是先更新父組件再更新子組件,所以進(jìn)行了一次由小到大的排序,后續(xù)對(duì)每個(gè)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的時(shí)候就已經(jīng)說過,這個(gè)get會(huì)執(zhí)行this.getter而這個(gè)this.getter是創(chuàng)建watcher時(shí)傳入的updateComponentupdateComponent的執(zhí)行會(huì)導(dǎo)致重新渲染頁面組件,也就是在屬性被修改時(shí)觸發(fā)了set方法,而這個(gè)set方法會(huì)將依賴當(dāng)前屬性的頁面組件重新渲染,從而達(dá)到數(shù)據(jù)驅(qū)動(dòng)的效果。

總結(jié)

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

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

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

相關(guān)文章

最新評(píng)論