vue@2.x实现分析

本文最后更新于:2022年4月21日 上午

生命周期

初始化阶段

Vue.prototype._init = function (options?: Object){
  // 设置uid
  vm._uid = uid++

  // 不为根组件设置响应式
  vm._isVue = true

  // 合并配置
  // 合并实例的option对象,里面会包含像生命周期钩子回调用,指令,过滤器,vue内置对象等,与用户自定义的全局xx合并
  // 实例内部执行会比外部new Vue快很多
  if (options && options._isComponent) {
    // 实例内部执行new Vue
    initInternalComponent(vm, options)
  } else {
    // 从外部执行new Vue时
    vm.$options = mergeOptions(
      √s || {},
      vm
    )
  }

  // 初始化生命周期
  initLifecycle(vm){
    //关联`$parent`,并将自己添加进父组件的`$children`数组,生成自己的`$children`数组和`$refs`对象,并指定`$root`
    var parent = options.parent;
    parent.$children.push(vm);
    vm.$parent = parent;
    vm.$root = parent ? parent.$root : vm;
    vm.$children = [];
    vm.$refs = {};
    vm._watcher = null;
    vm._isMounted = false;// 判断是否mount的flag
  }

  initEvents(vm){// 初始化事件中心
    vm._events = Object.create(null);

    var listeners = vm.$options._parentListeners;
    if (listeners) {
      updateComponentListeners(vm, listeners); // 组件上的事件
    }
  }

  initRender(vm){
      vm.$slots = resolveSlots(options._renderChildren, renderContext);// 初始化插槽,得到vm.$slots对象
      // key是插槽名 value是vnode

      vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
      // 模版编译的createElement函数
      vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
      // 初始化用户自己写的createElement函数

      defineReactive$$1(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
        !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
      }, true);
      defineReactive$$1(vm, '$listeners', options._parentListeners || emptyObject, function () {
        !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
      }, true);
  }

  callHook(vm, 'beforeCreate');

  initInjections(vm); // resolve injections before data/props

  initState (vm) {
    initProps(vm, opts.props);
    // 子组件标签上的prop将会编译为attrs对象
    // render函数在校验这些props后会会生成propData传入VNode构造器
    // 实例化子组件的同时也为prop绑定了响应式
    initMethods(vm, opts.methods);
    // 确保方法命名规范并添加到实例上
    initData(vm);
    // 确保data中命名规范并绑定响应式
    initComputed(vm, opts.computed);
    initWatch(vm, opts.watch);
  }
  initProvide(vm); // resolve provide after data/props
  callHook(vm, 'created');
}

挂载阶段

mountComponent () {
  callHook(vm, '⭐️beforeMount');

  let updateComponent;

  updateComponent = () => {
    vm._update(vm._render(), hydrating);
    // update函数
    Vue.prototype._update = function (vnode, hydrating) {
      if (!prevVnode) {
        // initial render
        vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
      } else {
        // updates
        vm.$el = vm.__patch__(prevVnode, vnode);
    };
  };

  new Watcher(vm, updateComponent, noop, {
        before: function before () {
        if (vm._isMounted) {
          callHook(vm, 'beforeUpdate');
        }
      }
    subs[i].notify  ---> Watcher.prototype.update()
    callHook(vm, '⭐️beforeDestroy');
  }, true /* isRenderWatcher init阶段生成的是渲染watcher*/);
  
  vm._isMounted = true;
  
  callHook(vm, 'mounted');
}

销毁阶段

Vue.prototype.$destroy = function () {

  callHook(vm, '⭐️beforeDestroy');
  // 销毁实例,子实例,从父组件children中删除,销毁所有事件和指令
  remove(parent.$children, vm);
  vm._watcher.teardown();
  vm.__patch__(vm._vnode, null);
  // fire destroyed hook
  
  callHook(vm, '⭐️destroyed');

  vm.$el.__vue__ = null;
  vm.$vnode.parent = null;
  };
}

keep-alive

