computed 思想实现

// main.js
import Vue from '../modules/vue'

var vm = new Vue({
  el:'#app',
  template: `
    <span>{{a}}</span>
    <span>+</span>
    <span>{{b}}</span>
    <span>=</span>
    <span>{{total}}</span>
  `,
  data(){
    return {
      a:1,
      b:2,
    }
  },
  computed:{
    total(){
      console.log('computed total');
      return this.a + this.b
    },
    total:{
      get:function(){
        console.log('computed total');
        return this.a + this.b
      }
    }
  }
});

console.log(vm);

console.log(vm.total);
console.log(vm.total);
console.log(vm.total);
console.log(vm.total);

vm.a = 100;

console.log(vm.total);
console.log(vm.total);
console.log(vm.total);
console.log(vm.total);
// modules/vue/index.js
/**
 * 计算属性:解决模版中复杂的逻辑运算及复用的问题
 * 计算属性只在内部依赖发生改变时,才会重新求值
 * 计算属性会缓存其依赖的上一次计算出的数据结果
 * 多次复用一个相同值的数据,计算属性只调用一次
 */

var Vue = (function(){
  /**
   * 收集计算属性props
   * computedData:{
   *  value:函数执行返回的结果
   *  get:get
   *  dep:['a','b']
   * }
   */
  var computedData = {},
      reg_var = /\{\{(.+?)\}\}/g, /**匹配{{}}中的内容 */
      dataPool = {}; /**收集data */

  var Vue = function(options){
    /**保存el到vue实例上 */
    this.$el = document.querySelector(options.el);
    /**保存data到vue实例上 */
    this.$data = options.data();
    /**初始化函数_init */
    this._init(this, options.computed,options.template);
  }
  /**
   * 初始化函数
   * @param {*} vm 
   * @param {*} computed 
   * @param {*} template 
   */
  Vue.prototype._init = function(vm,computed,template){
    dataReactive(vm);
    computedReactive(vm,computed);
    render(vm,template);
  }
  /**
   * 渲染函数
   * @param {*} vm 
   * @param {*} template 
   */
  function render(vm,template){
    var container = document.createElement('div');
    var _el = vm.$el;
    container.innerHTML = template;

    var domTree = _compileTemplate(vm,container);
    _el.appendChild(domTree);

  }
  function updata(vm,key){
    dataPool[key] && (dataPool[key].textContent = vm[key]);
  }
  /**
   * 编译模版,把模版中的{{}}替换成vue实例中的变
   * @param {*} vm 
   * @param {*} container 
   * @returns 
   */
  function _compileTemplate(vm,container){
    var allNodes = container.getElementsByTagName('*');
    for(var i=0;i<allNodes.length;i++){
      var nodeItem = allNodes[i];
      var matched = nodeItem.textContent.match(reg_var);/**匹配{{}}中的内容 */
      // console.log(matched);
      if(matched){
        nodeItem.textContent = nodeItem.textContent.replace(reg_var,function(node,key){
          dataPool[key.trim()] = nodeItem; // 收集包含{{}}模版的节点
          return vm[key.trim()]; // 把模版中的{{a}}替换成vue实例中的变量
        });
      }
    }
    return container;
  }
  /**
   * 把data响应式挂载到vue实例上
   * @param {*} vm 
   */
  function dataReactive(vm){
    var _data = vm.$data;
    for(var key in _data){
      (function(key){
        Object.defineProperty(vm, key, {
          get: function(){
            return _data[key];
          },
          set: function(newValue){
            _data[key] = newValue;
            // 数据改变更新视图
            updata(vm,key)
            // 数据改变,更新计算属性
            updataComputeData(vm,key,function(vm,key){
              updata(vm,key)
            })
          }
        })
      })(key);
    }
  }
  /**
   * 把computed响应式挂载到vue实例上
   * @param {*} vm 
   * @param {*} computed 
   */
  function computedReactive(vm,computed){
    _initComputedData(vm,computed);
    for (const key in computedData) {
      (function(key){
        Object.defineProperty(vm, key, {
          get: function(){
            return computedData[key].value;
          },
          set: function(newValue){
            computedData[key].value = newValue;
          }
        })
      })(key);
    }
  }
  /**
   * 收集computed的数据
   * @param {*} vm 
   * @param {*} computed 
   */
  function _initComputedData(vm,computed){
    for (const key in computed) {
      /**
       * Object.getOwnPropertyDescriptor(computed,key)
       * {
       *   configurable:true,
       *   enumerable:true,
       *   value:function total(){retur this.a+this.b},
       *   writable:true
       * }
       */
      var descriptor = Object.getOwnPropertyDescriptor(computed,key),
          /**
           * descriptor.value.get
           *  total:{
                get:function(){
                    console.log('computed total');
                    return this.a + this.b
                  }
                }
           */
          /**
           * descriptor.value
           * total(){
                console.log('computed total');
                return this.a + this.b
              },
           */
          descriptorFn = descriptor.value.get?descriptor.value.get:descriptor.value;
      computedData[key] = {};
      computedData[key].value=descriptorFn.call(vm); // value:computed执行的结果
      computedData[key].get = descriptorFn.bind(vm); // get:computed执行函数
      computedData[key].dep = _collectDep(descriptorFn); // dep:该计算属性依赖的变量
      // console.log(computedData);
      // console.log(Object.getOwnPropertyDescriptor(computed,key));
    }
  }
  /**
   * 收集计算属性中依赖的变量
   * @param {*} fn 
   * @returns 
   */
  function _collectDep(fn){
    var _collection = fn.toString().match(/this\.\w+/g); // 匹配计算属性中的this.xxx
    if(_collection.length >0){
      for(var i=0;i<_collection.length;i++){
        _collection[i] = _collection[i].replace('this.',''); // this.a => a
      }
    }
    // console.log(_collection);
    return _collection;
  }
  function updataComputeData(vm,key,updata){
    var _dep = [];
    for (const _key in computedData) {
     _dep = computedData[_key].dep;
     console.log('_dep',_dep,key);
     for (let i = 0; i < _dep.length; i++) {
      if(_dep[i] == key){
        vm[_key] = computedData[_key].get();
        updata(vm,_key)
      }
     }
    }
  }

  return Vue;
})();

export default Vue;
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

️不倒翁

你的鼓励就是我前进的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值