溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

vue響應(yīng)式原理的解析

發(fā)布時(shí)間:2020-06-22 16:39:22 來源:億速云 閱讀:115 作者:清晨 欄目:開發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)vue響應(yīng)式原理的解析,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。


響應(yīng)式原理作為 Vue 的核心,使用數(shù)據(jù)劫持實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)視圖。在面試中是經(jīng)??疾榈闹R(shí)點(diǎn),也是面試加分項(xiàng)。

本文將會(huì)循序漸進(jìn)的解析響應(yīng)式原理的工作流程,主要以下面結(jié)構(gòu)進(jìn)行:

  1. 分析主要成員,了解它們有助于理解流程
  2. 將流程拆分,理解其中的作用
  3. 結(jié)合以上的點(diǎn),理解整體流程

主要成員

響應(yīng)式原理中,Observe、Watcher、Dep這三個(gè)類是構(gòu)成完整原理的主要成員。

  • Observe,響應(yīng)式原理的入口,根據(jù)數(shù)據(jù)類型處理觀測(cè)邏輯
  • Watcher,用于執(zhí)行更新渲染,組件會(huì)擁有一個(gè)渲染W(wǎng)atcher,我們常說的收集依賴,就是收集 Watcher
  • Dep,依賴收集器,屬性都會(huì)有一個(gè)Dep,方便發(fā)生變化時(shí)能夠找到對(duì)應(yīng)的依賴觸發(fā)更新
     

下面來看看這些類的實(shí)現(xiàn),包含哪些主要屬性和方法。

Observe:我會(huì)對(duì)數(shù)據(jù)進(jìn)行觀測(cè)

溫馨提示:代碼里的序號(hào)對(duì)應(yīng)代碼塊下面序號(hào)的講解

// 源碼位置:/src/core/observer/index.js
class Observe {
 constructor(data) {
  this.dep = new Dep()
  // 1
  def(data, '__ob__', this)
  if (Array.isArray(data)) {
   // 2
   protoAugment(data, arrayMethods)
   // 3
   this.observeArray(data)
  } else {
   // 4
   this.walk(data)
  }
 }
 walk(data) {
  Object.keys(data).forEach(key => {
   defineReactive(data, key, data[key])
  })
 }
 observeArray(data) {
  data.forEach(item => {
   observe(item)
  })
 }
}
  1. 為觀測(cè)的屬性添加 __ob__ 屬性,它的值等于 this,即當(dāng)前 Observe 的實(shí)例
  2. 為數(shù)組添加重寫的數(shù)組方法,比如:push、unshift、splice 等方法,重寫目的是在調(diào)用這些方法時(shí),進(jìn)行更新渲染
  3. 觀測(cè)數(shù)組內(nèi)的數(shù)據(jù),observe 內(nèi)部會(huì)調(diào)用 new Observe,形成遞歸觀測(cè)
  4. 觀測(cè)對(duì)象數(shù)據(jù),defineReactive 為數(shù)據(jù)定義 get 和 set ,即數(shù)據(jù)劫持

Dep:我會(huì)為數(shù)據(jù)收集依賴

// 源碼位置:/src/core/observer/dep.js
let id = 0
class Dep{
 constructor() {
  this.id = ++id // dep 唯一標(biāo)識(shí)
  this.subs = [] // 存儲(chǔ) Watcher
 }
 // 1
 depend() {
  Dep.target.addDep(this)
 }
 // 2
 addSub(watcher) {
  this.subs.push(watcher)
 }
 // 3
 notify() {
  this.subs.forEach(watcher => watcher.update())
 }
}

// 4
Dep.target = null

export function pushTarget(watcher) {
 Dep.target = watcher
} 

export function popTarget(){
 Dep.target = null
}

export default Dep
  1. 據(jù)收集依賴的主要方法,Dep.target 是一個(gè) watcher 實(shí)例
  2. 添加 watcher 到數(shù)組中,也就是添加依賴
  3. 屬性在變化時(shí)會(huì)調(diào)用 notify 方法,通知每一個(gè)依賴進(jìn)行更新
  4. Dep.target 用來記錄 watcher 實(shí)例,是全局唯一的,主要作用是為了在收集依賴的過程中找到相應(yīng)的 watcher
     