callHook(vm, '⭐️activated');
callHook(vm, '⭐️deactivated');

响应式原理

defineReactive

defineReactive (
  ...
) {
  const dep = new Dep()
  ...
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
  // render函数接触data触发getter
    get: function reactiveGetter () {
      if (Dep.target) {
        // 此时Dep.target已经是渲染watcher
        // 执行depend
⭐️        dep.depend()
        if (childOb) {
          ...
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      val = newVal

🔥    dep.notify()
    }
  })
}

mountComponent

mountComponent (...): Component {
  callHook(vm, 'beforeMount')

  let updateComponent;
⭐️ // render函数生成虚拟DOM,期间会访问数据
  updateComponent = () => {
    vm._update(vm._render(), hydrating);
  };

⭐️ //组件`mount`过程中会实例化一个渲染`watcher`,传入updateComponent函数
  vm._watcher = new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)

  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
}

Watcher对象

class Watcher {
  constructor (
    vm,
    expOrFn,
    cb,
    options
  ) {
    this.vm = vm;
    vm._watchers.push(this);

    this.deps = [];
    this.newDeps = [];
    this.depIds = new _Set();
    this.newDepIds = new _Set();

    // parse expression for getter
    if (typeof expOrFn === 'function') {
⭐️ // getter此时为传入的updateComponent函数
      this.getter = expOrFn;
    }
  }

  get () {
⭐️  //首先触发watcher中的get()
    // pushTarget方法
    pushTarget(this)
    let value

⭐️  // 调用getter
    value = this.getter.call(vm, vm)
    if (this.deep) {
      traverse(value)
    }
⭐️ // 当前组件收集完成后清理
    popTarget()
    this.cleanupDeps()

    return value
  }


  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
      ⭐️ // 添加订阅
        dep.addSub(this)
      }
    }
  }

⭐️// 对比新旧订阅列表,清空依赖
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        //清空依赖
        dep.removeSub(this)
      }
    }
    // 每次更新页面,render依赖收集的时候
    // 将不展示于页面上的数据的依赖移除
    // 考虑v-if
    [this.depIds,this.newDepIds]=[this.newDepIds,this.depIds]
    this.newDepIds.clear()
    [this.deps,this.newDeps] = [this.newDeps,this.deps]
    this.newDeps.length = 0
  }
🔥  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      // 同步立即run
      this.run();
    } else {
🔥    queueWatcher(this);
    }
  }
  // 通过一个队列queue管理需要更新的watcher,重复的将会只执行最后一个
  function queueWatcher (watcher) {
    var id = watcher.id;
    if (has[id] == null) {
      has[id] = true;
      if (!flushing) {
        queue.push(watcher);
      } else {
        // if already flushing, splice the watcher based on its id
        // if already past its id, it will be run next immediately.
        var i = queue.length - 1;
        while (i > index && queue[i].id > watcher.id) {
          i--;
        }
        queue.splice(i + 1, 0, watcher);
      }
      // queue the flush
      if (!waiting) {
        waiting = true;

        if (!config.async) {
          flushSchedulerQueue();
          return
        }
        // 在下一个tick执行这个队列
        nextTick(flushSchedulerQueue);
      }
    }
  }
}

Dep对象

class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }
 // 将watcher加入subs
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
     // 执行当前watcher中的addDep函数
      Dep.target.addDep(this)
    }
  }

🔥  notify () {
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

⭐️ // target置空
Dep.target = null

const targetStack = []

function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  ⭐️ // 将Dep.target 指向当前watcher
  Dep.target = _target
}

function popTarget () {
  Dep.target = targetStack.pop()
}

