搜索
简帛阁>技术文章>MVVM框架原理浅谈

MVVM框架原理浅谈

MVVM基本原理

MVVM(Model-View-ViewModel)本质上就是MVC 的改进版,MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。

MVVM相比与MVC模式主要是分离了试图和模型,所有的交互都通过ViewModel进行了数据的通知与交互,从而达到了低耦合的优点,View的修改可独立于Model的修改,可提高重用性,可在View中重用独立的视图逻辑。归根结底可总结为数据的双向绑定的过程,即ViewModel的数据与Model的绑定,ViewModel的数据与View中的数据绑定,从而达到数据低耦合高重用性的过程。

MVVM的原理的基础

MVVM的基础模式其实不复杂,通过该原理主要就是需要实现ViewModel层到Model层与View层的交互过程。在Vue的官方示例图中,给出了如下图示;

从图示所致,DOM就是视图,Model就是JS对象,Vue就是作为ViewModel存在将双向的数据进行绑定,那如何来实现一个简单的双向数据绑定呢?

MVVM的流程梳理

根据js的相关内容来实现的思考,基本流程如下;

大致的思路逻辑如上所示,其中在Model修改数据的时候主要就是通过addEventListener事件来注册回调函数,ViewModel到View的事件或者View到ViewModel的事件主要就是通过Object.defineProperty属性来设置值。

假如数据格式如下;

视图(View)
<input v-model="c" type="text">  

MVVM框架(ViewModel)
将视图的内容和数据当做参数传入Mvvm中生成一个实例

数据(Model)
{c: 2}
MVVM实例初始化过程
  1. 首先,根据传入的Model,调用Object.defineProperty来劫持数据,并设置set和get方法,并注册回调watch更新视图函数
  2. 通过传入的视图信息编译初始化一个视图,并根据传入的编译后的视图添加监听函数,监听html中输入输入的响应事件,如果触发则回调设置到Mvvm中的对应的实例值
  3. 如果是通过其他事件修改了Model的值则执行注册的watch方法,重新生成对应的视图内容,渲染出新的页面值,从而完成从数据到视图的更新

以上,就基本是MVVM框架中从Model层到View层,和View层到Model层的数据的交流的概述流程。

实现简易的MVVM框架
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="app">
        <h1>{<!-- -->{singer}}</h1>                       <!-- 模板渲染 -->
        <h1>{<!-- -->{song.first}}</h1>                   <!--  递归嵌套 -->
        <input v-model="singer" type="text">      <!--  双向绑定 -->
    </div>
</body>
<script >

    function Mvvm(options = {<!-- -->}) {<!-- -->
        // vm.$options Vue上是将所有属性挂载到上面
        this.$options = options;
        let data = this._data = this.$options.data;

        this.init()                     // 初始化设置熟悉

        // 数据劫持
        observe(data);

        new Compile(options.el, this)   // 编译内容
    }

    // Dep 订阅发布模式实现类
    function Dep() {<!-- -->
        // 一个数组(存放函数的事件池)
        this.subs = [];
    }
    Dep.prototype.addSub = function(sub){<!-- -->
        this.subs.push(sub);
    },

    Dep.prototype.notify = function(val) {<!-- -->
        // 绑定的方法,都有一个update方法
        this.subs.forEach(sub => sub.update(val));
    }

    // 数据劫持
    function observe(data){<!-- -->
        // 如果传入为空或者不是object则返回
        if (!data || typeof data !== "object"){<!-- -->
            return
        }

        for (let key in data){<!-- -->
                let dep = new Dep()
                let val = data[key]
                observe(val)      // 嵌套调用
                Object.defineProperty(data, key, {<!-- -->
                    configurable: true,
                    get() {<!-- -->
                        // 通过该函数注册回调函数到dep
                        if (Dep.target){<!-- -->
                            dep.addSub(Dep.target)
                        }
                        return val;
                    },
                    set(newVal) {<!-- -->
                        if (val === newVal) {<!-- -->
                            return;
                        }
                        // 更新回调视图
                        val = newVal;
                        dep.notify(newVal)
                    }
                })
            }
    }

    Mvvm.prototype = {<!-- -->
        init() {<!-- -->
            for (let key in this._data){<!-- -->
                Object.defineProperty(this, key, {<!-- -->
                    configurable: true,
                    get() {<!-- -->
                        return this._data[key];
                    },
                    set(newVal) {<!-- -->
                        this._data[key] = newVal;
                    }
                })
            }
        }
    }

    function Compile(el, vm){<!-- -->
        vm.$el = document.querySelector(el);
        // 在el范围里将内容都拿到,当然不能一个一个的拿
        let fragment = document.createDocumentFragment();

        while (child = vm.$el.firstChild) {<!-- -->
            fragment.appendChild(child);    // 此时将el中的内容放入内存中
        }
        // 对el里面的内容进行替换
        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('.');   // 匹配到的第一个分组 如: a.b, c
                    let val = vm;
                    arr.forEach(key => {<!-- -->
                        val = val[key];
                    });

                    let w = new Watcher(vm, RegExp.$1, function(newVal) {<!-- -->
                        node.textContent = newVal;   // 当watcher触发时会自动将内容放进输入框中
                    });
                    node.textContent = txt.replace(reg, val).trim();
                }

                if (node.nodeType == 1) {<!-- -->
                    // 检查是否是v-model属性值
                    let nodeAttr = node.attributes
                    Array.from(nodeAttr).forEach(attr => {<!-- -->
                        let name = attr.name;   // v-model  type
                        let exp = attr.value;   // data key
                        if (name == "v-model"){<!-- -->
                            node.value = vm[exp]
                            let w = new Watcher(vm, exp, function(newVal) {<!-- -->
                                node.value = vm[exp];   // 当watcher触发时会自动将内容放进输入框中
                            });
                        }

                        node.addEventListener('input', e => {<!-- -->
                            let newVal = e.target.value;
                            // 而值的改变会调用set,set中又会调用notify,notify中调用watcher的update方法实现了更新
                            vm[exp] = newVal;
                        });
                    })
                }
                if (node.childNodes && node.childNodes.length) {<!-- -->
                    replace(node);
                }
            });
        }

        replace(fragment);  // 替换内容

        vm.$el.appendChild(fragment);   // 再将文档碎片放入el中

    }


    // 监听函数
    function Watcher(vm, exp, fn) {<!-- -->
        this.vm = vm
        this.exp = exp
        this.fn = fn;   // 将fn放到实例上
        let arr = exp.split('.');   // 检查是否是深层次递归嵌套
        let data = this.vm._data
        if (arr.length > 1){<!-- -->
            let length = arr.length
            for(i=0;i<length-1;i++){<!-- -->
                data = data[arr[i]]
            }
            // 只注册最后一个回调函数
            Dep.target = this
            const value = data[arr[length-1]]
        } else {<!-- -->
            Dep.target = this
            const value = data[exp]
        }
        Dep.target = null
    }
    Watcher.prototype.update = function(val){<!-- -->
            this.fn(val)
    };

    let app = new Mvvm({<!-- -->el: "#app",data: {<!-- -->"singer": "singer1", "song": {<!-- -->"first": "contry", "second": "home"}}})