pushTarget 和 popTarget 這兩個(gè)方法顯而易見是用來設(shè)置 Dep.target的。Dep.target 也是一個(gè)關(guān)鍵點(diǎn),這個(gè)概念可能初次查看源碼會(huì)有些難以理解,在后面的流程中,會(huì)詳細(xì)講解它的作用,需要注意這部分的內(nèi)容。

Watcher:我會(huì)觸發(fā)視圖更新

// 源碼位置:/src/core/observer/watcher.js
let id = 0
export class Watcher {
 constructor(vm, exprOrFn, cb, options){
  this.id = ++id // watcher 唯一標(biāo)識(shí)
  this.vm = vm
  this.cb = cb
  this.options = options
  // 1
  this.getter = exprOrFn
  this.deps = []
  this.depIds = new Set()

  this.get()
 }
 run() {
  this.get()
 }
 get() {
  pushTarget(this)
  this.getter()
  popTarget(this)
 }
 // 2
 addDep(dep) {
  // 防止重復(fù)添加 dep
  if (!this.depIds.has(dep.id)) {
   this.depIds.add(dep.id)
   this.deps.push(dep)
   dep.addSub(this)
  }
 }
 // 3
 update() {
  queueWatcher(this)
 }
}
  1. this.getter 存儲(chǔ)的是更新視圖的函數(shù)
  2. watcher 存儲(chǔ) dep,同時(shí) dep 也存儲(chǔ) watcher,進(jìn)行雙向記錄
  3. 觸發(fā)更新,queueWatcher 是為了進(jìn)行異步更新,異步更新會(huì)調(diào)用 run 方法進(jìn)行更新頁(yè)面
     

響應(yīng)式原理流程

對(duì)于以上這些成員具有的功能,我們都有大概的了解。下面結(jié)合它們,來看看這些功能是如何在響應(yīng)式原理流程中工作的。

數(shù)據(jù)觀測(cè)

數(shù)據(jù)在初始化時(shí)會(huì)通過 observe 方法來創(chuàng)建 Observe 類

// 源碼位置:/src/core/observer/index.js
export function observe(data) {
 // 1
 if (!isObject(data)) {
  return
 }
 let ob;
 // 2
 if (data.hasOwnProperty('__ob__') && data.__ob__ instanceof Observe) {
  ob = data.__ob__
 } else {
  // 3
  ob = new Observe(data)
 }
 return ob
}

在初始化時(shí),observe 拿到的 data 就是我們?cè)?data 函數(shù)內(nèi)返回的對(duì)象。

  1. observe 函數(shù)只對(duì) object 類型數(shù)據(jù)進(jìn)行觀測(cè)
  2. 觀測(cè)過的數(shù)據(jù)都會(huì)被添加上 __ob__ 屬性,通過判斷該屬性是否存在,防止重復(fù)觀測(cè)
  3. 創(chuàng)建 Observe 類,開始處理觀測(cè)邏輯
     

對(duì)象觀測(cè)

進(jìn)入 Observe 內(nèi)部,由于初始化的數(shù)據(jù)是一個(gè)對(duì)象,所以會(huì)調(diào)用 walk 方法:

walk(data) {
 Object.keys(data).forEach(key => {
  defineReactive(data, key, data[key])
 })
}

defineReactive 方法內(nèi)部使用 Object.defineProperty 對(duì)數(shù)據(jù)進(jìn)行劫持,是實(shí)現(xiàn)響應(yīng)式原理最核心的地方。

function defineReactive(obj, key, value) {
 // 1
 let childOb = observe(value)
 // 2
 const dep = new Dep()
 Object.defineProperty(obj, key, {
  get() {
   if (Dep.target) {
    // 3
    dep.depend()
    if (childOb) {
     childOb.dep.depend()
    }
   }
   return value
  },
  set(newVal) {
   if (newVal === value) {
    return
   }
   value = newVal
   // 4
   childOb = observe(newVal)
   // 5
   dep.notify()
   return value
  }
 })
}
  1. 由于值可能是對(duì)象類型,這里需要調(diào)用 observe 進(jìn)行遞歸觀測(cè)
  2. 這里的 dep 就是上面講到的每一個(gè)屬性都會(huì)有一個(gè) dep,它是作為一個(gè)閉包的存在,負(fù)責(zé)收集依賴和通知更新
  3. 在初始化時(shí),Dep.target 是組件的渲染 watcher,這里 dep.depend 收集的依賴就是這個(gè) watcher,childOb.dep.depend 主要是為數(shù)組收集依賴
  4. 設(shè)置的新值可能是對(duì)象類型,需要對(duì)新值進(jìn)行觀測(cè)
  5. 值發(fā)生改變,dep.notify 通知 watcher 更新,這是我們改變數(shù)據(jù)后能夠?qū)崟r(shí)更新頁(yè)面的觸發(fā)點(diǎn)
     

