原理

用对象保存所有生命周期函数,给用户传入 options 中的钩子函数绑定 this 指向当前实例

import Vue from '../module/Vue';

var vm = new Vue({
  ...
  beforeCreate() {
    console.log('beforeCreate')
  },
  created() {
    console.log('created')
  },
  beforeMount() {
    console.log('beforeMount')
  },
  mounted() {
    console.log('mounted')
    this.isShowImg1 = false;
    this.isShowImg2 = false;
  },
  ...
});
console.log(vm);

实现代码

var Vue = (function() {
	function Vue(options) {

    // 用对象保存生命周期函数
    var recycles = {
      beforeCreate: options.beforeCreate.bind(this),
      created: options.created.bind(this),
      beforeMount: options.beforeMount.bind(this),
      mounted: options.mounted.bind(this)
    }
		// init之前,调用 beforeCreate
    recycles.beforeCreate(); // 得在上边改this指向,否则指向的是recycles

    this.$el = document.querySelector(options.el);
    this.$data = options.data();

    // 初始化
    this._init(this, options.template, options.methods, recycles);
  }

  Vue.prototype._init = function(vm, template, methods, recycles) {
    var container = document.createElement('div');
    container.innerHTML = template;

    var showPool = new Map();
    var eventPool = new Map();

    initData(vm, showPool);
    
    // 创建完成调用 created
    recycles.created();
    
    initPool(container, methods, showPool, eventPool);
    bindEvent(vm, eventPool);
    render(vm, showPool, container, recycles);
  }

  function initData(vm, showPool) {
    var _data = vm.$data;

    for (var key in _data) {
      (function(key) {
        Object.defineProperty(vm, key, {
          get: function() {
            return _data[key];
          },
          set: function(newVal) {
            _data[key] = newVal;
            update(vm, key, showPool)
          }
        });
      })(key);
    }
  }

  function initPool(container, methods, showPool, eventPool) {
    var _allNodes = container.getElementsByTagName('*');
    var dom = null;
    for (var i = 0; i < _allNodes.length; i++) {
      dom = _allNodes[i];

      var vIfData = dom.getAttribute('v-if');
      var vShowData = dom.getAttribute('v-show');
      var vEvent = dom.getAttribute('@click');

      if (vIfData) {
        showPool.set(
          dom,
          {
            type: 'if',
            prop: vIfData
          }
        )
        dom.removeAttribute('v-if');
      } else if (vShowData) {
        showPool.set(
          dom,
          {
            type: 'show',
            prop: vShowData
          }
        )
        dom.removeAttribute('v-show');
      }

      if (vEvent) {
        eventPool.set(
          dom,
          methods[vEvent]
        )
        dom.removeAttribute('@click');
      }
    }
    console.log(eventPool);
  }

  function bindEvent(vm, eventPool) {
    for (var [dom, handler] of eventPool) {
      vm[handler.name] = handler;
      dom.addEventListener('click', vm[handler.name].bind(vm), false);
    }
  }

  function render(vm, showPool, container, recycles) {
    var _data = vm.$data;
    var _el = vm.$el;

    for (var [dom, info] of showPool) {
      switch (info.type) {
        case 'if':
          info.comment = document.createComment(['v-if']);
          // 如果为假,就用 comment 节点替换 dom 节点
          // replaceChild(newNode, oldNode);
          !_data[info.prop] && dom.parentNode.replaceChild(info.comment, dom);
          break;
        case 'show':
          // 如果为假,就设置display为none
          !_data[info.prop] && (dom.style.display = 'none');
          break;
        default:
          break;
      }
    }
    // 挂载之前
    recycles.beforeMount();

    _el.appendChild(container);

    // 挂载之后
    recycles.mounted();
  }

  function update(vm, key, showPool) {
    var _data = vm.$data;

    for (var [dom, info] of showPool) {
      if (info.prop === key) {
        switch (info.type) {
          case 'if':
            !_data[key] ? dom.parentNode.replaceChild(info.comment, dom)
                        : info.comment.parentNode.replaceChild(dom, info.comment);
            break;
          case 'show':
            !_data[key] ? (dom.style.display = 'none')
                        : (dom.removeAttribute('style'));
            break;
          default:
            break;
        }
      }
    }
  }

  return Vue;
})();

export default Vue;

生命周期钩子触发流程

当 Vue 组件实例化时,会依次调用各个生命周期钩子。这些钩子在组件的不同阶段被调用,如下:

  1. beforeCreate:此时组件实例已创建,但 dataprops 还未初始化。
  2. created:此时组件实例已经创建完成,dataprops 已经初始化,但还没有挂载 DOM。
  3. beforeMount:此时组件已准备好挂载,DOM 尚未渲染。
  4. mounted:此时组件已挂载到真实 DOM,可以访问 DOM 元素。
  5. beforeUpdate:当响应式数据更新时,此钩子会在虚拟 DOM 重新渲染之前调用。
  6. updated:虚拟 DOM 重新渲染并应用更新到真实 DOM 后调用。
  7. beforeDestroy:在实例销毁之前调用,此时实例仍然完全可用。
  8. destroyed:在实例销毁后调用,此时组件的所有事件监听器、子实例、以及 DOM 绑定都已销毁。