亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁技術(shù)文章
文章詳情頁

詳細(xì)分析vue響應(yīng)式原理

瀏覽:3日期:2023-01-11 16:45:14

前言

響應(yīng)式原理作為 Vue 的核心,使用數(shù)據(jù)劫持實現(xiàn)數(shù)據(jù)驅(qū)動視圖。在面試中是經(jīng)常考查的知識點,也是面試加分項。

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

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

文章稍長,但大部分是代碼實現(xiàn),還請耐心觀看。為了方便理解原理,文中的代碼會進行簡化,如果可以請對照源碼學(xué)習(xí)。

主要成員

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

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

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

Observe:我會對數(shù)據(jù)進行觀測

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

// 源碼位置:/src/core/observer/index.jsclass 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) }) }} 為觀測的屬性添加 __ob__ 屬性,它的值等于 this,即當(dāng)前 Observe 的實例 為數(shù)組添加重寫的數(shù)組方法,比如:push、unshift、splice 等方法,重寫目的是在調(diào)用這些方法時,進行更新渲染 觀測數(shù)組內(nèi)的數(shù)據(jù),observe 內(nèi)部會調(diào)用 new Observe,形成遞歸觀測 觀測對象數(shù)據(jù),defineReactive 為數(shù)據(jù)定義 get 和 set ,即數(shù)據(jù)劫持

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

// 源碼位置:/src/core/observer/dep.jslet id = 0class Dep{ constructor() { this.id = ++id // dep 唯一標(biāo)識 this.subs = [] // 存儲 Watcher } // 1 depend() { Dep.target.addDep(this) } // 2 addSub(watcher) { this.subs.push(watcher) } // 3 notify() { this.subs.forEach(watcher => watcher.update()) }}// 4Dep.target = nullexport function pushTarget(watcher) { Dep.target = watcher} export function popTarget(){ Dep.target = null}export default Dep 據(jù)收集依賴的主要方法,Dep.target 是一個 watcher 實例 添加 watcher 到數(shù)組中,也就是添加依賴 屬性在變化時會調(diào)用 notify 方法,通知每一個依賴進行更新 Dep.target 用來記錄 watcher 實例,是全局唯一的,主要作用是為了在收集依賴的過程中找到相應(yīng)的 watcher

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

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

// 源碼位置:/src/core/observer/watcher.jslet id = 0export class Watcher { constructor(vm, exprOrFn, cb, options){ this.id = ++id // watcher 唯一標(biāo)識 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) }} this.getter 存儲的是更新視圖的函數(shù) watcher 存儲 dep,同時 dep 也存儲 watcher,進行雙向記錄 觸發(fā)更新,queueWatcher 是為了進行異步更新,異步更新會調(diào)用 run 方法進行更新頁面

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

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

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

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

// 源碼位置:/src/core/observer/index.jsexport 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}

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

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

對象觀測

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

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

defineReactive 方法內(nèi)部使用 Object.defineProperty 對數(shù)據(jù)進行劫持,是實現(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 } })} 由于值可能是對象類型,這里需要調(diào)用 observe 進行遞歸觀測 這里的 dep 就是上面講到的每一個屬性都會有一個 dep,它是作為一個閉包的存在,負(fù)責(zé)收集依賴和通知更新 在初始化時,Dep.target 是組件的渲染 watcher,這里 dep.depend 收集的依賴就是這個 watcher,childOb.dep.depend 主要是為數(shù)組收集依賴 設(shè)置的新值可能是對象類型,需要對新值進行觀測 值發(fā)生改變,dep.notify 通知 watcher 更新,這是我們改變數(shù)據(jù)后能夠?qū)崟r更新頁面的觸發(fā)點

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

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

數(shù)組觀測

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

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

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

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

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

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

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

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

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

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

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

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

// 源碼位置:/src/core/observer/array.js// 1let arrayProto = Array.prototype// 2export 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 }}) 將數(shù)組的原型保存起來,因為重寫的數(shù)組方法里,還是需要調(diào)用原生數(shù)組方法的 arrayMethods 是一個對象,用于保存重寫的方法,這里使用 Object.create(arrayProto) 創(chuàng)建對象是為了使用者在調(diào)用非重寫方法時,能夠繼承使用原生的方法 調(diào)用原生方法,存儲返回值,用于設(shè)置重寫函數(shù)的返回值 inserted 存儲新增的值,若 inserted 存在,對新值進行觀測 ob.dep.notify 觸發(fā)視圖更新

依賴收集

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

偽代碼流程

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

// data 數(shù)據(jù)let data = { name: ’joe’}// 渲染watcherlet watcher = { run() { dep.tagret = watcher document.write(data.name) }}// deplet dep = [] // 存儲依賴 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() }) }})

