【面試題解析✨】Vue 的數據是如何渲染到頁面的?

面試的時候,面試官常常會問 Vue 雙向綁定的原理是什麼我猜大部分人會跟我同樣,不假思索的回答利用 Object.defineProperty 實現的。html

其實這個回答很籠統,並且也沒回答完整?Vue 中 Object.defineProperty 只是對數據作了劫持,具體的如何渲染到頁面上,並無考慮到。接下來從初始化開始,看看 Vue 都作了什麼事情。vue

 

前提知識

 


在讀源碼前,須要瞭解 Object.defineProperty 的使用,以及 Vue Dep 的用法。這裏就簡單帶過,各位大佬能夠直接跳過,進行源碼分析。node

 

Object.defineProperty

 

當使用 Object.defineProperty 對對象的屬性進行攔截時,調用該對象的屬性,則會調用 get 函數,屬性值則是 get 函數的返回值。當修改屬性值時,則會調用 set 函數。react

 

固然也能夠經過 Object.defineProperty 給對象添加屬性值,Vue 中就是經過這個方法將 datacomputed 等屬性添加到 vm 上。web

 1 Object.defineProperty(obj, key, {
 2   enumerable: true,
 3   configurable: true,
 4   get: function reactiveGetter () {
 5     const value = getter ? getter.call(obj) : val
 6     // 用於依賴收集,Dep 中講到
 7     if (Dep.target) {
 8       dep.depend()
 9       if (childOb) {
10         childOb.dep.depend()
11         if (Array.isArray(value)) {
12           dependArray(value)
13         }
14       }
15     }
16     return value
17   },
18   set: function reactiveSetter (newVal) {
19     val = newVal
20     // val 發生變化時,發出通知,Dep 中講到
21     dep.notify()
22   }
23 })

Dep

這裏不講什麼設計模式了,直接看代碼。面試

 1 let uid = 0
 2 
 3 export default class Dep {
 4   static target: ?Watcher;
 5   id: number;
 6   subs: Array<Watcher>;
 7 
 8   constructor () {
 9     this.id = uid++
10     this.subs = []
11   }
12 
13   addSub (sub: Watcher) {
14     // 添加 Watcher
15     this.subs.push(sub)
16   }
17 
18   removeSub (sub: Watcher) {
19     // 從列表中移除某個 Watcher
20     remove(this.subs, sub)
21   }
22 
23   depend () {
24     // 當 target 存在時,也就是目標 Watcher 存在的時候,
25     // 就能夠爲這個目標 Watcher 收集依賴
26     // Watcher 的 addDep 方法在下文中
27     if (Dep.target) {
28       Dep.target.addDep(this)
29     }
30   }
31 
32   notify () {
33     // 對 Watcher 進行排序
34     const subs = this.subs.slice()
35     if (process.env.NODE_ENV !== 'production' && !config.async) {
36       subs.sort((a, b) => a.id - b.id)
37     }
38     // 當該依賴發生變化時, 調用添加到列表中的 Watcher 的 update 方法進行更新
39     for (let i = 0, l = subs.length; i < l; i++) {
40       subs[i].update()
41     }
42   }
43 }
44 
45 // target 爲某個 Watcher 實例,一次只能爲一個 Watcher 收集依賴
46 Dep.target = null
47 // 經過堆棧存放 Watcher 實例,
48 // 當某個 Watcher 的實例未收集完,又有新的 Watcher 實例須要收集依賴,
49 // 那麼舊的 Watcher 就先存放到 targetStack,
50 // 等待新的 Watcher 收集完後再爲舊的 Watcher 收集
51 // 配合下面的 pushTarget 和 popTarget 實現
52 const targetStack = []
53 
54 export function pushTarget (target: ?Watcher) {
55   targetStack.push(target)
56   Dep.target = target
57 }
58 
59 export function popTarget () {
60   targetStack.pop()
61   Dep.target = targetStack[targetStack.length - 1]
62 }

