Skip to content

v0.0.5 总结一波。完善功能,优化代码逻辑,修复bug

功能点:

  • 当v-for存在同级子节点时,patch函数无法正常工作
  • 完善生命周期钩子函数功能
  • v-model 指令会转换为props[value]和@input事件
  • 支持数组类型响应

当v-for存在同级子节点时,patch函数无法正常工作

修改render.js中的createElement

js
  // ...
  // 在解析子节点时,增加isArray判断,然后合并数组
  children.forEach(child => {
    if (typeof child === 'string') {
      arr.push({ vm, tag: 'text', text: child })
    } else if (Array.isArray(child)) {
      arr.push.apply(arr, child) // 利用apply的数组参数将两个数组合并
    } else {
      arr.push(child)
    }
  })
  // ...

完善生命周期钩子函数功能

  1. beforeCreate 此时仅初始化了上下文生命周期,事件,渲染函数,vue组件中的属性还无法通过this直接进行访问,只能通过this.$options访问
js
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
  1. created 初始化injections、state、provide,此时vue组件的相关数据已经处理完毕,可直接通过this获取prop,methods,data,注意,此时真实DOM还未挂载
js
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

function initState(vm){
  vm._watchers = []
  const opts = vm.$options
  opts.props && initProps(vm, opts.props)
  opts.methods && initMethods(vm, opts.methods)
  opts.data ? initData(vm) : observe(vm._data = {}, true /* asRootData */)
  opts.computed && initComputed(vm, opts.computed)
  opts.watch && opts.watch !== nativeWatch && initWatch(vm, opts.watch)
}
  1. beforeMount 在mountComponent函数中触发,组件挂载前,render函数执行前

  2. mounted 在mountComponent函数中触发,组件挂载后,render函数执行后,由于微任务的执行顺序影响,在此处有时无法直接获取到el,因此需要借助$nextTick。

  3. beforeUpdate 在每个渲染Watcher(isRenderWatcher===true)的before钩子函数中,当每个组件执行渲染更新前触发

js
new Watcher(vm, updateComponent, noop, {
  before: function before () {
    if (vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'beforeUpdate');
    }
  }
}, true /* isRenderWatcher */);
  1. updated 在scheduler.js中,每一轮监听响应变化,nextTick时,刷新监听队列中的每个监听器,遍历每个vm的渲染Watcher
js
function callUpdatedHooks (queue) {
  var i = queue.length;
  while (i--) {
    var watcher = queue[i];
    var vm = watcher.vm;
    if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'updated');
    }
  }
}
  1. beforeDestroy 组件销毁之前触发
js
Vue.prototype.$destroy = function(){
  ...
  callHook(vm, 'beforeDestroy');
  ...
}
  1. destroyed 组件销毁后触发
js
Vue.prototype.$destroy = function(){
  ...
  const vm = this
  vm.$parent.remove(vm)
  vm._watcher && vm._watcher.teardown()
  let i = vm._watchers.length;
  while (i--) {
    vm._watchers[i].teardown();
  }
  if (vm._data.__ob__) {
    vm._data.__ob__.vmCount--;
  }
  vm._isDestroyed = true
  vm.__patch__(vm._vnode, null)

  callHook(vm, 'destroyed');
  ...
}
  1. activated (使用keep-alive时有用) 重新激活时

  2. deactivated (使用keep-alive时有用) 解除激活状态

v-model 指令会转换为props[value]和@input事件

下面是经过模板编译的虚拟vnode配置数据

js
{ 
  directives: [{
      name: "model",
      rawName: "v-model",
      value: (_vm.todo),
      expression: "todo"
  }],
  domProps: {
      "value": (_vm.todo)
  },
  on: {
      "input": function($event) {
          if ($event.target.composing) {
              return;
          }
          // 通过监听input事件,将target.value赋值给_vm.todo
          _vm.todo = $event.target.value
      }
  }
}

支持数组类型响应

我们之前都是通过this.countList = [...this.countList, this.count], 这种重新赋值的方式来改写数组的,这样写是为了触发data的响应,这样写弊端也很明显,因此我们需要更细粒度的控制数组的响应。

由于Object.defineProperty无法支持未定义的属性,数组的索引也是一样的逻辑 因此我们需要重写数组的push,slice等方法,来增加或移除对应属性的响应。