初始化:

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

更新:

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

真正流程

下面來看看真正的依賴收集流程是如何進行的。

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ù)對數(shù)據(jù)進行劫持。

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 開始渲染頁面。渲染頁面需要對數(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 實例。

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 實例。

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

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

補充一點,通常頁面上會綁定很多屬性變量,渲染會對屬性取值,此時每個屬性收集的依賴都是同一個 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; } // 對新增的值觀測 inserted && ob.observeArray(inserted) // 更新視圖 ob.dep.notify() return res }})

還記得重寫的方法里,會調(diào)用 ob.dep.notify 更新視圖,__ob__ 是我們在 Observe 為觀測數(shù)據(jù)定義的標(biāo)識,值為 Observe 實例。那么 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 } })} observe 函數(shù)返回值為 Observe 實例 childOb.dep.depend 執(zhí)行,為 Observe 實例的 dep 添加依賴

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

整體流程

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

初始化流程

入口文件:

// 源碼位置:/src/core/instance/index.jsimport { 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.jsexport 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 對 mixin 選項和傳入的 options 選項進行合并 // 這里的 $options 可以理解為 new Vue 時傳入的對象 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)注兩個函數(shù),initState 初始化數(shù)據(jù),vm.$mount(vm.$options.el) 初始化渲染頁面。

先進入 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ù)時,執(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 // 這里就開始走觀測數(shù)據(jù)的邏輯了 observe(data, true /* asRootData */)}

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

new Observe 觀測數(shù)據(jù) defineReactive 對數(shù)據(jù)進行劫持

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.jsexport 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ù)改變時 會調(diào)用此方法 updateComponent = () => { // vm._render() 返回 vnode,這里面會就對 data 數(shù)據(jù)進行取值 // vm._update 將 vnode 轉(zhuǎn)為真實dom,渲染到頁面上 vm._update(vm._render(), hydrating) } } // 執(zhí)行 Watcher,這個就是上面所說的渲染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 = 0export 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)于會執(zhí)行 vm._render 函數(shù),對實例上的屬性取值, //由此觸發(fā) Object.defineProperty 的 get 方法,在 get 方法內(nèi)進行依賴收集(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) { // 計算屬性更新 this.dirty = true } else if (this.sync) { // 同步更新 this.run() } else { // 一般的數(shù)據(jù)都會進行異步更新 queueWatcher(this) }}

queueWatcher:

// 源碼位置:/src/core/observer/scheduler.js// 用于存儲 watcherconst queue: Array<Watcher> = []// 用于 watcher 去重let has: { [key: number]: ?true } = {}/** * Flush both queues and run the watchers. */function flushSchedulerQueue () { let watcher, id // 對 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.jsconst callbacks = []let pending = falsefunction 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 timerFuncif (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。最終,會調(diào)用 watcher.run 更新頁面。

至此更新流程完畢。

寫在最后

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

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

簡化后的代碼我已放在github,有需要的可以看看。

以上就是詳細(xì)分析vue響應(yīng)式原理的詳細(xì)內(nèi)容,更多關(guān)于Vue響應(yīng)式原理的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Vue
相關(guān)文章:
主站蜘蛛池模板: 国产成人福利美女观看视频 | 国产女人一区二区 | 亚洲视频一区网站 | 国产麻豆精品一区二区 | 国产亚洲欧美日韩在线观看一区二区 | 国内自拍一二三四2021 | 欧美一区二区三区国产精品 | 亚洲欧美日韩精品高清 | 国产成人a视频在线观看 | 日日噜噜夜夜狠狠视频无 | 国产粗大猛烈18p | 麻豆入口视频在线观看 | 中文字幕制服 | 国产精品13页 | 欧洲欧美人成免费观看 | 97国内精品久久久久久久影视 | 国产福利视频一区美女 | 久久er99热精品一区二区 | 国产大片免费观看网站 | 色一情一区二区三区四区 | 亚洲综合一区二区三区四区 | 色婷婷国产 | 国产 麻豆 欧美亚洲综合久久 | 日韩电影中文 | 欧美a级黄色大片 | 国产精品日韩欧美在线第3页 | 高清视频一区二区三区 | 欧美日韩一区二区三区四区在线观看 | 国产特级毛片aaaaaa | 国产精品欧美日韩精品 | 丝袜美女被出水视频一区 | 欧美性生交大片 | 国产美女精品在线观看 | 欧美第一区 | 高清免费a级在线观看国产 高清免费毛片 | 久久曰 | www亚洲成人 | jiucao在线看片www | 亚洲日韩中文字幕天堂不卡 | 亚洲欧洲日韩国产一区二区三区 | 成年人午夜 |