當某個 Watcher 須要依賴某個 dep 時,那麼調用 dep.addSub(Watcher) 便可,當 dep 發生變化時,調用 dep.notify() 就能夠觸發 Watcher 的 update 方法。接下來看看 Vue 中 Watcher 的實現。設計模式

 1 class Watcher {
 2   // 不少屬性,這裏省略
 3   ...
 4   // 構造函數
 5   constructor (
 6     vm: Component,
 7     expOrFn: string | Function,
 8     cb: Function,
 9     options?: ?Object,
10     isRenderWatcher?: boolean
11   ) { ... }
12   
13   get () {
14     // 當執行 Watcher 的 get 函數時,會將當前的 Watcher 做爲 Dep 的 target
15     pushTarget(this)
16     let value
17     const vm = this.vm
18     try {
19       // 在執行 getter 時,當遇到響應式數據,會觸發上面講到的 Object.defineProperty 中的 get 函數
20       // Vue 就是在 Object.defineProperty 的 get 中調用 dep.depend() 進行依賴收集。
21       value = this.getter.call(vm, vm)
22     } catch (e) {
23       ...
24     } finally {
25       ...
26       // 當前 Watcher 的依賴收集完後,調用 popTarget 更換 Watcher
27       popTarget()
28       this.cleanupDeps()
29     }
30     return value
31   }
32   
33   // dep.depend() 收集依賴時,會通過 Watcher 的 addDep 方法
34   // addDep 作了判斷,避免重複收集,而後調用 dep.addSub 將該 Watcher 添加到 dep 的 subs 中
35   addDep (dep: Dep) {
36     const id = dep.id
37     if (!this.newDepIds.has(id)) {
38       this.newDepIds.add(id)
39       this.newDeps.push(dep)
40       if (!this.depIds.has(id)) {
41         dep.addSub(this)
42       }
43     }
44   }
45 }

經過 Object.defineProperty 中的 getDep 的 depend 以及 Watcher 的 addDep 這三個函數的配合,完成了依賴的收集,就是將 Watcher 添加到 dep 的 subs 列表中。數組

 

當依賴發生變化時,就會調用 Object.defineProperty 中的 set,在 set 中調用 dep 的 notify,使得 subs 中的每一個 Watcher 都執行 update 函數。瀏覽器

 

Watcher 中的 update 最終會從新調用 get 函數,從新求值並從新收集依賴。app

 

源碼分析

 


先看看 new Vue 都作了什麼?

 1 // vue/src/core/instance/index.js
 2 function Vue (options) {
 3   if (process.env.NODE_ENV !== 'production' &&
 4     !(this instanceof Vue)
 5   ) {
 6     // 只能使用 new Vue 調用該方法,不然輸入警告
 7     warn('Vue is a constructor and should be called with the `new` keyword')
 8   }
 9   // 開始初始化
10   this._init(options)
11 }

_init 方法經過原型掛載在 Vue 上

 1 // vue/src/core/instance/init.js
 2 export function initMixin (Vue: Class<Component>) {
 3   Vue.prototype._init = function (options?: Object) {
 4     const vm: Component = this
 5     // a uid
 6     vm._uid = uid++
 7 
 8     let startTag, endTag
 9     // 初始化前打點,用於記錄 Vue 實例初始化所消耗的時間
10     if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
11       startTag = `vue-perf-start:${vm._uid}`
12       endTag = `vue-perf-end:${vm._uid}`
13       mark(startTag)
14     }
15 
16     // a flag to avoid this being observed
17     vm._isVue = true
18     // 合併參數到 $options
19     if (options && options._isComponent) {
20       initInternalComponent(vm, options)
21     } else {
22       vm.$options = mergeOptions(
23         resolveConstructorOptions(vm.constructor),
24         options || {},
25         vm
26       )
27     }
28 
29     if (process.env.NODE_ENV !== 'production') {
30       // 非生產環境以及支持 Proxy 的瀏覽器中,對 vm 的屬性進行劫持,並將代理後的 vm 賦值給 _renderProxy
31       // 當調用 vm 不存在的屬性時,進行錯誤提示。
32       // 在不支持 Proxy 的瀏覽器中,_renderProxy = vm; 爲了簡單理解,就當作等同於 vm
33 
34       // 代碼在 src/core/instance/proxy.js
35       initProxy(vm)
36     } else {
37       vm._renderProxy = vm
38     }
39     // expose real self
40     vm._self = vm
41     // 初始化聲明周期函數
42     initLifecycle(vm)
43     // 初始化事件
44     initEvents(vm)
45     // 初始化 render 函數
46     initRender(vm)
47     // 觸發 beforeCreate 鉤子
48     callHook(vm, 'beforeCreate')
49     // 初始化 inject
50     initInjections(vm) // resolve injections before data/props
51     // 初始化 data/props 等
52     // 經過 Object.defineProperty 對數據進行劫持
53     initState(vm)
54     // 初始化 provide
55     initProvide(vm) // resolve provide after data/props
56     // 數據處理完後,觸發 created 鉤子
57     callHook(vm, 'created')
58 
59     // 從 new Vue 到 created 所消耗的時間
60     if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
61       vm._name = formatComponentName(vm, false)
62       mark(endTag)
63       measure(`vue ${vm._name} init`, startTag, endTag)
64     }
65 
66     // 若是 options 有 el 參數則進行 mount
67     if (vm.$options.el) {
68       vm.$mount(vm.$options.el)
69     }
70   }
71 }