通過 Object.defineProperty 對(duì)屬性定義后,屬性的獲取觸發(fā) get 回調(diào),屬性的設(shè)置觸發(fā) set 回調(diào),實(shí)現(xiàn)響應(yīng)式更新。

通過上面的邏輯,也能得出為什么 Vue3.0 要使用 Proxy 代替 Object.defineProperty 了。Object.defineProperty 只能對(duì)單個(gè)屬性進(jìn)行定義,如果屬性是對(duì)象類型,還需要遞歸去觀測(cè),會(huì)很消耗性能。而 Proxy 是代理整個(gè)對(duì)象,只要屬性發(fā)生變化就會(huì)觸發(fā)回調(diào)。

數(shù)組觀測(cè)

對(duì)于數(shù)組類型觀測(cè),會(huì)調(diào)用 observeArray 方法:

observeArray(data) {
 data.forEach(item => {
  observe(item)
 })
}

與對(duì)象不同,它執(zhí)行 observe 對(duì)數(shù)組內(nèi)的對(duì)象類型進(jìn)行觀測(cè),并沒有對(duì)數(shù)組的每一項(xiàng)進(jìn)行 Object.defineProperty 的定義,也就是說數(shù)組內(nèi)的項(xiàng)是沒有 dep 的。

所以,我們通過數(shù)組索引對(duì)項(xiàng)進(jìn)行修改時(shí),是不會(huì)觸發(fā)更新的。但可以通過 this.$set 來修改觸發(fā)更新。那么問題來了,為什么 Vue 要這樣設(shè)計(jì)?

結(jié)合實(shí)際場(chǎng)景,數(shù)組中通常會(huì)存放多項(xiàng)數(shù)據(jù),比如列表數(shù)據(jù)。這樣觀測(cè)起來會(huì)消耗性能。還有一點(diǎn)原因,一般修改數(shù)組元素很少會(huì)直接通過索引將整個(gè)元素替換掉。例如:

export default {
  data() {
    return {
      list: [
        {id: 1, name: 'Jack'},
        {id: 2, name: 'Mike'}
      ]
    }
  },
  cretaed() {
    // 如果想要修改 name 的值,一般是這樣使用
    this.list[0].name = 'JOJO'
    // 而不是以下這樣
    // this.list[0] = {id:1, name: 'JOJO'}
    // 當(dāng)然你可以這樣更新
    // this.$set(this.list, '0', {id:1, name: 'JOJO'})
  }
}

數(shù)組方法重寫

當(dāng)數(shù)組元素新增或刪除,視圖會(huì)隨之更新。這并不是理所當(dāng)然的,而是 Vue 內(nèi)部重寫了數(shù)組的方法,調(diào)用這些方法時(shí),數(shù)組會(huì)更新檢測(cè),觸發(fā)視圖更新。這些方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
     

回到 Observe 的類中,當(dāng)觀測(cè)的數(shù)據(jù)類型為數(shù)組時(shí),會(huì)調(diào)用 protoAugment 方法。

if (Array.isArray(data)) {
 protoAugment(data, arrayMethods)
 // 觀察數(shù)組
 this.observeArray(data)
} else {
 // 觀察對(duì)象
 this.walk(data)
}

這個(gè)方法里把數(shù)組原型替換為 arrayMethods ,當(dāng)調(diào)用改變數(shù)組的方法時(shí),優(yōu)先使用重寫后的方法。

function protoAugment(data, arrayMethods) {
 data.__proto__ = arrayMethods
}

接下來看看 arrayMethods 是如何實(shí)現(xiàn)的:

// 源碼位置:/src/core/observer/array.js
// 1
let arrayProto = Array.prototype
// 2
export let arrayMethods = Object.create(arrayProto)

