betway必威-betway必威官方网站
做最好的网站

前端数据流框架精讲,撸一个简单的MVVM例子

我个人以为mvvm框架里面最重要的一点就是VM这部分,它要与Model层建立联系,将Model层转换成可以被View层识别的数据结构;其次也要同View建立联系,将数据及时更新到View层上,并且响应View对数据的更改,同步到Model层。
MVVM的具体例子,可以看一下阮一峰老师的这篇博客。
我们提取其中比较关键的点:

原文链接, 如果感兴趣可以加QQ群: 157937068, 一起交流。

MVC,MVP和MVVM都是常见的软件架构设计模式(Architectural Pattern),它通过分离关注点来改进代码的组织方式。不同于设计模式(Design Pattern),只是为了解决一类问题而总结出的抽象方法,一种架构模式往往使用了多种设计模式。

  1. Model层存储数据
  2. 需要一个View-Model来对数据做中转,响应数据变化,同步到两端
  3. View层来负责展示数据,接受用户事件

本次分享是带大家了解什么是 mvvm,mvvm 的原理,以及近几年产生了哪些演变。

要了解MVC、MVP和MVVM,就要知道它们的相同点和不同点。不同部分是C(Controller)、P(Presenter)、VM(View-Model),而相同的部分则是MV(Model-View)。

Model层,我们用一个对象来代表。例如:

同时借 mvvm 这个话题拓展到对各类前端数据流方案的思考,形成对前端数据流整体认知,帮助大家在团队中更好的做技术选型。

Model&View

这里有一个可以对数值进行加减操作的组件:上面显示数值,两个按钮可以对数值进行加减操作,操作后的数值会更新显示。

 图片 1

我们将依照这个“栗子”,尝试用JavaScript实现简单的具有MVC/MVP/MVVM模式的Web应用。

 

let data = {
    text: 'foo'
};

Mvvm 的概念与发展

Model

Model层用于封装和应用程序的业务逻辑相关的数据以及对数据的处理方法。这里我们把需要用到的数值变量封装在Model中,并定义了add、sub、getVal三种操作数值方法。

var myapp = {}; // 创建这个应用对象

myapp.Model = function() {
    var val = 0; // 需要操作的数据

    /* 操作数据的方法 */
    this.add = function(v) {
        if (val < 100) val  = v;
    };

    this.sub = function(v) {
        if (val > 0) val -= v;
    };

    this.getVal = function() {
        return val;
    };
};

View层对于我们而言,可以认为是DOM节点。例如:

Mvvm & 单向数据流

Mvvm 是指双向数据流,即 View-Model 之间的双向通信,由 ViewModel 作桥接。如下图所示:

图片 2

image

而单向数据流则去除了 View -> Model 这一步,需要由用户手动绑定。

View

View作为视图层,主要负责数据的展示。

myapp.View = function() {

    /* 视图元素 */
    var $num = $('#num'),
        $incBtn = $('#increase'),
        $decBtn = $('#decrease');

    /* 渲染数据 */
    this.render = function(model) {
        $num.text(model.getVal()   'rmb');
    };
};

 

现在通过Model&View完成了数据从模型层到视图层的逻辑。但对于一个应用程序,这远远是不够的,我们还需要响应用户的操作、同步更新View和Model。于是,在MVC中引入了控制器controller,让它来定义用户界面对用户输入的响应方式,它连接模型和视图,用于控制应用程序的流程,处理用户的行为和数据上的改变。

 

<div id="app">
<p>text</p>
</div>

生态 - 内置 & 解耦

许多前端框架都内置了 Mvvm 功能,比如 Knockout、Angular、Ember、Avalon、Vue、San 等等。

而就像 Redux 一样,Mvvm 框架中也出现了许多与框架解耦的库,比如 Mobx、Immer、Dob 等,这些库需要一个中间层与框架衔接,比如 mobx-react、redux-box、dob-react。解耦让框架更专注 View 层,实现了库与框架灵活搭配的能力。

解耦的数据流框架也诠释了更高抽象级别的 Mvvm 架构,即:View - 前端框架,Model - (mobx, dob),ViewModel - (mobx-react, dob-react)。

同时也实现了数据与框架分离,便于测试与维护。比如下面的例子,左边是框架无关的纯数据/数据操作定义,右边是 View ViewModel:

图片 3

image

MVC

那时计算机世界天地混沌,浑然一体,然后出现了一个创世者,将现实世界抽象出模型形成model,将人机交互从应用逻辑中分离形成view,然后就有了空气、水、鸡啊、蛋什么的。
——《前端MVC变形记》

上个世纪70年代,美国施乐帕克研究中心,就是那个发明图形用户界面(GUI)的公司,开发了Smalltalk编程语言,并开始用它编写图形界面的应用程序。

到了Smalltalk-80这个版本的时候,一位叫Trygve Reenskaug的工程师为Smalltalk设计了MVC(Model-View-Controller)这种架构模式,极大地降低了GUI应用程序的管理难度,而后被大量用于构建桌面和服务器端应用程序。

 

图片 4

 

如图,实线代表方法调用,虚线代表事件通知。

MVC允许在不改变视图的情况下改变视图对用户输入的响应方式,用户对View的操作交给了Controller处理,在Controller中响应View的事件调用Model的接口对数据进行操作,一旦Model发生变化便通知相关视图进行更新。

为了方便注入内容,改用JS来写,可以写成

运行效率 - 脏检测 & getter/setter 劫持

Angluar 早期的脏检测机制虽然开创了 mvvm 先河,但监听效率比较低,需要 N 1 次确认数据是否有联动变化,就像下图所示:

