导航

[[深入01] 执行上下文](https://juejin.im/post/684490...)
[[深入02] 原型链](https://juejin.im/post/684490...)
[[深入03] 继承](https://juejin.im/post/684490...)
[[深入04] 事件循环](https://juejin.im/post/684490...)
[[深入05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490...)
[[深入06] 隐式转换 和 运算符](https://juejin.im/post/684490...)
[[深入07] 浏览器缓存机制(http缓存机制)](https://juejin.im/post/684490...)
[[深入08] 前端安全](https://juejin.im/post/684490...)
[[深入09] 深浅拷贝](https://juejin.im/post/684490...)
[[深入10] Debounce Throttle](https://juejin.im/post/684490...)
[[深入11] 前端路由](https://juejin.im/post/684490...)
[[深入12] 前端模块化](https://juejin.im/post/684490...)
[[深入13] 观察者模式 发布订阅模式 双向数据绑定](https://juejin.im/post/684490...)
[[深入14] canvas](https://juejin.im/post/684490...)
[[深入15] webSocket](https://juejin.im/post/684490...)
[[深入16] webpack](https://juejin.im/post/684490...)
[[深入17] http 和 https](https://juejin.im/post/684490...)
[[深入18] CSS-interview](https://juejin.im/post/684490...)
[[深入19] 手写Promise](https://juejin.im/post/684490...)
[[深入20] 手写函数](https://juejin.im/post/684490...)

[[react] Hooks](https://juejin.im/post/684490...)

[[部署01] Nginx](https://juejin.im/post/684490...)
[[部署02] Docker 部署vue项目](https://juejin.im/post/684490...)
[[部署03] gitlab-CI](https://juejin.im/post/684490...)

[[源码-webpack01-前置知识] AST抽象语法树](https://juejin.im/post/684490...)
[[源码-webpack02-前置知识] Tapable](https://juejin.im/post/684490...)
[[源码-webpack03] 手写webpack - compiler简单编译流程](https://juejin.im/post/684490...)
[[源码] Redux React-Redux01](https://juejin.im/post/684490...)
[[源码] axios ](https://juejin.im/post/684490...)
[[源码] vuex ](https://juejin.im/post/684490...)
[[源码-vue01] data响应式 和 初始化渲染 ](https://juejin.im/post/684490...)
[[源码-vue02] computed 响应式 - 初始化,访问,更新过程 ](https://juejin.im/post/684490...)
[[源码-vue03] watch 侦听属性 - 初始化和更新 ](https://juejin.im/post/684490...)
[[源码-vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490...)
[[源码-vue05] Vue.extend ](https://juejin.im/post/684490...)

[[源码-vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790...)

前置知识

Watcher类

  • Watcher分为三种

    • computed watcher - 负责更新computed
    • user-watcher - watch 一个函数
    • render-watcher - 重新渲染
  • 三种watcher有固定的执行顺序

    • computed watcher -> user watcher -> render watcher
    • 这样安排三种 watcher 的顺序的原因?

      • computed watcher 在 render watcher 前面执行,就能保证在视图渲染的时候拿到的是最新的computed

(1) data响应式

  • <font color=blue size=5>data响应式具体过程</font>

    1. new Vue(options)构造函数中调用this._init(options)方法,而this._init(options)方法是在initMixin(Vue)中定义的
    2. Vue.prototype._init => 这里主要关注 initState(vm)

      • 合并 options 对象
      • 调用 initProxy
      • 调用 initState(vm)
      • 其他数据的初始化
      • vm.$mount(vm.$options.el) 初始化渲染
    3. initState(vm) => 这里主要关注 initData

      • initProps
      • initMethods
      • initData
      • initComputed
      • initWatch
    4. initData

      • 传入的options对象的data,是函数就调用返回对象,是对象就直接使用
      • 如果props,method,data中有相同的key值就抛出警告
      • proxy(vm, _data, key)

        • 主要的作用就是给data中的属性做一层代理
        • 通过访问 this.name = vm.name = vm._data.name = vm.$options.data.name = vm.data.name
      • observe(data, true)
    5. observe(data, true)

      • 判断data是否具有__ob__属性,该属性表示data是否观察过了,即具有响应式了
      • 没有 __ob__属性,就就行观测,执行 new Observer(value)
    6. new Observer(value)

      • 给data添加__ob__属性,值是当前的 observer 实例
      • data是数组

        • 具有原型执行 protoAugment(value, arrayMethods)

          • 重写数组原型上的 7 种方法
          • push pop unshift shift splice sort reverse 这7种都改变数组
          • push unshift splice 添加的属性包装成数组

              1. 继续执行 ob.observeArray(inserted) 循环遍历添加的每一项属性,执行第5步中的observe(items[i])
              1. 并调用ob.dep.notify()执行watcher种的upate去更新视图,从而是这些重写的数组方法具有响应式
        • 不具有有原型执行 copyAugment(value, arrayMethods, arrayKeys)
        • this.observeArray(value)

          • 循环遍历数组种的每一个成员,执行第5步中的observe(items[i])
      • data是对象

        • this.walk(value)
    7. this.walk(value)

      • defineReactive(obj, keys[i])
    8. defineReactive(obj, keys[i])

      • Object.defineProperty

        • get

          • dep.depend()
        • set

          • dep.notify()
  • 源码
  • initState - src/core/instance/state.js

    initState - src/core/instance/state.js
    ---
    
    export function initState (vm: Component) {
    vm._watchers = []
    const opts = vm.$options // opts 获取vm中的 options 参数
    if (opts.props) initProps(vm, opts.props) // props存在,就初始化 props
    if (opts.methods) initMethods(vm, opts.methods)// methods存在,就初始化 methods
    
    if (opts.data) {
      // data存在,初始化 data
      initData(vm)
    } else {
      // data不存在
      observe(vm._data = {}, true /* asRootData */)
    }
    
    if (opts.computed) initComputed(vm, opts.computed) // computed存在,初始化 computed
    if (opts.watch && opts.watch !== nativeWatch) {
        // watch存在,并且不是原生的对象上的watch属性,就初始化 watch
        // 分为三种 watcher
            // render watcher
            // compute watcher
            // user watcher - watch
      initWatch(vm, opts.watch)
    }
    }
  • initData - src/core/instance/state.js

    initData - src/core/instance/state.js
    ---
    
    function initData (vm: Component) {
    let data = vm.$options.data
    // 获取传入的options对象上的data属性
    // 注意:这里的vm.$options在不是组件的情况下,是合并后的options
    
    data = vm._data = typeof data === 'function'
      ? getData(data, vm)
      : data || {}
    // data是一个函数就调用取返回值,是对象就直接赋值
      // vm._data = vm.$options.data
    
    if (!isPlainObject(data)) {
      // 不是一个对象,不是一个纯对象赋值空对象
      data = {}
      process.env.NODE_ENV !== 'production' && warn(
        'data functions should return an object:\n' +
        'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        vm
      )
    }
    // 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 (process.env.NODE_ENV !== 'production') {
        if (methods && hasOwn(methods, key)) {
          warn(
            `Method "${key}" has already been defined as a data property.`,
            vm
          )
          // method中存在了该key,所以data中不能再有相同的key
        }
      }
      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
          // props中存在了该key,所以data中不能再有相同的key
        )
      } else if (!isReserved(key)) {
        // props和methods中都不存在该key,就执行代理proxy函数
        proxy(vm, `_data`, key)
      }
    }
    // observe data
    // data的响应式
    observe(data, true /* asRootData */)
    }
  • proxy - src/core/instance/state.js

    proxy - src/core/instance/state.js
    ---
    
    export function proxy (target: Object, sourceKey: string, key: string) {
    // proxy(vm, `_data`, key)
    sharedPropertyDefinition.get = function proxyGetter () {
      return this[sourceKey][key]
      // 1. 重写 get
      // 2. 返回 this._data[key]
      // 3. this指向:sharedPropertyDefinition.get方法是通过 vm.key 来调用的,this指向vm
    }
    sharedPropertyDefinition.set = function proxySetter (val) {
      this[sourceKey][key] = val
      // 1. 重写 set
      // 2. this._data[key] = val
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
    // vm[key] = vm._data[key]
    // 因为: vm._data = vm.$options.data
    // 所以:vm[key] = vm._data[key] =  vm.$options.data[key]
    }
  • observe - src/core/observer/index.js

    observe - src/core/observer/index.js
    ---
    
    export function  observe (value: any, asRootData: ?boolean): Observer | void  {
    // 1. observe(vm._data = {}, true /* asRootData */)
    // 2. observe(data, true /* asRootData */)
    if (!isObject(value) || value instanceof VNode) {
      // 不是一个对象 或者 是VNode 
      return
    }
    let ob: Observer | void
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
      // 如果value具有__ob__属性 并且 __ob__ 是Observer的实例,就直接赋值
      // 即已经观测过了, 直接赋值
      ob = value.__ob__
    } else if (
      shouldObserve &&
      !isServerRendering() &&
      (Array.isArray(value) || isPlainObject(value)) &&
      Object.isExtensible(value) &&
      !value._isVue
    ) {
      ob = new Observer(value)
      // 生成一个ob实例
    }
    if (asRootData && ob) {
      // 如果是根data即new Vue()初始化的时候传入的data
      // 并且 ob 存在
      // vmCount++
        // 即统计被生成的次数
      ob.vmCount++
    }
    return ob
    }
  • Observer - src/core/observer/index.js

    Observer  - src/core/observer/index.js
    ---
    
    export class Observer {
    value: any;
    dep: Dep;
    vmCount: number; // number of vms that have this object as root $data
    
    constructor (value: any) {
      this.value = value // 赋值传入的对象
      this.dep = new Dep() // dep实例
      this.vmCount = 0
      def(value, '__ob__', this)
      // def
      // 给 ( value ) 添加 ( __ob__ ) 属性,值是 ( observer ) 实例
        // function def (obj: Object, key: string, val: any, enumerable?: boolean) {
        //   Object.defineProperty(obj, key, {
        //     value: val,
        //     enumerable: !!enumerable,
        //     writable: true,
        //     configurable: true
        //   })
        // }
      if (Array.isArray(value)) {
        if (hasProto) {
          // const hasProto = '__proto__' in {}
          // 是数组,并且本身具有原型属性__proto__
          protoAugment(value, arrayMethods)
          // protoAugment 重写原型
            // value.__proto__ = arrayMethods
          // arrayMethods
            // const arrayMethods = Object.create(arrayProto)
            // const arrayProto = Array.prototype
            //  def(arrayMethods, method, function mutator (...args){})
            // 重写 7 种数组原型上的方法
        } else {
          // 是数组,并且本身不具有原型,复制原型上的每一个属性
          copyAugment(value, arrayMethods, arrayKeys)
          // function copyAugment (target: Object, src: Object, keys: Array<string>) {
          //   for (let i = 0, l = keys.length; i < l; i++) {
          //     const key = keys[i]
          //     def(target, key, src[key])
          //   }
          // }
    
          // const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
    
    
        }
        this.observeArray(value)
        // 观测数组
          // 1. 循环数组的每一项, 对每一项执行 observe(items[i])
      } else {
        this.walk(value)
         // 观测对象
      }
    }
    
    /**
     * Walk through all properties and convert them into
     * getter/setters. This method should only be called when
     * value type is Object.
     */
    walk (obj: Object) {
      const keys = Object.keys(obj)
      for (let i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i])
      }
    }
    
    /**
     * Observe a list of Array items.
     */
    observeArray (items: Array<any>) {
      for (let i = 0, l = items.length; i < l; i++) {
        observe(items[i])
      }
    }
    }
  • walk - src/core/observer/index.js

    walk - src/core/observer/index.js
    ---
    
    walk (obj: Object) {
      const keys = Object.keys(obj)
      for (let i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i])
      }
    }
  • defineReactive - src/core/observer/index.js

    defineReactive - src/core/observer/index.js
    ---
    
    export function defineReactive (
    obj: Object,
    key: string,
    val: any,
    customSetter?: ?Function,
    shallow?: boolean
    ) {
    const dep = new Dep()
    
    const property = Object.getOwnPropertyDescriptor(obj, key)
    if (property && property.configurable === false) {
        // 不存在 或者 属性描述对象不可以被修改,就直接返回
      return
    }
    // Object.getOwnPropertyDescriptor(obj, key) 表示获取obj.key属性属性描述对象
    
    // cater for pre-defined getter/setters
    const getter = property && property.get
    const setter = property && property.set
    if ((!getter || setter) && arguments.length === 2) {
      val = obj[key]
    }
    
    let childOb = !shallow && observe(val)
    // 继续观测对象的每一项的value值,如果还是对象就继续观察 添加响应Object.defineProperty
    
    
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        const value = getter ? getter.call(obj) : val
        if (Dep.target) {
          dep.depend()
          if (childOb) {
            childOb.dep.depend() // 循环收集依赖
            if (Array.isArray(value)) {
              // 如果每一项的key对象的value是一个数组
    
              dependArray(value)
              // 收集依赖
            }
          }
        }
        return value
      },
      set: function reactiveSetter (newVal) {
        const value = getter ? getter.call(obj) : val
        /* eslint-disable no-self-compare */
        if (newVal === value || (newVal !== newVal && value !== value)) {
          return
        }
        /* eslint-enable no-self-compare */
        if (process.env.NODE_ENV !== 'production' && customSetter) {
          customSetter()
        }
        // #7981: for accessor properties without setter
        if (getter && !setter) return
        if (setter) {
          setter.call(obj, newVal)
        } else {
          val = newVal
        }
        childOb = !shallow && observe(newVal)
        dep.notify() // ---------------------------- 通知watcher执行更新
      }
    })
    }
    
  • Dep类 - src/core/observer/dep.js

    Dep类 - src/core/observer/dep.js
    ---
    
    
    export default class Dep {
    static target: ?Watcher;
    // target一个 watcher 类型的静态属性
    
    id: number;
    // 每次new都使id+1
    // 即每次执行都自增1
    
    subs: Array<Watcher>;
    // subs数组 用来存放 wacher 
    
    constructor () {
      this.id = uid++ // id++ uid++ 每次执行都加 +1
      this.subs = [] // 初始化为空数组,存放依赖即watcher
    }
    
    addSub (sub: Watcher) {
      this.subs.push(sub)
      // 添加 watcher
    }
    
    removeSub (sub: Watcher) {
      remove(this.subs, sub)
      // 删除 watcher
    }
    
    depend () {
      if (Dep.target) { //--------------------------------------------- 重点注意 Dep.target
        // Dep.target 是当前正在执行的正在计算的 watcher,存在闭包中,相当于全局变量
        // 在下面有初始化
    
        Dep.target.addDep(this) // ------------------------------------ Dep 和 Watcher 相互关系
        // Dep.target.addDep(this)
          // 向 watcher 中添加 dep 实例
          // this参数就是dep实例
          // Dep.target 就是一个正在计算的watcher
        
        // Watcher中的 addDep
          // addDep (dep: Dep) {
          //   const id = dep.id // dep实例的id属性
          //   if (!this.newDepIds.has(id)) { // ------------ newDepIds中不存在该id
          //     this.newDepIds.add(id) // 向 ( Watcher类 ) 的 newDepIds中添加 id
          //     this.newDeps.push(dep) // 向 ( Watcher类 ) 的 newDeps 中添加 dep
          //     if (!this.depIds.has(id)) { // ------------- depIds 中不存在该id
          //       dep.addSub(this)   // 向 ( Dep类 ) 的 subs 中添加 该watcher
          //     }
          //   }
          // }
        
        // 这里操作有点绕
          // Dep.target.addDep(this) 
              // 1. 调用 Dep.target.addDep(this) = new Watcher().addDep(this)
              // 2. 把 dep 实例添加到 watcher的 newDeps 数组中
              // 3. 把 dep.id 添加到 watcher的 newDepIds 数组中
              // 4. 执行 dep.addSubs 把 watcher 添加到 Dep的 subs 数组中
      }
    }
    
    notify () {
      const subs = this.subs.slice()
      // 浅拷贝subs数组,缓存一份
      if (process.env.NODE_ENV !== 'production' && !config.async) {
        subs.sort((a, b) => a.id - b.id)
      }
    
      for (let i = 0, l = subs.length; i < l; i++) {
        subs[i].update()
        // 执行subs数组的每一个成员watcher上的update方法
      }
    }
    }
    
    
    Dep.target = null
    // 当前watcher
    
    const targetStack = []
    // 存放watcher的栈结构,后进先出
    
    export function pushTarget (target: ?Watcher) {
    targetStack.push(target)
    // watcher 入栈
    
    Dep.target = target
    // 赋值最新的watcher
    }
    
    export function popTarget () {
    targetStack.pop()
    // watcher 出栈
    
    Dep.target = targetStack[targetStack.length - 1]
    // 前一个watcher
    }
  • Watcher类 - src/core/observer/dep.js

    export default class Watcher {
    constructor (
      vm: Component, // vm实例
      expOrFn: string | Function,
      cb: Function,
      options?: ?Object,
      isRenderWatcher?: boolean // 是否是renderWatcher
    ) {
      this.vm = vm
      if (isRenderWatcher) {
        // 是renderWatcher, 则把该watcher实例赋值给 vm._watcher
        vm._watcher = this
      }
      vm._watchers.push(this) // push
      
      // parse expression for getter
      if (typeof expOrFn === 'function') {
        this.getter = expOrFn
        // 是函数赋值 getter
      } else {
        // 不是函数
        this.getter = parsePath(expOrFn)
        if (!this.getter) {
          this.getter = noop
          process.env.NODE_ENV !== 'production' && warn(
            `Failed watching path: "${expOrFn}" ` +
            'Watcher only accepts simple dot-delimited paths. ' +
            'For full control, use a function instead.',
            vm
          )
        }
      }
      this.value = this.lazy
        ? undefined
        : this.get()
    }
    
    get () {
      pushTarget(this)
      let value
      const vm = this.vm
      try {
        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)
        }
        popTarget()
        this.cleanupDeps()
      }
      return value
    }
    
    addDep (dep: Dep) {
      const id = dep.id // dep实例的id属性
      if (!this.newDepIds.has(id)) { // ------------ newDepIds中不存在该id
        this.newDepIds.add(id) // 向 ( Watcher类 ) 的 newDepIds中添加 id
        this.newDeps.push(dep) // 向 ( Watcher类 ) 的 newDeps 中添加 dep
        if (!this.depIds.has(id)) { // ------------- depIds 中不存在该id
          dep.addSub(this)   // 向 ( Dep类 ) 的 subs 中添加 该watcher
        }
      }
    }
    
    update () {
      /* istanbul ignore else */
      if (this.lazy) { // 用于computed watcher
        this.dirty = true
      } else if (this.sync) { // 同步watcher
        this.run()
      } else {
        queueWatcher(this) // nextTick(flushSchedulerQueue)将watcher放入队列,在下一轮tick去更新
      }
    }
    }

(2) 初始化渲染

初始化渲染.png

(2-1) vue的不同构建版本

  • 独立构建 (包括template编译的过程) - runtime+compiler完整版

    • 渲染过程:template -> render函数 -> vnode -> 真实的dom
  • 运行时候构建(不包括template编译的过程) - runtime版

    • 渲染过程:render函数 -> vnode -> 真实的dom
  • 打包后dist文件夹中的文件会有所不同

    • 独立构建的完整版 vue.js
    • 运行时版本 vue.runtime.js

      • 运行时版本相比完整版体积要小大约 30%

(2-2) dom的初始化渲染 - vm.$mount(vm.$options.el)

  • 执行 new Vue(options) => this._init(options) => Vue.prototype._init => initState(vm) => vm.$mount(vm.$options.el)

    • 即在this._init中先初始化initState()把data变成响应式后,就会执行dom挂载vm.$mount(vm.$options.el)
  • <font color=red>vm.$mount</font>

    • 有两种版本,runtime版和runtime+compiler版本,但最终都会调用 public mount method 的$mount
  • <font color=blue size=5>初始化渲染过程</font>

    1. 执行 vm.$mount(vm.$options.el) 方法

      • 如果是runtime版本就是直接调用 mountComponent(this, el, hydrating) 方法
      • 如果是runtime+compiler版本(即传入new Vue()的参数对象中不存在render方法)就会先处理template,将template通过 compileToFunctions(template, options) 函数编译成render方法,然后调用 mountComponent(this, el, hydrating) 方法
    2. 执行 mountComponent(this, el, hydrating) 函数

      • 实例化render watcher即并把updateComponent=()=>{vm._update(vm._render(), hydrating)}函数作为new Watcher(vm, updateComponent, noop,{})的第二个参数传入
    3. new Watcher(vm, updateComponent, noop,{})

      • 执行get()方法
      • 在get方法中将当前watcher赋值给Dep.target
      • 在get方法中执行updateComponent方法,从而执行vm._update(vm._render(), hydrating)方法
    4. vm._update(vm._render(), hydrating)

      • vm._render()会把template转成 vnode
      • vm._update()则会把vnode挂载到真正的dom上,渲染出页面
  • 源码
  • src/core/instance/index.js

    src/core/instance/index.js
    ---
    
    function Vue (options) {
    this._init(options)
    }
  • src/core/instance/init.js

    src/core/instance/init.js
    ---
    
    Vue.prototype._init = function (options?: Object) { // _init方法初始化
      initState(vm)
      if (vm.$options.el) {
          vm.$mount(vm.$options.el) 
          // mount方法 - 负责页面的挂载
          // 传入el,el是 new Vue({el: '#app'}) 这里的el
          
          // vm.$mount(vm.$options.el) 
          // 最终就是调用 mountComponent(this, el, hydrating) 方法
      }
    }
  • src/platforms/web/entry-runtime-with-compiler.js
    总结: vm.$mount() 的作用就是:调用 mountComponent(this, el, hydrating) 方法

    src/platforms/web/entry-runtime-with-compiler.js
    总结:
    - vm.$mount(vm.$options.el) 的作用就是:调用 mountComponent(this, el, hydrating) 方法
    ---
    
    const mount = Vue.prototype.$mount
    // mount
    // mout的主要作用:缓存 runtime 版本的 $mount,代码如下:
      // mount = Vue.prototype.$mount = function (
      //   el?: string | Element,
      //   hydrating?: boolean
      // ): Component {
      //   el = el && inBrowser ? query(el) : undefined
      //   return mountComponent(this, el, hydrating)
      // }
    
    Vue.prototype.$mount = function (
    // 这里是runtime+compiler版本的$mount方法
    el?: string | Element,
    hydrating?: boolean
    ): Component {
    el = el && query(el)
    // query(el)
      // 主要作用:就是将el转成Element节点
      // 具体是:
        // 1. 是字符串并且存在,就查找id对应的dom
        // 2. 是字符串但是dom没有对应的元素,开发环境会抛出警告,然后就创建一个空的div返回
        // 3. 不是字符串,而是dom元素直接返回
      // function query (el: string | Element): Element {
      //   if (typeof el === 'string') {
      //     const selected = document.querySelector(el)
      //     if (!selected) {
      //       process.env.NODE_ENV !== 'production' && warn(
      //         'Cannot find element: ' + el
      //       )
      //       return document.createElement('div')
      //     }
      //     return selected
      //   } else {
      //     return el
      //   }
      // }
    
    /* istanbul ignore if */
    if (el === document.body || el === document.documentElement) {
      process.env.NODE_ENV !== 'production' && warn(
        `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
      )
      return this
      // el不能是body或html标签,因为会被覆盖
    }
    
    const options = this.$options
    // resolve template/el and convert to render function
    if (!options.render) {
      let template = options.template
      // new Vue(options)时,options中没有render方法,就会去看是不是有template属性
      if (template) {
        if (typeof template === 'string') {
          // -------------------------------------------- template在options对象参数中存在,并且是字符串
          if (template.charAt(0) === '#') {
            template = idToTemplate(template)
            /* istanbul ignore if */
            if (process.env.NODE_ENV !== 'production' && !template) {
              warn(
                `Template element not found or is empty: ${options.template}`,
                this
              )
            }
          }
        } else if (template.nodeType) {
          // -------------------------------------------- template在options对象参数中存在,是dom结构
          template = template.innerHTML
        } else {
          if (process.env.NODE_ENV !== 'production') {
            warn('invalid template option:' + template, this)
          }
          return this
        }
      } else if (el) {
        // ---------------------------------------------- template在options对象参数中不存在,就寻找el,el存在
        template = getOuterHTML(el)
        // 获取el对应的dom,并且赋值给template
      }
      // 其实上面一大段 if (template) 都是在处理 template
    
      if (template) {
        // 此时的template是经过上面处理过后的template
    
        /* istanbul ignore if */
        // mark直接滤过
        // if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        //   mark('compile')
        // }
    
        const { render, staticRenderFns } = compileToFunctions(template, {
          outputSourceRange: process.env.NODE_ENV !== 'production',
          shouldDecodeNewlines,
          shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        }, this)
        // compileToFunctions
          // 主要就是将模板 template 编译成 render 函数
    
        options.render = render 
        options.staticRenderFns = staticRenderFns
        // 将render函数挂载到$options上
    
        /* istanbul ignore if */
        // if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        //   mark('compile end')
        //   measure(`vue ${this._name} compile`, 'compile', 'compile end')
        // }
      }
    }
    return mount.call(this, el, hydrating)
    // 调用上面缓存的 mount 方法
      // mount 中会调用  mountComponent(this, el, hydrating)
    
      // mount = Vue.prototype.$mount = function (
      //   el?: string | Element,
      //   hydrating?: boolean
      // ): Component {
      //   el = el && inBrowser ? query(el) : undefined
      //   return mountComponent(this, el, hydrating)
      // }
    
      // 这里执行 mount 方法时,还会再次执行 query(el) 方法,多了一次,只是runtime版本考虑的
    }
  • src/core/instance/lifecycle.js

    src/core/instance/lifecycle.js
    ---
    
    export function mountComponent (
    vm: Component,
    el: ?Element,
    hydrating?: boolean
    ): Component {
    vm.$el = el
    if (!vm.$options.render) {
      vm.$options.render = createEmptyVNode
      ...
    }
    callHook(vm, 'beforeMount') // ------------------------ beforeMount 生命周期钩子
    
    let updateComponent
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      ...
    } else {
      updateComponent = () => {
        vm._update(vm._render(), hydrating)
      }
      // updateComponent 的赋值
        // 赋值:是在这里赋值的
        // 执行:是触发了响应式的 set 方法,调用watcher.update()方法时执行的
    }
    
    new Watcher(vm, updateComponent, noop, {
      before () {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate')
        }
      }
    }, true /* isRenderWatcher 这里的watcher就是渲染watcher*/)
    // watcher分为三种
      // 1. computed watcher
      // 2. normal watcher 用户自定义的 watcher - 即 watch 对象中的方法
      // 3. render watcher
    // wathcer的执行顺序是固定的
      // computed watcher -> normal watcher -> render watcher
      // 这样就能保证在渲染时能拿到最新的 computed 和 执行了 watche 中定义的函数
    
    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')  // ------------------------ mounted 生命周期钩子
    }
    return vm
    }
  • src/core/observer/watcher.js

    src/core/observer/watcher.js
    ---
    
    export default class Watcher {
    constructor (
      vm: Component,
      expOrFn: string | Function,
      cb: Function,
      options?: ?Object,
      isRenderWatcher?: boolean
    ) {
      this.vm = vm
      if (isRenderWatcher) {
        // 如果是渲染watcher - renderWatcher
        vm._watcher = this
        // 就在组件实例上添加 _watcher 属性,值就是该renderWatcher实例
      }
      vm._watchers.push(this)
      // 向 _watchers 数组中添加该renderWatcher
    
      if (typeof expOrFn === 'function') {
        this.getter = expOrFn
        // expOrFn 是函数就赋值给 getter
          // 1. 如果是renderWatcher的话 this.getter = updateComponent方法
            // updateComponent 方法返回 vm._update(vm._render(), hydrating)
      } else {
       ...
      }
      this.value = this.lazy
        ? undefined
        : this.get()
      // lazy属性只有 computed Watcher 才有
        // 1. render watcher 就会调用 get() 方法并赋值给 this.value
    }
    
    /**
     * Evaluate the getter, and re-collect dependencies.
     */
    get () {
      pushTarget(this)
      // pushTarget 两个作用
      // 1. 向 targetStack 数组中添加该 watcher,这里是render watcher
      // 2. Dep.target = this 将该render watcher 赋值给 Dep.target,表示正在执行的是渲染watcher
    
      let value
      const vm = this.vm
      try {
        value = this.getter.call(vm, vm)
        // 调用 getter 函数
        // 这里因为是渲染watcher,所以执行的是 updateComponent
        // updateComponent = vm._update(vm._render(), hydrating)
    
        // 注意这里是重点
          // 执行 vm._update(vm._render(), hydrating)
          // 1. 因为在执行 vm._update 方法的过程中,会获取响应式data中的属性,触发get进行依赖收集
          // 2. vm._render() 将template转成 vnode
          // 2. vm._update() 对比后将 vnode 转成真正的 DOM
      } 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)
        }
        popTarget()
        this.cleanupDeps()
      }
      return value
    }
    }

参考

详细 - 目前能找到的写得最好的 https://juejin.im/post/684490...
三种 watcher https://juejin.im/post/684490...
大滴滴 https://juejin.im/post/684490...


woow_wu7
10 声望2 粉丝