注册
web

手把手教你实现MVVM架构

引言

现在的前端真可谓是百花齐放,百家争鸣,各种框架层出不穷,但是主要目前用的最多的还是要数VueReact、以及Angular,这三种,当然不乏近期新出的一些其他框架,但是她们都有一个显著的特点,那就是使用了MVVM的架构。

首先我们要搞清楚什么是MVVM

MVVM就是Model-View-ViewModel的缩写,MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想,在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。把ModelView关联起来的就是ViewModelViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model

改变JavaScript对象的状态,会导致DOM结构作出对应的变化!这让我们的关注点从如何操作DOM变成了如何更新JavaScript对象的状态,而操作JavaScript对象比DOM简单多了。

这就是MVVM的设计思想:关注Model的变化,让MVVM框架去自动更新DOM的状态,从而把开发者从操作DOM的繁琐步骤中解脱出来!

接下来我会带着你们如何去实现一个简易的MVVM架构。

一、构建MVVM构造函数

创建一个MVVM构造函数,用于接收参数,如:datamethodscomputed等:

function MVVM(options) { 
   this.$options = options;
   let data = this._data = this.$options.data;
   observe(data);
   for (let key in data) {
       Object.defineProperty(this, key, {
           enumerable: true,
           get() {
               return this._data[key];
          },
           set(newVal) {
               this._data[key] = newVal;
          }
      });
  };
   initComputed.call(this);
   new Compile(options.el, this);
   options.mounted.call(this);
}

二、构建Observe构造函数

创建一个Observe构造函数,用于监听数据变化:

function Observe(data) { 
   let dep = new Dep();
   for (let key in data) {
       let val = data[key];
       observe(val);
       Object.defineProperty(data, key, {
           enumerable: true,
           get() {
               Dep.target && dep.addSub(Dep.target);
               return val;
          },
           set(newVal) {
               if (val === newVal) {
                   return;
              }
               val = newVal;
               observe(newVal);
               dep.notify();
          }
      });
  };
};

三、构建Compile构造函数

创建一个Compile构造函数,用于解析模板指令:

function Compile(el, vm) { 
   vm.$el = document.querySelector(el);
   let fragment = document.createDocumentFragment();
   while (child = vm.$el.firstChild) {
       fragment.appendChild(child);
  }
   replace(fragment);
   function replace(frag) {
       Array.from(frag.childNodes).forEach(node => {
           let txt = node.textContent;
           let reg = /\{\{(.*?)\}\}/g;
           if (node.nodeType === 3 && reg.test(txt)) {
               let arr = RegExp.$1.split('.');
               let val = vm;
               arr.forEach(key => { val = val[key]; });
               node.textContent = txt.replace(reg, val).trim();
               new Watcher(vm, RegExp.$1, newVal => {
                   node.textContent = txt.replace(reg, newVal).trim();
              });
          }
       if (node.nodeType === 1) {
           let nodeAttr = node.attributes;
           Array.from(nodeAttr).forEach(attr => {
               let name = attr.name;
               let exp = attr.value;
               if (name.includes('')) {
                   node.value = vm[exp];
              }
               new Watcher(vm, exp, newVal => {
                   node.value = newVal;
              });
               node.addEventListener('input', e => {
                   let newVal = e.target.value;
                   vm[exp] = newVal;
              });
            });
        };
        if (node.childNodes && node.childNodes.length) {
            replace(node);
        }
      });
    }
    vm.$el.appendChild(fragment);
}

四、构建Watcher构造函数

创建一个Watcher构造函数,用于更新视图:

function Watcher(vm, exp, fn) { 
   this.fn = fn;
   this.vm = vm;
   this.exp = exp;
   Dep.target = this;
   let arr = exp.split('.');
   let val = vm;
   arr.forEach(key => {
       val = val[key];
  });
   Dep.target = null;
}

Watcher.prototype.update = function() {
       let arr = this.exp.split('.');
       let val = this.vm;
       arr.forEach(key => {
           val = val[key];
      });
       this.fn(val);
}

五、构建Dep构造函数

创建一个Dep构造函数,用于管理Watcher:

function Dep() { 
   this.subs = [];
}

Dep.prototype.addSub = function(sub) {
   this.subs.push(sub);
}

Dep.prototype.notify = function() {
   this.subs.forEach(sub => {
       sub.update();
  });
}

六、构建initComputed构造函数

创建一个initComputed构造函数,用于初始化计算属性:

function initComputed() { 
   let vm = this;
   let computed = this.$options.computed;
   Object.keys(computed).forEach(key => {
       Object.defineProperty(vm, key, {
           get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
           set() {}
      });
  });
}

总结:

至此我们就完成了一个简易的MVVM框架,虽然简易,但是基本的核心思想差不多都已经表达出来了,最后还是希望大家不要丢在收藏文件夹里吃灰,还是要多多动手练习一下,所谓眼过千遍,不如手过一遍。

作者:前端第一深情阿斌
来源:juejin.cn/post/7202431872968851517

0 个评论

要回复文章请先登录注册