⭐️ 收集依赖 ⭐️

  1. 实例初始化阶段为每个标记为响应式的数据(拥有不可枚举属性__ob__)进行响应式绑定,生成各自的Dep实例;

  2. 组件渲染函数执行过程中实例化一个渲染Watcher,并指定为此时的Dep.target(Dep 的 Target 指向不是固定的,在渲染函数的执行过程中会一直变化来为各类数据 data prop computed watch)创建维护 subs 数组;

  3. 当渲染函数访问到绑定了响应式的数据时,触发数据的 getter,将当前组件渲染Watcher添加到该数据持有的Dep.subs数组中;

  4. 同时渲染Watcher自己也有一个deps数组保存,用来记录自己订阅了谁,这样做的意义是在下次Watcher.update时将不再显示页面上的数据绑定移除(对比新旧deps数组)

    可以理解成 data 数据的 subs 数组中每个 watcher 即代表一个受该数据响应式影响的组件实例

🔥 派发更新 🔥

  1. 修改数据触发 setter,执行该数据持有的Dep.notify方法
  2. 遍历当前数据Dep.subs中的每一个Watcher,执行其Watcher.update()
  3. vue 不会每次数据更新就立刻去更新视图,而是用队列管理这些回调任务queueWatcher
  4. 队列会判断 watcher 是否重复,并且进行排序【先父后子】【自定义 watcher> 渲染 watcher】
  5. 在下一个 tick 执行这个队列的回调updateComponent函数,patch实例(diff 并更换新 DOM)

nextTick

  • js事件循环,主线程的一轮循环就是一个tick,只有在执行完所有同步函数和macrotaskmicrotask后才能进入下一个tick
  • 派发更新时queueWatcher调用nextTick()来执行需要更新的Watcher队列
  • 手动调用nextTick的回调也会加入callback数组,在下一个tick执行
    const callbacks = []
    let pending = false
    
    function flushCallbacks () {
      pending = false
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }
    
    let microTimerFunc // 在下一轮微任务执行cb数组
    let macroTimerFunc // 在下一轮宏任务执行cb数组
    let useMacroTask = false
    
    // 依次尝试使用setImmediate,messageChanel,setTimeout来实现macroTimerFunc
    if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
      macroTimerFunc = () => {
        setImmediate(flushCallbacks)
      }
    } else if (typeof MessageChannel !== 'undefined' && (
      isNative(MessageChannel) ||
      // PhantomJS
      MessageChannel.toString() === '[object MessageChannelConstructor]'
    )) {
      const channel = new MessageChannel()
      const port = channel.port2
      channel.port1.onmessage = flushCallbacks
      macroTimerFunc = () => {
        port.postMessage(1)
      }
    } else {
      macroTimerFunc = () => {
        setTimeout(flushCallbacks, 0)
      }
    }
    
    //
    if (typeof Promise !== 'undefined' && isNative(Promise)) {
      const p = Promise.resolve()
      microTimerFunc = () => {
        p.then(flushCallbacks)
      }
    } else {
      // 回退到使用macroTimerFunc
      microTimerFunc = macroTimerFunc
    }
    
    export function nextTick (cb?: Function, ctx?: Object) {
      let _resolve
      callbacks.push(() => {
        if (cb) {
          try {
            cb.call(ctx)
          } catch (e) {
            handleError(e, ctx, 'nextTick')
          }
        } else if (_resolve) {
          _resolve(ctx)
        }
      })
      if (!pending) {
        pending = true
        if (useMacroTask) {
          macroTimerFunc()
        } else {
          microTimerFunc()
        }
      }
      // 如果调用了nextTick但没传cb,默认给一个Promise.resolve()来resolve
      if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
          _resolve = resolve
        })
      }
    }

组件生命周期关系

  • 组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。
  • 组件的销毁操作是先父后子,销毁完成的顺序是先子后父。

加载渲染过程

父 beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount- >子 mounted->父 mounted

子组件更新过程

父 beforeUpdate->子 beforeUpdate->子 updated->父 updated

销毁过程

父 beforeDestroy->子 beforeDestroy->子 destroyed->父 destroyed

计算属性和侦听属性

Computed

