MVVM机制浅析

来公司一年多了,接触了angular,vue这两种主流的MVVM框架,之前一直没有去思考其实现的原理,但框架其实也是用基础的js方法实现的,所以还是很有必要去了解下其原理的,在这里简单的总结一下。

1,几种实现双向绑定的方法

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

  • 发布者-订阅者模式(backbone.js)
  • 脏值检查(angular.js)
  • 数据劫持(vue.js)

发布者-订阅者模式: 一般通过sub,pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 vm.set('property', value),现在用的较少,这里不再阐述其原理了,有兴趣的同学可以点击这里

脏值检查: ng是通过脏检查的检测机制来对比数据是否发生了变更,来决定是否更新视图。那么脏检查是怎么触发的呢,大致有一下几种情况:

  • UI事件,如ng-click
  • ajax请求
  • timeout延迟
  • 执行$apply()(非ng事件时需我们手动触发脏检查)

这里我们就脏值检查举个小栗子:

// html
<div ng-app="app">
    <div ng-controller="myController">
        <span>{{count}}</span>
        <button ng-click="counter=counter+1">increase</button>
    </div>
</div>


// js
var app = angular.module('app', []);
app.controller('myController', function($scope) {
    $scope.count = 0;
})
复制代码

看下这时的效果

我们发下这时通过ng-click形成的数据更改是实时响应的。来,让我们换种实现方式:

<div ng-app="app">
    <div ng-controller="CounterCtrl">
        <span>{{data}}</span>
        <button my-click>click</button>
    </div>
</div>

app.controller('CounterCtrl', function($scope) {
    $scope.data = 0;
})
app.directive('myClick', function() {
    return function(scope, element, attr) {
        element.on('click', function() {
            scope.data++;
            console.log(scope.data);
        })
    }
})
复制代码

这时的效果

我们发现虽然data的值在更改,但是view却并没有更新,这是因为我没有用ng事件来触发UI事件,所以没有执行脏检查,这个时候我们可以手动加下$apply试下效果。

数据劫持: vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

2,数据劫持的代码实现

这里我们用数据劫持的方式来实现一个简易的数据绑定,我们来写一下:

简单的写下html

<div id='app'>
    <h3>姓名</h3>
    <p>{{name}}</p>
    <h3>年龄</h3>
    <p>{{age}}</p>
</div>
复制代码

接下来准备我们要展示数据

document.addEventListener('DOMContentLoaded', function() {
    let opt = {
        el:'#app', 
        data:{
            name:'准备中...', 
            age:30
        }
    }
    let vm = new MVVM(opt);
    setTimeout(() => {
        opt.data.name = '小明';
    }, 2000);
})
复制代码

好,到这里准备工作都做好了,接下来是重头戏,不多说,直接上代码:

class MVVM {
    constructor(opt) {
        this.opt = opt;
        this.observe(opt.data);
        let root = document.querySelector(opt.el);
        this.complie(root);
    }
    
    // 绑定观察者对象
    observe(data) {
        Object.keys(data).forEach(key => {
            // 声明一个观察者对象
            let obv = new Observer();
            
            // 记录data[key]的值,防止set的时候递归调用报错
            data['_' + key] = data[key];
            
            Object.defineProperty(data, key, {
                get() {
                    Observer.target && obv.addSubNode(Observer.target);
                    return data['_' + key];
                },
                set(newVal) {
                    obv.update(newVal);
                    data['_' + key] = newVal;
                }
            })
        })
    }
    
    // 初始化页面,遍历 DOM,收集每一个key变化时,随之调整的位置,以观察者方法存放起来    
    complie(node) {
        [].forEach.call(node.childNodes, child =>{
            if(!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)){
                let key = RegExp.$1.trim()
                child.innerHTML = child.innerHTML.replace(new RegExp('\\{\\{\\s*'+ key +'\\s*\\}\\}', 'gm'),this.opt.data[key]) 
                
                // 记录当前的DOM节点
                Observer.target = child;
                
                // 调用set函数
                this.opt.data[key];
                
                Observer.target = null;
            }
            else if (child.firstElementChild) {
                this.compile(child);
            }
        })
    }
}

// 常规观察者类
class Observer{
    constructor() {
        this.subNode = []    
    }
    addSubNode(node){
        this.subNode.push(node)
    }
    update(newVal){
        this.subNode.forEach(node=>{
            node.innerHTML = newVal
        })
    }
}

复制代码

好了至此就是所有的代码,我们可以把opt声明为全局变量,这样我们就可以在控制台修改相关数据,然后实时观察修改结果。

具体的实现代码参照了这篇文章50行代码的MVVM,感受闭包的艺术,当时看到的时候有些地方比较困惑,理解了之后这里将其贴上来,注释上我自己的理解,仅供学习参考吧。(感觉data['_' + key] 这种记录方式不太好,引入了无关的值)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值