关于javascript:vue源码01-data响应式-和-初始化渲染

34次阅读

共计 20230 个字符,预计需要花费 51 分钟才能阅读完成。



导航

[[深刻 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) 初始化渲染

(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…

正文完
 0