let methods = [
 'push',
 'pop',
 'shift',
 'unshift',
 'reverse',
 'sort',
 'splice'
]

methods.forEach(method => {
 arrayMethods[method] = function(...args) {
  // 3
  let res = arrayProto[method].apply(this, args)
  let ob = this.__ob__
  let inserted = ''
  switch(method){
   case 'push':
   case 'unshift':
    inserted = args
    break;
   case 'splice':
    inserted = args.slice(2)
    break;
  }
  // 4
  inserted && ob.observeArray(inserted)
  // 5
  ob.dep.notify()
  return res
 }
})
  1. 將數(shù)組的原型保存起來,因?yàn)橹貙懙臄?shù)組方法里,還是需要調(diào)用原生數(shù)組方法的
  2. arrayMethods 是一個(gè)對(duì)象,用于保存重寫的方法,這里使用 Object.create(arrayProto) 創(chuàng)建對(duì)象是為了使用者在調(diào)用非重寫方法時(shí),能夠繼承使用原生的方法
  3. 調(diào)用原生方法,存儲(chǔ)返回值,用于設(shè)置重寫函數(shù)的返回值
  4. inserted 存儲(chǔ)新增的值,若 inserted 存在,對(duì)新值進(jìn)行觀測(cè)
  5. ob.dep.notify 觸發(fā)視圖更新

依賴收集

依賴收集是視圖更新的前提,也是響應(yīng)式原理中至關(guān)重要的環(huán)節(jié)。

偽代碼流程

為了方便理解,這里寫一段偽代碼,大概了解依賴收集的流程:

// data 數(shù)據(jù)
let data = {
  name: 'joe'
}

// 渲染watcher
let watcher = {
  run() {
    dep.tagret = watcher
    document.write(data.name)
  }
}

// dep
let dep = [] // 存儲(chǔ)依賴 
dep.tagret = null // 記錄 watcher

// 數(shù)據(jù)劫持
Object.defineProperty(data, 'name', {
  get(){
    // 收集依賴
    dep.push(dep.tagret)
  },
  set(newVal){
    data.name = newVal
    dep.forEach(watcher => {
      watcher.run()
    })
  }
})

初始化:

  1. 首先會(huì)對(duì) name 屬性定義 get 和 set
  2. 然后初始化會(huì)執(zhí)行一次 watcher.run 渲染頁(yè)面
  3. 這時(shí)候獲取 data.name,觸發(fā) get 函數(shù)收集依賴。

更新:

修改 data.name,觸發(fā) set 函數(shù),調(diào)用 run 更新視圖。

真正流程

下面來看看真正的依賴收集流程是如何進(jìn)行的。

function defineReactive(obj, key, value) {
 let childOb = observe(value)
 const dep = new Dep()
 Object.defineProperty(obj, key, {
  get() {
   if (Dep.target) {
    dep.depend() // 收集依賴
    if (childOb) {
     childOb.dep.depend()
    }
   }
   return value
  },
  set(newVal) {
   if (newVal === value) {
    return
   }
   value = newVal
   childOb = observe(newVal)
   dep.notify()
   return value
  }
 })
}

首先初始化數(shù)據(jù),調(diào)用 defineReactive 函數(shù)對(duì)數(shù)據(jù)進(jìn)行劫持。

export class Watcher {
 constructor(vm, exprOrFn, cb, options){
  this.getter = exprOrFn
  this.get()
 }
 get() {
  pushTarget(this)
  this.getter()
  popTarget(this)
 }
}

初始化將 watcher 掛載到 Dep.target,this.getter 開始渲染頁(yè)面。渲染頁(yè)面需要對(duì)數(shù)據(jù)取值,觸發(fā) get 回調(diào),dep.depend 收集依賴。

class Dep{
 constructor() {
  this.id = id++
  this.subs = []
 }
 depend() {
  Dep.target.addDep(this)
 }
}

Dep.target 為 watcher,調(diào)用 addDep 方法,并傳入 dep 實(shí)例。

export class Watcher {
 constructor(vm, exprOrFn, cb, options){
  this.deps = []
  this.depIds = new Set()
 }
 addDep(dep) {
  if (!this.depIds.has(dep.id)) {
   this.depIds.add(dep.id)
   this.deps.push(dep)
   dep.addSub(this)
  }
 }
}

