Vue框架MVVM架构的原理实现详解

2023-06-01 00:00:00 架构 框架 详解

1.什么是MVVM?


MVVM其实表示的是 Model-View-ViewModel

Model:模型层,负责处理业务逻辑以及和服务器端进行交互

View:视图层:负责将数据模型转化为UI展示出来,可以简单的理解为HTML页面

ViewModel:视图模型层,用来连接Model和View,是Model和View之间的通信桥梁



在MVVM的架构下,View层和Model层并没有直接联系,而是通过ViewModel层进行交互。 ViewModel层通过双向数据绑定将View层和Model层连接了起来,使得View层和Model层的同步工作完全是自动的。

因此开发者只需关注业务逻辑,无需手动操作DOM。


1.png


Vue 与 MVVM的关系




其实,Vue 框架就是一个典型的 MVVM 模型的框架。

Vue 框架其实就是起到 MVVM 模式中的 ViewModel 层的作用。

使用代码来理解之间的关系:

使用jQuery来操作DOM元素,添加一个button按钮,并绑定click事件


if(Btn){

    let btn = $('<button>点我</butten>')

    btn.on('click',function(){

        console.log('点到我了...')

    });

    $('#app').append(btn)

}



从上述代码可以看到,负责视图的 HTML 代码和负责业务逻辑的 JS 代码耦合到一起,这是个很严重的问题

如果我们直接操作DOM元素,会造成性能低下等一系列问题

如果使用Vue的话,可以将视图层和模型层有效地分离开来




<template>

   <div>

     <button @click="handeClick()">点我</button>

   </div>

</template>

<script>

export default {

   name:'App',

   methods:{

     handleClick:function(){

       console.log('点到我了...');

     }

   },

}

</script>



上面这段代码可以看到,负责视图的 HTML 代码和负责业务逻辑的 JS 代码有效地分离开来。之所以能做到这一点,主要是依靠 Vue 框架才得以实现的。



2.png


MVVM原理

上面已经说了,

View层和Model层并没有直接联系,而是通过ViewModel层进行交互。 ViewModel层通过双向数据绑定将View层和Model层连接了起来,使得View层和Model层的同步工作完全是自动的。

实现数据绑定的做法有大致如下几种:



脏值检查(angular.js)

数据劫持(vue.js)

发布者-订阅者模式(backbone.js)

这里大致说下脏值检查: angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 setInterval() 定时轮询检测数据变动,angular只有在指定的事件触发时进入脏值检测,大致如下:




DOM事件,譬如用户输入文本,点击按钮等。( ng-click )

XHR响应事件 ( $http )

浏览器Location变更事件 ( $location )

Timer事件( $timeout , $interval )

执行 $digest() 或 $apply()



实现双向数据绑定步骤

要实现mvvm的双向绑定,就必须要实现以下几点:

1.实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者

2.实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

3.实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图

4.mvvm入口函数,整合以上三者

3.png




实现MVVM


<!DOCTYPE html>

<html>

<head>

    <meta charset="UTF-8">

    <title>Two-way data-binding</title>

</head>

<body>

    <div id="app">

        <input type="text" v-model="text">

        {{ text }}

    </div>

    <script>

        function observe (obj, vm) {

            Object.keys(obj).forEach(function (key) {

                defineReactive(vm, key, obj[key]);

            });

        }

        function defineReactive (obj, key, val) {

            var dep = new Dep();

            Object.defineProperty(obj, key, {

                get: function () {

                    if (Dep.target) dep.addSub(Dep.target);

                    return val

                },

                set: function (newVal) {

                    if (newVal === val) return

                    val = newVal;

                    dep.notify();

                }

            });

        }

        function nodeToFragment (node, vm) {

            var flag = document.createDocumentFragment();

            var child;

            while (child = node.firstChild) {

                compile(child, vm);

                flag.appendChild(child);

            }

            return flag;

        }

        function compile (node, vm) {

            var reg = /\{\{(.*)\}\}/;

            // 节点类型为元素

            if (node.nodeType === 1) {

                var attr = node.attributes;

                // 解析属性

                for (var i = 0; i < attr.length; i++) {

                    if (attr[i].nodeName == 'v-model') {

                        var name = attr[i].nodeValue; // 获取v-model绑定的属性名

                        node.addEventListener('input', function (e) {

                            // 给相应的data属性赋值,进而触发该属性的set方法

                            vm[name] = e.target.value;

                        });

                        node.value = vm[name]; // 将data的值赋给该node

                        node.removeAttribute('v-model');

                    }

                }

                new Watcher(vm, node, name, 'input');

            }

            // 节点类型为text

            if (node.nodeType === 3) {

                if (reg.test(node.nodeValue)) {

                    var name = RegExp.$1; // 获取匹配到的字符串

                    name = name.trim();

                    new Watcher(vm, node, name, 'text');

                }

            }

        }

        function Watcher (vm, node, name, nodeType) {

        //  this为watcher函数

            Dep.target = this;

        //  console.log(this);

            this.name = name;

            this.node = node;

            this.vm = vm;

            this.nodeType = nodeType;

            this.update();

            Dep.target = null;

        }

        Watcher.prototype = {

            update: function () {

                this.get();

                if (this.nodeType == 'text') {

                    this.node.nodeValue = this.value;

                }

                if (this.nodeType == 'input') {

                    this.node.value = this.value;

                }

            },

            // 获取daa中的属性值

            get: function () {

                this.value = this.vm[this.name]; // 触发相应属性的get

            }

        }

        function Dep () {

            this.subs = []

        }

        Dep.prototype = {

            addSub: function(sub) {

                this.subs.push(sub);

            },

            notify: function() {

                this.subs.forEach(function(sub) {

                    sub.update();

                });

            }

        };

        function Vue (options) {

            this.data = options.data;

            var data = this.data;

            observe(data, this);

            var id = options.el;

            var dom = nodeToFragment(document.getElementById(id), this);

            // 编译完成后,将dom返回到app中

            document.getElementById(id).appendChild(dom);

        }

        var vm = new Vue({

            el: 'app',

            data: {

                text: 'hello world'

            }

        });

    </script>

</body>

</html>



转:


https://juejin.cn/post/6844903929298288647?utm_source=gold_browser_extension%3Futm_source%3Dgold_browser_extension


相关文章