图片 5

image

现在几乎所有框架都改为 getter/setter 劫持实现监听,任何数据的变化都可以在一个事件循环周期内完成:

图片 6

image

Model

Model层用来存储业务的数据,一旦数据发生变化,模型将通知有关的视图。

myapp.Model = function() {
    var val = 0;

    this.add = function(v) {
        if (val < 100) val  = v;
    };

    this.sub = function(v) {
        if (val > 0) val -= v;
    };

    this.getVal = function() {
        return val;
    };

    /* 观察者模式 */
    var self = this, 
        views = [];

    this.register = function(view) {
        views.push(view);
    };

    this.notify = function() {
        for(var i = 0; i < views.length; i  ) {
            views[i].render(self);
        }
    };
};

Model和View之间使用了观察者模式,View事先在此Model上注册,进而观察Model,以便更新在Model上发生改变的数据。

let str = `<div id="app"><p>test</p></div>`;

语法 - 特殊语法 & 原生语法

早期一些 Mvvm 框架需要手动触发视图刷新,现在这种做法几乎都被原生赋值语句取代。

View

view和controller之间使用了策略模式,这里View引入了Controller的实例来实现特定的响应策略,比如这个栗子中按钮的 click 事件:

myapp.View = function(controller) {
    var $num = $('#num'),
        $incBtn = $('#increase'),
        $decBtn = $('#decrease');

    this.render = function(model) {
        $num.text(model.getVal()   'rmb');
    };

    /*  绑定事件  */
    $incBtn.click(controller.increase);
    $decBtn.click(controller.decrease);
};

如果要实现不同的响应的策略只要用不同的Controller实例替换即可。

至于View-Model,我们要做两件事,一是将数据及时同步到View层,二是响应用户事件,更改数据。我们设计一个函数来完成这项工作。

数据变更方式 - Mutable & Immutable

下图的代码语法虽为 mutable,但产生的结果可能是 mutable,也可能是 immutable,取决于 mvvm 框架内置实现机制:

图片 7

image

Controller

控制器是模型和视图之间的纽带,MVC将响应机制封装在controller对象中,当用户和你的应用产生交互时,控制器中的事件触发器就开始工作了。

myapp.Controller = function() {
    var model = null,
        view = null;

    this.init = function() {
        /* 初始化Model和View */
        model = new myapp.Model();
        view = new myapp.View(this);

        /* View向Model注册,当Model更新就会去通知View啦 */
        model.register(view);
        model.notify();
    };

    /* 让Model更新数值并通知View更新视图 */
    this.increase = function() {
        model.add(1);
        model.notify();
    };

    this.decrease = function() {
        model.sub(1);
        model.notify();
    };
};

这里我们实例化View并向对应的Model实例注册,当Model发生变化时就去通知View做更新,这里用到了观察者模式。

当我们执行应用的时候,使用Controller做初始化:

(function() {
    var controller = new myapp.Controller();
    controller.init();
})();

可以明显感觉到,MVC模式的业务逻辑主要集中在Controller,而前端的View其实已经具备了独立处理用户事件的能力,当每个事件都流经Controller时,这层会变得十分臃肿。而且MVC中View和Controller一般是一一对应的,捆绑起来表示一个组件,视图与控制器间的过于紧密的连接让Controller的复用性成了问题,如果想多个View共用一个Controller该怎么办呢?这里有一个解决方案:

 

图片 8

 

来把王者荣耀压压惊~其实我想说的是MVP模式...

// 把对象转成可监听的
const ob = function (data) {
  // 无new构造
  if (!(this instanceof ob)) {
    return new ob(data);
  }
  // 设定观察者列表
  let observerList = [];

  // 暴露添加观察者方法
  this.addOb = function (fn) {
    observerList.push(fn);
  }

  // 遍历属性,通过defineProperty来对属性变化做监听
  for (let key in data) {
    let value = data[key];
    Object.defineProperty(data, key, {
      enumerable:true, // 枚举
      get () {
        return value;
      },
      set (newVal) {
        value = newVal;
        observerList.forEach((el)=>{
          el(newVal);
        })
      }
    })
  }
};

Connect 的两种写法

由于 mvvm 支持了 mutable 与 immutable 两种写法,所以对于 mutable 的底层,我们使用左图的 connect 语法,对于 immutable 的底层,需要使用右图的 conenct 语法:

[图片上传失败...(image-b7408b-1522595335875)]

对左图而言,由于 mutable 驱动,所有数据改动会自动调用视图刷新,因此不但更新可以一步到位,而且可以数据全量注入,因为没用到的变量不会导致额外渲染。

对右图,由于 immutable 驱动,本身并没有主动驱动视图刷新能力,所以当右下角节点变更时,会在整条链路产生新的对象,通过 view 更新机制一层层传导到要更新的视图。

MVP

MVP(Model-View-Presenter)是MVC模式的改良,由IBM的子公司Taligent提出。和MVC的相同之处在于:Controller/Presenter负责业务逻辑,Model管理数据,View负责显示。

 

 

 图片 9

虽然在MVC里,View是可以直接访问Model的,但MVP中的View并不能直接使用Model,而是通过为Presenter提供接口,让Presenter去更新Model,再通过观察者模式更新View。

与MVC相比,MVP模式通过解耦View和Model,完全分离视图和模型使职责划分更加清晰;由于View不依赖Model,可以将View抽离出来做成组件,它只需要提供一系列接口提供给上层操作。

本文由betway必威发布于网页设计,转载请注明出处:前端数据流框架精讲,撸一个简单的MVVM例子

Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。