zoukankan      html  css  js  c++  java
  • 【面试题解析✨】Vue 的数据是如何渲染到页面的?

    面试的时候,面试官经常会问 Vue 双向绑定的原理是什么我猜大部分人会跟我一样,不假思索的回答利用 Object.defineProperty 实现的。

    其实这个回答很笼统,而且也没回答完整?Vue 中 Object.defineProperty 只是对数据做了劫持,具体的如何渲染到页面上,并没有考虑到。接下来从初始化开始,看看 Vue 都做了什么事情。

    前提知识


    在读源码前,需要了解 Object.defineProperty 的使用,以及 Vue Dep 的用法。这里就简单带过,各位大佬可以直接跳过,进行源码分析。

    Object.defineProperty

    当使用 Object.defineProperty 对对象的属性进行拦截时,调用该对象的属性,则会调用 get 函数,属性值则是 get 函数的返回值。当修改属性值时,则会调用 set 函数。

    当然也可以通过 Object.defineProperty 给对象添加属性值,Vue 中就是通过这个方法将 datacomputed 等属性添加到 vm 上。

     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 函数,重新求值并重新收集依赖。

    源码分析


    先看看 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)}
    ` +
     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 点:

    1、 new Vue 初始化数据等

    2、$mount 将 render、template 或 el 转为 render 函数

    3、生成一个渲染 Watcher 收集依赖,并将执行 render 函数生成 vnode 传递给 patch 函数执行,渲染页面。

    4、当渲染 Watcher 依赖发生变化时,执行 Watcher 的 getter 函数,重新依赖收集。并且重新执行 render 函数生成 vnode 传递给 patch 函数进行页面的更新。

    侵删,点击跳转原文

  • 相关阅读:
    sync_with_stdio(false)和cin.tie(NULL)
    会场安排问题(贪心 两种方法)
    面向对象分析和设计笔记——第6章界面组件
    用Java实现文件复制
    面向对象分析和设计笔记——第5章输入输出
    面向对象分析和设计笔记——第4章设计模式
    常规类、抽象类和接口的对比分析
    使用for-each循环的三种情况
    StringTokenizer类
    String类的常用方法
  • 原文地址:https://www.cnblogs.com/Object-L/p/12574853.html
Copyright © 2011-2022 走看看