接下來進入 $mount,由於用的是完整版的 Vue,直接看 vue/src/platforms/web/entry-runtime-with-compiler.js 這個文件。

 1 // vue/src/platforms/web/entry-runtime-with-compiler.js
 2 // 首先將 runtime 中的 $mount 方法賦值給 mount 進行保存
 3 const mount = Vue.prototype.$mount
 4 // 重寫 $mount,對 template 編譯爲 render 函數後再調用 runtime 的 $mount
 5 Vue.prototype.$mount = function (
 6   el?: string | Element,
 7   hydrating?: boolean
 8 ): Component {
 9   el = el && query(el)
10 
11   // 掛載元素不容許爲 body 或 html
12   if (el === document.body || el === document.documentElement) {
13     process.env.NODE_ENV !== 'production' && warn(
14       `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
15     )
16     return this
17   }
18 
19   const options = this.$options
20   if (!options.render) {
21     let template = options.template
22     // render 函數不存在時,將 template 轉化爲 render 函數
23     // 具體就不展開了
24     ...
25     if (template) {
26         ...
27     } else if (el) {
28       // template 不存在,則將 el 轉成 template
29       // 從這裏能夠看出 Vue 支持 render、template、el 進行渲染
30       template = getOuterHTML(el)
31     }
32     if (template) {
33       const { render, staticRenderFns } = compileToFunctions(template, {
34         outputSourceRange: process.env.NODE_ENV !== 'production',
35         shouldDecodeNewlines,
36         shouldDecodeNewlinesForHref,
37         delimiters: options.delimiters,
38         comments: options.comments
39       }, this)
40       options.render = render
41       options.staticRenderFns = staticRenderFns
42     }
43   }
44   // 調用 runtime 中 $mount
45   return mount.call(this, el, hydrating)
46 }

查看 runtime 中的 $mount

1 // vue/src/platforms/web/runtime/index.js
2 Vue.prototype.$mount = function (
3   el?: string | Element,
4   hydrating?: boolean
5 ): Component {
6   el = el && inBrowser ? query(el) : undefined
7   return mountComponent(this, el, hydrating)
8 }

mountComponent 定義在 vue/src/core/instance/lifecycle.js 中

 1 // vue/src/core/instance/lifecycle.js
 2 export function mountComponent (
 3   vm: Component,
 4   el: ?Element,
 5   hydrating?: boolean
 6 ): Component {
 7   vm.$el = el
 8   if (!vm.$options.render) {
 9     // 未定義 render 函數時,將 render 賦值爲 createEmptyVNode 函數
10     vm.$options.render = createEmptyVNode
11     if (process.env.NODE_ENV !== 'production') {
12       /* istanbul ignore if */
13       if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
14         vm.$options.el || el) {
15         // 用了 Vue 的 runtime 版本,而沒有 render 函數時,報錯處理
16         warn(
17           'You are using the runtime-only build of Vue where the template ' +
18           'compiler is not available. Either pre-compile the templates into ' +
19           'render functions, or use the compiler-included build.',
20           vm
21         )
22       } else {
23         // template 和 render 都未定義時,報錯處理
24         warn(
25           'Failed to mount component: template or render function not defined.',
26           vm
27         )
28       }
29     }
30   }
31   // 調用 beforeMount 鉤子
32   callHook(vm, 'beforeMount')
33   // 定義 updateComponent 函數
34   let updateComponent
35   /* istanbul ignore if */
36   if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
37     // 須要作監控性能時,在 updateComponent 內加入打點的操做
38     updateComponent = () => {
39       const name = vm._name
40       const id = vm._uid
41       const startTag = `vue-perf-start:${id}`
42       const endTag = `vue-perf-end:${id}`
43 
44       mark(startTag)
45       const vnode = vm._render()
46       mark(endTag)
47       measure(`vue ${name} render`, startTag, endTag)
48 
49       mark(startTag)
50       vm._update(vnode, hydrating)
51       mark(endTag)
52       measure(`vue ${name} patch`, startTag, endTag)
53     }
54   } else {
55     updateComponent = () => {
56       // updateComponent 主要調用 _update 進行瀏覽器渲染
57       // _render 返回 VNode
58       // 先繼續往下看,等會再回來看這兩個函數
59       vm._update(vm._render(), hydrating)
60     }
61   }
62 
63   // new 一個渲染 Watcher
64   new Watcher(vm, updateComponent, noop, {
65     before () {
66       if (vm._isMounted && !vm._isDestroyed) {
67         callHook(vm, 'beforeUpdate')
68       }
69     }
70   }, true /* isRenderWatcher */)
71   hydrating = false
72 
73   // 掛載完成,觸發 mounted
74   if (vm.$vnode == null) {
75     vm._isMounted = true
76     callHook(vm, 'mounted')
77   }
78   return vm
79 }

先繼續往下看,看看 new Watcher 作了什麼,再回過頭看 updateComponent 中的 _update 和 _render

 

Watcher 的構造函數以下

 1 // vue/src/core/observer/watcher.js
 2 constructor (
 3   vm: Component,
 4   expOrFn: string | Function,
 5   cb: Function,
 6   options?: ?Object,
 7   isRenderWatcher?: boolean
 8 ) {
 9   this.vm = vm
10   if (isRenderWatcher) {
11     vm._watcher = this
12   }
13   vm._watchers.push(this)
14   // options
15   if (options) {
16     this.deep = !!options.deep
17     this.user = !!options.user
18     this.lazy = !!options.lazy
19     this.sync = !!options.sync
20     this.before = options.before
21   } else {
22     this.deep = this.user = this.lazy = this.sync = false
23   }
24   this.cb = cb
25   this.id = ++uid // uid for batching
26   this.active = true
27   this.dirty = this.lazy // for lazy watchers
28   ...
29   // expOrFn 爲上文的 updateComponent 函數,賦值給 getter
30   if (typeof expOrFn === 'function') {
31     this.getter = expOrFn
32   } else {
33     this.getter = parsePath(expOrFn)
34     if (!this.getter) {
35       this.getter = noop
36       ...
37     }
38   }
39   // lazy 爲 false,調用 get 方法
40   this.value = this.lazy
41     ? undefined
42     : this.get()
43 }
44 
45 // 執行 getter 函數,getter 函數爲 updateComponent,並收集依賴
46 get () {
47   pushTarget(this)
48   let value
49   const vm = this.vm
50   try {
51     value = this.getter.call(vm, vm)
52   } catch (e) {
53     ...
54   } finally {
55     if (this.deep) {
56       traverse(value)
57     }
58     popTarget()
59     this.cleanupDeps()
60   }
61   return value
62 }

new Watcher 後會調用 updateComponent 函數,上文中 updateComponent 內執行了 vm._update_update 執行前會經過 _render 得到 vnode,接下里看看 _update 作了什麼。_update 定義在 vue/src/core/instance/lifecycle.js

 1 // vue/src/core/instance/lifecycle.js
 2 Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
 3   const vm: Component = this
 4   const prevVnode = vm._vnode
 5   vm._vnode = vnode
 6   ...
 7   
 8   if (!prevVnode) {
 9     // 初始渲染
10     vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
11   } else {
12     // 更新 vnode
13     vm.$el = vm.__patch__(prevVnode, vnode)
14   }
15   ...
16 }

接下來到了 __patch__ 函數進行頁面渲染。

1 // vue/src/platforms/web/runtime/index.js
2 import { patch } from './patch'
3 Vue.prototype.__patch__ = inBrowser ? patch : noop
1 // vue/src/platforms/web/runtime/patch.js
2 import { createPatchFunction } from 'core/vdom/patch'
3 export const patch: Function = createPatchFunction({ nodeOps, modules })

createPatchFunction 提供了不少操做 virtual dom 的方法,最終會返回一個 path 函數。

 1 export function createPatchFunction (backend) {
 2   ...
 3   // oldVnode 表明舊的節點,vnode 表明新的節點
 4   return function patch (oldVnode, vnode, hydrating, removeOnly) {
 5     // vnode 爲 undefined, oldVnode 不爲 undefined 則須要執行 destroy
 6     if (isUndef(vnode)) {
 7       if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
 8       return
 9     }
10 
11     let isInitialPatch = false
12     const insertedVnodeQueue = []
13 
14     if (isUndef(oldVnode)) {
15       // oldVnode 不存在,表示初始渲染,則根據 vnode 建立元素
16       isInitialPatch = true
17       createElm(vnode, insertedVnodeQueue)
18     } else {
19       
20       const isRealElement = isDef(oldVnode.nodeType)
21       if (!isRealElement && sameVnode(oldVnode, vnode)) {
22         // oldVnode 與 vnode 爲相同節點,調用 patchVnode 更新子節點
23         patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
24       } else {
25         if (isRealElement) {
26           // 服務端渲染的處理
27           ...
28         }
29         // 其餘操做
30         ...
31       }
32     }
33 
34     invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
35     // 最終渲染到頁面上
36     return vnode.elm
37   }
38 }

當渲染 Watcher 的依賴的數據發生變化時,會觸發 Object.defineProperty 中的 set 函數。

 

從而調用 dep.notify() 通知該 Watcher 進行 update 操做。最終達到數據改變時,自動更新頁面。 Watcher 的 update 函數就再也不展開了,有興趣的小夥伴能夠自行查看。

 

最後再回過頭看看前面遺留的 _render 函數。

 
 
1 updateComponent = () => {
2   vm._update(vm._render(), hydrating)
3 }

以前說了 _render 函數會返回 vnode,看看具體作了什麼吧。

 1 // vue/src/core/instance/render.js
 2 Vue.prototype._render = function (): VNode {
 3   const vm: Component = this
 4   // 從 $options 取出 render 函數以及 _parentVnode
 5   // 這裏的 render 函數能夠是 template 或者 el 編譯的
 6   const { render, _parentVnode } = vm.$options
 7 
 8   if (_parentVnode) {
 9     vm.$scopedSlots = normalizeScopedSlots(
10       _parentVnode.data.scopedSlots,
11       vm.$slots,
12       vm.$scopedSlots
13     )
14   }
15 
16   vm.$vnode = _parentVnode
17   let vnode
18   try {
19     currentRenderingInstance = vm
20     // 最終會執行 $options 中的 render 函數
21     // _renderProxy 能夠看作 vm
22     // 將 vm.$createElement 函數傳遞給 render,也就是常常看到的 h 函數
23     // 最終生成 vnode
24     vnode = render.call(vm._renderProxy, vm.$createElement)
25   } catch (e) {
26     // 異常處理
27     ...
28   } finally {
29     currentRenderingInstance = null
30   }
31 
32   // 若是返回的數組只包含一個節點,則取第一個值
33   if (Array.isArray(vnode) && vnode.length === 1) {
34     vnode = vnode[0]
35   }
36   
37   // vnode 若是不是 VNode 實例,報錯並返回空的 vnode
38   if (!(vnode instanceof VNode)) {
39     if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
40       warn(
41         'Multiple root nodes returned from render function. Render function ' +
42         'should return a single root node.',
43         vm
44       )
45     }
46     vnode = createEmptyVNode()
47   }
48   // 設置父節點
49   vnode.parent = _parentVnode
50   // 最終返回 vnode
51   return vnode
52 }

接下來就是看 vm.$createElement 也就是 render 函數中的 h

1 // vue/src/core/instance/render.js
2 import { createElement } from '../vdom/create-element'
3 export function initRender (vm: Component) {
4   ...
5   vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
6   vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
7   ...
8 }
  1 // vue/src/core/vdom/create-element.js
  2 export function createElement (
  3   context: Component,
  4   tag: any,
  5   data: any,
  6   children: any,
  7   normalizationType: any,
  8   alwaysNormalize: boolean
  9 ): VNode | Array<VNode> {
 10   // data 是數組或簡單數據類型,表明 data 沒傳,將參數值賦值給正確的變量
 11   if (Array.isArray(data) || isPrimitive(data)) {
 12     normalizationType = children
 13     children = data
 14     data = undefined
 15   }
 16   if (isTrue(alwaysNormalize)) {
 17     normalizationType = ALWAYS_NORMALIZE
 18   }
 19   // 將正確的參數傳遞給 _createElement
 20   return _createElement(context, tag, data, children, normalizationType)
 21 }
 22 
 23 export function _createElement (
 24   context: Component,
 25   tag?: string | Class<Component> | Function | Object,
 26   data?: VNodeData,
 27   children?: any,
 28   normalizationType?: number
 29 ): VNode | Array<VNode> {
 30   if (isDef(data) && isDef((data: any).__ob__)) {
 31     // render 函數中的 data 不能爲響應式數據
 32     process.env.NODE_ENV !== 'production' && warn(
 33       `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
 34       'Always create fresh vnode data objects in each render!',
 35       context
 36     )
 37     // 返回空的 vnode 節點
 38     return createEmptyVNode()
 39   }
 40   // 用 is 指定標籤
 41   if (isDef(data) && isDef(data.is)) {
 42     tag = data.is
 43   }
 44   if (!tag) {
 45     // in case of component :is set to falsy value
 46     return createEmptyVNode()
 47   }
 48   // key 值不是簡單數據類型時,警告提示
 49   if (process.env.NODE_ENV !== 'production' &&
 50     isDef(data) && isDef(data.key) && !isPrimitive(data.key)
 51   ) { ... }
 52 
 53   if (Array.isArray(children) &&
 54     typeof children[0] === 'function'
 55   ) {
 56     data = data || {}
 57     data.scopedSlots = { default: children[0] }
 58     children.length = 0
 59   }
 60   // 處理子節點
 61   if (normalizationType === ALWAYS_NORMALIZE) {
 62     // VNode 數組
 63     children = normalizeChildren(children)
 64   } else if (normalizationType === SIMPLE_NORMALIZE) {
 65     children = simpleNormalizeChildren(children)
 66   }
 67   
 68   // 生成 vnode
 69   let vnode, ns
 70   if (typeof tag === 'string') {
 71     let Ctor
 72     ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
 73     if (config.isReservedTag(tag)) {
 74       ...
 75       vnode = new VNode(
 76         config.parsePlatformTagName(tag), data, children,
 77         undefined, undefined, context
 78       )
 79     } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
 80       vnode = createComponent(Ctor, data, context, children, tag)
 81     } else {
 82       vnode = new VNode(
 83         tag, data, children,
 84         undefined, undefined, context
 85       )
 86     }
 87   } else {
 88     vnode = createComponent(tag, data, context, children)
 89   }
 90   
 91   // 返回 vnode
 92   if (Array.isArray(vnode)) {
 93     return vnode
 94   } else if (isDef(vnode)) {
 95     if (isDef(ns)) applyNS(vnode, ns)
 96     if (isDef(data)) registerDeepBindings(data)
 97     return vnode
 98   } else {
 99     return createEmptyVNode()
100   }
101 }

總結

 


代碼看起來不少,其實主要流程能夠分爲如下 4 點:

 

一、 new Vue 初始化數據等

二、$mount 將 render、template 或 el 轉爲 render 函數

三、生成一個渲染 Watcher 收集依賴,並將執行 render 函數生成 vnode 傳遞給 patch 函數執行,渲染頁面。

四、當渲染 Watcher 依賴發生變化時,執行 Watcher 的 getter 函數,從新依賴收集。而且從新執行 render 函數生成 vnode 傳遞給 patch 函數進行頁面的更新。

 

侵刪,點擊跳轉原文

相關文章
相關標籤/搜索