</script>
</html>

该段代码运行在浏览器之后,在输入框中输入数据,可看到第一行的数据也会跟着改变,此时打开浏览器调试窗口的终端,

从终端打印信息可看出,基本都是实现了set get方法,并且会根据数据的改变从而改变视图。

本段代码参考了网上一些实现的代码,并做了修改,代码的实现思路跟上图中的流程图相似。在实现的过程中也借用了闭包的原理,为每一个属性值在defineProperty的时候,创建一个订阅数组dep,然后当数据有更改之后,就通知所有订阅该dep的所有接受着,这样确保每次的数据修改都只修改到订阅的局部的订阅者。

主要做的两件事情就是将数据进行双向的绑定,具体的实现大家可自行深入学习,本文的示例代码基于网上实例进行修改,原文来自不好意思!耽误你的十分钟,让MVVM原理还给你。

总结

本文主要就是简单的探究了一下MVVM框架的基本原理,仅仅是作为在使用vue框架的时候的基础回顾,原理并不算复杂,主要就是通过数据的双向绑定从而提高开发效率与代码复用率。由于本人才疏学浅,如有错误请批评指正。

MVVM基本原理MVVM(ModelViewViewModel)本质上就是MVC的改进版,MVVM就是将其中的View的状态和行为抽象化,让我们将视图UI和业务逻辑分开。MVVM相比与MVC模式主要
MVVM模式即:1、Model:数据层。网络数据操作,file文件操作,本地数据库操作;2、View:视图层。布局加载,ui交互。3、ViewModel:vm,关联层。数据变化自动更新绑定的view,
首先了解一下MVCModel:数据模型,用来存储数据View:视图界面,用来展示UI界面和响应用户交互Controller:控制器(大管家角色),监听模型数据的改变和控制视图行为、处理用户交互接下来介
1MVVM是什么?响应式,双向数据绑定,即MVVM。是指数据层(Model)视图层(View)数据视图(ViewModel)的响应式框架。它包括:1修改View层,Model对应数据发生变化。2Mo
MVCM:Model模型——数据(对于前台而言例如:ajax、jsonp等从后台获取数据的)V:View视图——表现层(Html/css)用户能看见的C:Controller控制器——业务逻辑(先干什
不知不觉接触前端的时间已经过去半年了,越来越发觉对知识的学习不应该只停留在会用的层面,这在我学jQuery的一段时间后便有这样的体会。虽然jQuery只是一个JS的代码库,只要会一些JS的基本操作学习
现成MVVM菜单教程<!DOCTYPEhtml><html><head><metacharset"utf8"><title>Vue测试实例菜
MVVM是ModelViewViewModel的简写。它本质上就是MVC的改进版。MVVM就是将其中的View的状态和行为抽象化,让我们将视图UI和业务逻辑分开。当然这些事ViewModel已经帮我
mvc的模式已经深入人心,想必大家都很熟悉,但是未必都能遵守mvc模式。我们的一个mvc项目比较简单,主要是数据库的查询。一个DBHelp类,封装了数据库的操作,然后Controller中进行中各种查
MVC是最经典的开发模式之一,最早是后台那边来的,后台前端的复杂度也上来了,MVC的开发模式也带进前端了。MVC:MVC有两个很明显的问题:1m层和v层直接打交道,导致这两层耦合度高2因为所有逻辑都写