前端架构模式MVVM及数据双向绑定原理

目录

 

一、概述

二、什么是MVVM?

三、什么是双向数据绑定?

四、监听视图(DOM)改变(View——>Model)

五、监听模型数据(Model———>View)

     1.什么是数据劫持?

     2.为什么要使用数据劫持?

     3.数据劫持实现原理

     4.代码示例

 a.Object.defineProperty(obj,prop,descriptor)示例

 b.Object.keys(obj)示例

 c.Object数据劫持实现双向绑定示例

 d.Array方法数据劫持实现双向绑定示例(以push方法为例)


一、概述

           JS 几个流行的框架 Vuejs、AngularJS 都使用 MVVM 模式,该模式叫做视图模型双向数据绑定,以达到数据和视图快速同步的目的。

           主要体现就是表单元素值变,JS变量值改变,若页面上有输出,输出值也改变,典型的代码如下

<div id="app">
  <p>{{ message }}</p>
  <input v-model="message">
</div>
new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

运行效果:

二、什么是MVVM?

             MVVM 就是 Model-View-ViewModel的缩写,由Model ,View,ViewModel三部分组成,它是一种前端开发的架构模式。

  • Model :代表的是模型、数据,可以在 Model 层中定义数据修改和操作的业务逻辑。
  • View :代表的是视图,模版,用来显示数据。
  • ViewModel :连接 Model 和 View桥梁。

    如下图:

                   

在 MVVM 的架构下,View 层(DOM展示层)和 Model 层(数据对象层)并没有直接联系,而是通过 ViewModel 层进行交互。

ViewModel 层通过双向数据绑定将 View 层和 Model 层连接起来,使得 View 层和 Model 层的同步工作完全是自动的。

因此开发者只需关注业务逻辑,无需手动操作 DOM,复杂的数据状态维护交给 MVVM 统一来管理,称之为数据驱动的开发方法。

三、什么是双向数据绑定?

就是指数据传递有两个方向:

方向1:从View———>Model,View变化时,Model也发生变化。

方向2:从Model———>View,Model变化时,View也发生变化。

若要实现以上的数据传递,我们需要去监听视图(DOM)变化和监听数据的变化,才能做出相应改变。

四、监听视图(DOM)改变(View——>Model)

        通过 DOM 事件就可以实现监听视图的改变,例如 input 的 change、input 事件都可以完成监听,通过事件处理器,实现对应的模型的数据改变。

        如上图例子中的 input 元素的值的不断改变,就是 DOM 的改变,我们监听到之后,将模型变量 message 做同步更改!就是视图(View)改变传递到了模型(Model)上。

        为什么页面的展示也变了呢?就是下一个传递了,模型改变传递到了视图上。数据的改变传递到视图上如何实现的呢?就是监听数据来实现。