addDep 中添加完 dep 后,調(diào)用 dep.addSub 并傳入當(dāng)前 watcher 實(shí)例。

class Dep{
 constructor() {
  this.id = id++
  this.subs = []
 }
 addSub(watcher) {
  this.subs.push(watcher)
 }
}

將傳入的 watcher 收集起來,至此依賴收集流程完畢。

補(bǔ)充一點(diǎn),通常頁(yè)面上會(huì)綁定很多屬性變量,渲染會(huì)對(duì)屬性取值,此時(shí)每個(gè)屬性收集的依賴都是同一個(gè) watcher,即組件的渲染 watcher。

數(shù)組的依賴收集

methods.forEach(method => {
 arrayMethods[method] = function(...args) {
  let res = arrayProto[method].apply(this, args)
  let ob = this.__ob__
  let inserted = ''
  switch(method){
   case 'push':
   case 'unshift':
    inserted = args
    break;
   case 'splice':
    inserted = args.slice(2)
    break;
  }
  // 對(duì)新增的值觀測(cè)
  inserted && ob.observeArray(inserted)
  // 更新視圖
  ob.dep.notify()
  return res
 }
})

還記得重寫的方法里,會(huì)調(diào)用 ob.dep.notify 更新視圖,__ob__ 是我們?cè)?Observe 為觀測(cè)數(shù)據(jù)定義的標(biāo)識(shí),值為 Observe 實(shí)例。那么 ob.dep 的依賴是在哪里收集的?

function defineReactive(obj, key, value) {
 // 1
 let childOb = observe(value)
 const dep = new Dep()
 Object.defineProperty(obj, key, {
  get() {
   if (Dep.target) {
    dep.depend()
    // 2
    if (childOb) {
     childOb.dep.depend()
    }
   }
   return value
  },
  set(newVal) {
   if (newVal === value) {
    return
   }
   value = newVal
   childOb = observe(newVal)
   dep.notify()
   return value
  }
 })
}
  1. observe 函數(shù)返回值為 Observe 實(shí)例
     
  2. childOb.dep.depend 執(zhí)行,為 Observe 實(shí)例的 dep 添加依賴

所以在數(shù)組更新時(shí),ob.dep 內(nèi)已經(jīng)收集到依賴了。

整體流程

下面捋一遍初始化流程和更新流程,如果你是初次看源碼,不知道從哪里看起,也可以參照以下的順序。由于源碼實(shí)現(xiàn)比較多,下面展示的源碼會(huì)稍微刪減一些代碼

初始化流程

入口文件:

// 源碼位置:/src/core/instance/index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
 this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

_init:

// 源碼位置:/src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
 Vue.prototype._init = function (options&#63;: Object) {
  const vm: Component = this
  // a uid
  vm._uid = uid++

  // merge options
  if (options && options._isComponent) {
   // optimize internal component instantiation
   // since dynamic options merging is pretty slow, and none of the
   // internal component options needs special treatment.
   initInternalComponent(vm, options)
  } else {
   // mergeOptions 對(duì) mixin 選項(xiàng)和傳入的 options 選項(xiàng)進(jìn)行合并
   // 這里的 $options 可以理解為 new Vue 時(shí)傳入的對(duì)象
   vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
   )
  }

  // expose real self
  vm._self = vm
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate')
  initInjections(vm) // resolve injections before data/props
  // 初始化數(shù)據(jù)
  initState(vm)
  initProvide(vm) // resolve provide after data/props
  callHook(vm, 'created')

  if (vm.$options.el) {
   // 初始化渲染頁(yè)面 掛載組件
   vm.$mount(vm.$options.el)
  }
 }
}

上面主要關(guān)注兩個(gè)函數(shù),initState 初始化數(shù)據(jù),vm.$mount(vm.$options.el) 初始化渲染頁(yè)面。

先進(jìn)入 initState:

// 源碼位置:/src/core/instance/state.js 
export function initState (vm: Component) {
 vm._watchers = []
 const opts = vm.$options
 if (opts.props) initProps(vm, opts.props)
 if (opts.methods) initMethods(vm, opts.methods)
 if (opts.data) {
  // data 初始化
  initData(vm)
 } else {
  observe(vm._data = {}, true /* asRootData */)
 }
 if (opts.computed) initComputed(vm, opts.computed)
 if (opts.watch && opts.watch !== nativeWatch) {
  initWatch(vm, opts.watch)
 }
}

