导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

业务技能

针对性攻坚

AI


页面表现

实现

思路

  1. 对 data 进行数据劫持
  2. 分析 ast 数,找到元素绑定的 v-if 以及 @methods 事件函数
    1. 分别放进 showPool 和 eventPool 中(数据格式为 Map
      1. showPool 数据格式
        1. key: DOM对象
        2. value: { type: 'if/show', prop: data.xxx }
      2. eventPool 数据格式
        1. key: DOM对象
        2. value: methods.xxx 方法
  3. 绑定事件处理函数
  4. 初次渲染
    1. 如果遇到 v-if ,先给 showPool 中对应 value 加个 comment 注释节点用于后续占位
  5. 用户触发 methods 事件函数
    1. data数据更新
    2. 触发了属性 setter
    3. 进行 update 更新页面

函数

showPool:

Untitled

eventPool:

Untitled

实现代码

/**
 * showPool Map {dom: {}}
 * [
 *  [
 *    dom,
 *    {
 *      type: if/show
 *      prop: data下边的对应属性
 *     }
 *  ]
 * ]
 *
 * eventPool
 *
 * [
 *   [
 *    dom,
 *    handler
 *   ]
 * ]
 */

var Vue = (function() {
	function Vue(options) {
    // el
    this.$el = document.querySelector(options.el);
    this.$data = options.data();

    this._init(this, options.template, options.methods);
  }

  Vue.prototype._init = function(vm, template, methods) {

    var container = document.createElement('div');
    container.innerHTML = template;

    var showPool = new Map();
    var eventPool = new Map();
    initData(vm, showPool); // 更新的时候要用到showPool
    initPool(container, methods, showPool, eventPool);
    bindEvent(vm, eventPool);
    render(vm, showPool, container);
  }

  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) {
            // this.isShowImg1 = true;
            _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) {
    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;
      }
    }

    _el.appendChild(container);
  }

  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;