久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

手摸手帶你理解Vue響應(yīng)式原理

 路人甲Java 2021-10-11

前言

響應(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),理解整體流程

文章稍長(zhǎng),,但部分是代碼,,還請(qǐng)耐心觀看。為了方便理解原理,,文中的代碼會(huì)進(jìn)行簡(jiǎn)化,,如果可以請(qǐng)對(duì)照源碼學(xué)習(xí)。

主要成員

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

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

下面來看看這些類的實(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ù)組方法,,比如:pushunshift,、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ù)定義 getset ,,即數(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. 數(shù)據(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

pushTargetpopTarget 這兩個(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īng)式原理流程

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

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

數(shù)據(jù)在初始化時(shí)會(huì)通過 observe 方法來調(diào)用 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 實(shí)例,,開始處理觀測(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í)更新頁面的觸發(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ù)劫持
let oldValue = data.name
Object.defineProperty(data, 'name', {
   get(){
       // 收集依賴
       dep.push(dep.tagret)
       return oldValue
   },
   set(newVal){
       oldValue = newVal
       dep.forEach(watcher => {
           watcher.run()
       })
       
   }
})

初始化:

  1. 首先會(huì)對(duì) name 屬性定義 getset
  2. 然后初始化會(huì)執(zhí)行一次 watcher.run 渲染頁面
  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.targetthis.getter 開始渲染頁面,。渲染頁面需要對(duì)數(shù)據(jù)取值,,觸發(fā) get 回調(diào),dep.depend 收集依賴,。

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

Dep.targetwatcher,,調(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),通常頁面上會(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?: 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) {
      // 初始化渲染頁面 掛載組件
      vm.$mount(vm.$options.el)
    }
  }
}

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

先進(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'
    ? 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) 渲染頁面:

$mount:

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

mountComponent:

// 源碼位置:/src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: 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,,渲染到頁面上
      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ù)劫持,、渲染頁面和收集依賴,。

更新流程

數(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: ?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]: ?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?: Function, ctx?: 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 更新頁面,。

至此更新流程完畢。

寫在最后

如果沒有接觸過源碼的同學(xué),,我相信看完可能還是會(huì)有點(diǎn)懵的,,這很正常。建議對(duì)照源碼再自己多看幾遍就能知道流程了,。對(duì)于有基礎(chǔ)的同學(xué)就當(dāng)做是復(fù)習(xí)了,。

想要變強(qiáng),學(xué)會(huì)看源碼是必經(jīng)之路,。在這過程中,,不僅能學(xué)習(xí)框架的設(shè)計(jì)思想,還能培養(yǎng)自己的邏輯思維,。萬事開頭難,,遲早都要邁出這一步,不如就從今天開始,。

簡(jiǎn)化后的代碼我已放在 github,,有需要的可以看看。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多