五、监听模型数据(Model———>View)

        上面message变量的值就是模型数据,那么模型数据的变化我们如何可以监听到呢?这就用到了数据劫持

     1.什么是数据劫持?

            在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改,然后返回结果。

            数据劫持实现的核心API就是ES5中提供的方法Object.defineProperty()以及基于数组的数据修改方法push、pop、unshift、shift、splice、sort、reverse。

           注:目前Vue中能做到响应式的数组操作方法如下:
             push():在数组最后面添加元素
             pop():删除数组中的最后一个元素
             shift():删除数组中的第一个元素
             unshift():在数组最前面添加元素
             splice():删除、插入、替换
                       //删除元素:第二个参数传入你要删除几个元素,(如果没有传,则删除后面所有元素)
                       //替换元素:第二个参数表示我们要替换几个元素,后面是用于替换前面的元素
                       //插入元素:第二个参数传入0,并且后面跟上要插入的元素
             sort():排序
             reverse():反转

     2.为什么要使用数据劫持?

           首先在前端页面渲染中,触发渲染方案是基于事件机制实现,这种渲染的方案有个很大的弊端,就是需要通过事件监听机制触发JS事件,然后JS通过document获取需要重新渲染的DOM,             然后在js的DOM模型上修改数据,触发document渲染页面。在浏览器中document只是提供给JS操作文档模型的接口,双方通信通道资源有限,基于事件机制触发页面渲染,会消耗这个通               道的大量资源,降低浏览器性能。

            

        下面来看看基于数据劫持实现数渲染的模型图(JS与document通讯仅仅只需要一次,而且基于虚拟DOM(https://segmentfault.com/a/1190000016647776的支持,还可以实现最精准的                 DOM渲染):

             1309608-20190825142254246-1484027956.png

     3.数据劫持实现原理


Object.defineProperty(obj,prop,descriptor):定义属性或修改原有的属性值。

参数
 obj:目标对象
 prop:需要定义的属性或方法的名称
 descriptor:目标属性所拥有的特性

可供定义的特性列表
 value: 属性的值
 writable: 如果为false,属性的值就不能被重写。
 get: 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。
 set: 一旦目标属性被赋值,就会调回此方法。
 configurable: 如果为false,则任何尝试删除目标属性或修改属性性以下特性(writable,    configurable, enumerable)的行为将被无效化。
 enumerable: 是否能在for...in循环中遍历出来或在Object.keys中列举出来。



Object.keys(obj):返回一个由给定对象的自身可枚举属性组成的数组。

参数:要返回其枚举自身属性的对象

返回值:一个表示给定对象的所有可枚举属性的字符串数组

     4.代码示例

 a.Object.defineProperty(obj,prop,descriptor)示例

    let oData = {name:"test"};
    let val = oData["name"];
    Object.defineProperty(oData,"name",{
        get(){
            console.log("getter...",val);
            return val;
        },
        set(newValue){
            console.log("setter...",newValue);
            if(newValue == val){
                return false;
            }else{
                val = newValue;
            }
        }
    });

运行结果:

 b.Object.keys(obj)示例

    let obj = {name:"张三",age:25,address:"深圳",getName:function(){}};
    let arr = [1,2,3,4,5,6];
    let result1 = Object.keys(obj);
    let result2 = Object.keys(arr);
    console.log(result1);
    console.log(result2);

        运行结果:

           

c.Object数据劫持实现双向绑定示例

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>针对Object数据劫持实现数据双向绑定示例</title>
</head>
<body>
<input type="text" name="" id="demo"/>
<div id="show"></div>
</body>
</html>
<script>
var oDiv = document.getElementById('show');
var oInput = document.getElementById('demo');
var oData =
    {
        valueObj:{
                   value:'test'
                 },
        name:'默认初始值'
    };
//输入框事件:触发数据修改(写入)(监听view内容变化修改model值)
oInput.oninput = function(){
    oData.name = this.value;
}
//修改DOM数据(页面渲染)
function upDate(){
    oDiv.innerText = oData.name;
}
//初始化页面数据渲染
upDate();

//监听Model(这里就是oData对象)的方法
function Observer(data){
    if(!data || typeof data != 'object'){
        return data;
    };
    Object.keys(data).forEach(function(item){
        definedRective(data,item,data[item]);
    })
}

//当setter被触发时,修改数据并渲染到页面
function definedRective(data,key,val){
    //使用递归深度监听对象数据变化,如对oData.valueObj.value的监听
    Observer(val);
    Object.defineProperty(data,key,{
        get(){
            return val;
        },
        set(newValue){
            if(newValue == val) return;
            val = newValue;
            upDate(); //数据渲染到DOM
        }
    })
}

//观察oData
Observer(oData);
</script>

d.Array方法数据劫持实现双向绑定示例(以push方法为例)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>针对Array数据劫持实现数据双向绑定示例</title>
</head>
<body>
<input type="text" name="" id="demo"/>
<div id="show"></div>
</body>
</html>
<script>
    var oDiv = document.getElementById('show');
    var oInput = document.getElementById('demo');
    let arr = ["test"];
    //{push}相当于把Array对象里名字叫做push的那个属性的值拿出来赋给push
    let {push} = Array.prototype;
    function upArrData(){
        oDiv.innerText = arr[arr.length-1];
    }
    upArrData();
    oInput.oninput = function(){
        arr.push(this.value);
    }
    Object.defineProperty(Array.prototype,'push',{
        value:(function(){
            return function(...arg){
                //此处push就是上面{push}中的push
                push.apply(arr,arg);
                upArrData();
            }
        })()
    });
</script>

以上就是前端架构模式MVVM及数据双向绑定原理的核心实现数据劫持,作为MVVM架构模式框架,Vue的数据双向绑定核心实现其实除了上面的数据劫持,还有另外一个,就是发布订阅模式,有兴趣的自己研究去吧。

 

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

heroleader

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值