function initData (vm: Component) {
 let data = vm.$options.data
 // data 為函數(shù)時(shí),執(zhí)行 data 函數(shù),取出返回值
 data = vm._data = typeof data === 'function'
  &#63; getData(data, vm)
  : data || {}
 // proxy data on instance
 const keys = Object.keys(data)
 const props = vm.$options.props
 const methods = vm.$options.methods
 let i = keys.length
 while (i--) {
  const key = keys[i]
  if (props && hasOwn(props, key)) {
   process.env.NODE_ENV !== 'production' && warn(
    `The data property "${key}" is already declared as a prop. ` +
    `Use prop default value instead.`,
    vm
   )
  } else if (!isReserved(key)) {
   proxy(vm, `_data`, key)
  }
 }
 // observe data
 // 這里就開始走觀測(cè)數(shù)據(jù)的邏輯了
 observe(data, true /* asRootData */)
}

observe 內(nèi)部流程在上面已經(jīng)講過,這里再簡(jiǎn)單過一遍:

  1. new Observe 觀測(cè)數(shù)據(jù)
  2. defineReactive 對(duì)數(shù)據(jù)進(jìn)行劫持
     

initState 邏輯執(zhí)行完畢,回到開頭,接下來執(zhí)行 vm.$mount(vm.$options.el) 渲染頁(yè)面:

$mount:

// 源碼位置:/src/platforms/web/runtime/index.js 
Vue.prototype.$mount = function (
 el&#63;: string | Element,
 hydrating&#63;: boolean
): Component {
 el = el && inBrowser &#63; query(el) : undefined
 return mountComponent(this, el, hydrating)
}

mountComponent:

// 源碼位置:/src/core/instance/lifecycle.js
export function mountComponent (
 vm: Component,
 el: &#63;Element,
 hydrating&#63;: boolean
): Component {
 vm.$el = el
 callHook(vm, 'beforeMount')

 let updateComponent
 /* istanbul ignore if */
 if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  updateComponent = () => {
   const name = vm._name
   const id = vm._uid
   const startTag = `vue-perf-start:${id}`
   const endTag = `vue-perf-end:${id}`

   mark(startTag)
   const vnode = vm._render()
   mark(endTag)
   measure(`vue ${name} render`, startTag, endTag)

   mark(startTag)
   vm._update(vnode, hydrating)
   mark(endTag)
   measure(`vue ${name} patch`, startTag, endTag)
  }
 } else {
  // 數(shù)據(jù)改變時(shí) 會(huì)調(diào)用此方法
  updateComponent = () => {
   // vm._render() 返回 vnode,這里面會(huì)就對(duì) data 數(shù)據(jù)進(jìn)行取值
   // vm._update 將 vnode 轉(zhuǎn)為真實(shí)dom,渲染到頁(yè)面上
   vm._update(vm._render(), hydrating)
  }
 }
 
 // 執(zhí)行 Watcher,這個(gè)就是上面所說的渲染wacther 
 new Watcher(vm, updateComponent, noop, {
  before () {
   if (vm._isMounted && !vm._isDestroyed) {
    callHook(vm, 'beforeUpdate')
   }
  }
 }, true /* isRenderWatcher */)
 hydrating = false

 // manually mounted instance, call mounted on self
 // mounted is called for render-created child components in its inserted hook
 if (vm.$vnode == null) {
  vm._isMounted = true
  callHook(vm, 'mounted')
 }
 return vm
}

Watcher:

// 源碼位置:/src/core/observer/watcher.js 
let uid = 0

export default class Watcher {
 constructor(vm, exprOrFn, cb, options){
  this.id = ++id
  this.vm = vm
  this.cb = cb
  this.options = options
  // exprOrFn 就是上面?zhèn)魅氲?updateComponent
  this.getter = exprOrFn

  this.deps = []
  this.depIds = new Set()

  this.get()
 }
 get() {
  // 1. pushTarget 將當(dāng)前 watcher 記錄到 Dep.target,Dep.target 是全局唯一的
  pushTarget(this)
  let value
  const vm = this.vm
  try {
  // 2. 調(diào)用 this.getter 相當(dāng)于會(huì)執(zhí)行 vm._render 函數(shù),對(duì)實(shí)例上的屬性取值,
  //由此觸發(fā) Object.defineProperty 的 get 方法,在 get 方法內(nèi)進(jìn)行依賴收集(dep.depend),這里依賴收集就需要用到 Dep.target
   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)
   }
   // 3. popTarget 將 Dep.target 置空
   popTarget()
   this.cleanupDeps()
  }
  return value
 }
}