initComputed (vm: Component, computed: Object) {

  const watchers = vm._computedWatchers = Object.create(null)

  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    ...

⭐️1      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        { computed: true }
      )

    if (!(key in vm)) {
⭐️2      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        // ... key名称被data或者prop占用的警告
      }
    }
  }
}
createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
⭐️2      watcher.depend()
      return watcher.evaluate()
    }
  }
}
  1. 实例 initState 阶段遍历 Computed 内的 key,为每个 key 创建 computed watcher
  2. 利用 Object.defineProperty 给 key 值添加 getter
  3. 渲染函数访问到 computed 内的属性时候触发 getter,生成一个 Dep 实例,并且此时 Dep.target 是渲染 watcher,渲染 watcher 就订阅了这个 key 的变化
  4. 执行这个 compute key 的计算逻辑,访问依赖的数据会触发其中响应式数据的 getter,则 computed watcher 也就订阅了 data 的变化,最后也返回计算值并缓存起来
  5. 当依赖的响应式数据变化时,响应的 computed watcher 会收到通知
  6. computed watcher 会重新求值,比较两次运算结果,结果改变时才会通知渲染 watcher 更新

Watch

  • deep
  • immediate
initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        // 遍历handlers
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
  1. 遍历 Watch 内的 key
  2. 遍历每一个 handler,为每一个 handler 创建 user watcher
  3. user watcher 会被添加进订阅响应数据的 subs 数组完成订阅
  4. 响应式数据的变化会通知到 user watcher
  5. 渲染函数如果访问这个 watch 属性,也同理会被推入该 watch 属性的 subs 数组,订阅其变化

Vue事件

Vue 支持 2 种事件类型,原生 DOM 事件和自定义事件,它们主要的区别在于添加和删除事件的方式不一样

  • 原生事件使用add/removeEventListener直接添加到el
  • 自定义事件使用一个事件中心来维护
  • 自定义事件的派发是往当前实例上派发
  • 只有组件节点才可以添加自定义事件,并且添加原生 DOM 事件需要使用 .native 修饰符;而普通元素使用 .native 修饰符是没有作用的,也只能添加原生 DOM 事件。

    vue3中已经舍弃.native,全局的所有事件都交由vue管理

    // 简化
    export function eventsMixin (Vue) {
      Vue.prototype.$on = function (event, fn){
        const vm = this
        if (Array.isArray(event)) {
          for (let i = 0, l = event.length; i < l; i++) {
            this.$on(event[i], fn)
          }
        } else {
          (vm._events[event] || (vm._events[event] = [])).push(fn)
        }
        return vm
      }
    
      Vue.prototype.$once = function (event, fn){
        const vm = this
        vm.$on(event, function(/*参数*/){
          vm.$off(event, on)
          fn.apply(vm, arguments)
        })
        return vm
      }
    
      Vue.prototype.$off = function (event, fn){
        const vm = this
        // all
        if (!arguments.length) {
          vm._events = Object.create(null)
          return vm
        }
        // array of events
        if (Array.isArray(event)) {
          for (let i = 0, l = event.length; i < l; i++) {
            this.$off(event[i], fn)
          }
          return vm
        }
        // specific event
        const cbs = vm._events[event]
        if (!cbs) {
          return vm
        }
        if (!fn) {
          vm._events[event] = null
          return vm
        }
        if (fn) {
          // specific handler
          let cb
          let i = cbs.length
          while (i--) {
            cb = cbs[i]
            if (cb === fn || cb.fn === fn) {
              cbs.splice(i, 1)
              break
            }
          }
        }
        return vm
      }
    
      Vue.prototype.$emit = function (event,...args){
        const vm = this
        let cbs = vm._events[event]
        if (cbs) {
          for (let i = 0, l = cbs.length; i < l; i++) {
            try {
              cbs[i].apply(vm, args)
            } catch (e) {
              // handle Error
            }
          }
        }
        return vm
      }
    }

vue@2.x实现分析
http://yoursite.com/2022/02/24/[源码]vue2/
作者
tatekii
发布于
2022年2月24日
许可协议