至此初始化流程完畢,初始化流程的主要工作是數(shù)據(jù)劫持、渲染頁(yè)面和收集依賴。

更新流程

數(shù)據(jù)發(fā)生變化,觸發(fā) set ,執(zhí)行 dep.notify

// 源碼位置:/src/core/observer/dep.js 
let uid = 0

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
 static target: &#63;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 () {
  // stabilize the subscriber list first
  const subs = this.subs.slice()
  if (process.env.NODE_ENV !== 'production' && !config.async) {
   // subs aren't sorted in scheduler if not running async
   // we need to sort them now to make sure they fire in correct
   // order
   subs.sort((a, b) => a.id - b.id)
  }
  for (let i = 0, l = subs.length; i < l; i++) {
   // 執(zhí)行 watcher 的 update 方法
   subs[i].update()
  }
 }
}

wathcer.update:

// 源碼位置:/src/core/observer/watcher.js 
/**
 * Subscriber interface.
 * Will be called when a dependency changes.
 */
update () {
 /* istanbul ignore else */
 if (this.lazy) { // 計(jì)算屬性更新
  this.dirty = true
 } else if (this.sync) { // 同步更新
  this.run()
 } else {
  // 一般的數(shù)據(jù)都會(huì)進(jìn)行異步更新
  queueWatcher(this)
 }
}

queueWatcher:

// 源碼位置:/src/core/observer/scheduler.js

// 用于存儲(chǔ) watcher
const queue: Array<Watcher> = []
// 用于 watcher 去重
let has: { [key: number]: &#63;true } = {}
/**
 * Flush both queues and run the watchers.
 */
function flushSchedulerQueue () {
 let watcher, id

 // 對(duì) watcher 排序
 queue.sort((a, b) => a.id - b.id)

 // do not cache length because more watchers might be pushed
 // as we run existing watchers
 for (index = 0; index < queue.length; index++) {
  watcher = queue[index]
  id = watcher.id
  has[id] = null
  // run方法更新視圖
  watcher.run()
 }
}
/**
 * Push a watcher into the watcher queue.
 * Jobs with duplicate IDs will be skipped unless it's
 * pushed when the queue is being flushed.
 */
export function queueWatcher (watcher: Watcher) {
 const id = watcher.id
 if (has[id] == null) {
  has[id] = true
  // watcher 加入數(shù)組
  queue.push(watcher)
  // 異步更新
  nextTick(flushSchedulerQueue)
 }
}

nextTick:

// 源碼位置:/src/core/util/next-tick.js

const callbacks = []
let pending = false

function flushCallbacks () {
 pending = false
 const copies = callbacks.slice(0)
 callbacks.length = 0
 // 遍歷回調(diào)函數(shù)執(zhí)行
 for (let i = 0; i < copies.length; i++) {
  copies[i]()
 }
}

let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
 const p = Promise.resolve()
 timerFunc = () => {
  p.then(flushCallbacks)
 }
}

export function nextTick (cb&#63;: Function, ctx&#63;: Object) {
 let _resolve
 // 將回調(diào)函數(shù)加入數(shù)組
 callbacks.push(() => {
  if (cb) {
   cb.call(ctx)
  }
 })
 if (!pending) {
  pending = true
  // 遍歷回調(diào)函數(shù)執(zhí)行
  timerFunc()
 }
 // $flow-disable-line
 if (!cb && typeof Promise !== 'undefined') {
  return new Promise(resolve => {
   _resolve = resolve
  })
 }
}

這一步是為了使用微任務(wù)將回調(diào)函數(shù)異步執(zhí)行,也就是上面的p.then。最終,會(huì)調(diào)用 watcher.run 更新頁(yè)面。

至此更新流程完畢。


關(guān)于vue響應(yīng)式原理的解析就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI