博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于ES5`defineProperty` 实现简单的 Mvvm框架
阅读量:5994 次
发布时间:2019-06-20

本文共 6281 字,大约阅读时间需要 20 分钟。

基于ES5defineProperty 实现简单的 Mvvm框架

PREPARE

现阶段前端三大主流框架react,vue, angular都属于 MVVM范畴,即 模型---视图---视图模型

采用数据驱动, 即监听数据改变,渲染view。

核心是监听数据的变更!

其中React使用的是 diff 算法来实现数据变更检测的;

Angular 则使用的是zone.js实现数据变更检测;

Vue则使用Object.defineProperty, 后期版本则使用Object.Proxy

本文参考Vue 使用 Object.defineProperty实现数据变更检测, 实现一个简单的 mvvm框架

INIT

1.下面是一个简单的类Vue组件的实现方式
  • html

    {
    {song}}

    {

    {singer.a.b}}

    {

    {age}}

    复制代码
  • javascript

    let mvvm = new Mvvm({           el: '#app',           data: {               song: 2,               singer: {                   a: {                       b: 1                   },                   c: 1               },               age: 55           }       })复制代码
  • 首先是一个 Mvvm类,接受两个参数(后期会加入method等参数): eldata

2. 定义Mvvm
function Mvvm(options = {}) {        /*定义类的$option属性,_data私有属性,并将 私有$option.data的引用复制给私有属性_data和局部变  量data     */    this.$options = options;    let data = this._data = this.$options.data;        /*将data中所有的key设置观察者,增加数据变更的检测*/    observe(data);        /*将data中的所有的key代理到Mvvm实例上,形成mvvm实例树,方便书写*/    for (let key in data) {        Object.defineProperty(this, key, {            configurable: true,            get() {                return this._data[key];            },            set(newVal) {                this._data[key] = newVal;            }        })    }        /*数据如果变更,执行dom渲染*/    new Compile(this.$options.el, this)}复制代码
3.看一下 observe 方法:
function observe(data) {    if (!data || typeof data !== 'object') return;    return new Observe(data);}复制代码

增加了一个data类型检测机制(实际上为了递归结尾判断),实际上执行 Observe类的实例化

#####4. Observe

function Observe(data) {        /*创建一个消息订阅发布池类,包含一个watcher属性,watcher指向一个Watcher的实例,每个Watcher实例  都是一个订阅者,另一个属性是一个事件池数组events*/        let dep = new Dep();        /*data的每一个键值对循环执行observe方法*/    for (let key in data) {        let val = data[key]        observe(val);                /*对data的每一个key进行数据拦截, 设置get,当创建一个Watcher实例的时候,显示触发get,此时将这个Watcher实例(订阅者)添加到消息订阅发布池的事件池中;设置set 当设置新的值时候,触发 set方法, 如果所设值与原来不相等, 则重新监听新的值得变更, 并触发Watcher实例(订阅者)的notify方法,触发 Watcher实例的回调,更新视图数据*/        Object.defineProperty(data, key, {            configurable: true,            get() {                Dep.watcher && dep.addEvent(Dep.watcher);                return val;            },            set(newVal) {                if (val === newVal) {                    return;                }                val = newVal;                observe(newVal);                dep.notify();            }        })    }}复制代码
5.消息订阅发布池Dep
  • 类:

    function Dep() {    this.watcher = null;    this.events = [];}复制代码
  • 实例:

    Dep.prototype = {    addEvent(event) {        this.events.push(event);    },    notify() {        this.events.forEach(event => event.update())    }}复制代码

#####6. 订阅者Watcher

  • /*Watcher接收三个参数,第一个是整个mvvm实例树对象, 第二个是html中的插值表达式{
    {song.a.b}}, fn为设置新值之后的会调*/function Watcher(vm, exp, fn) { this.fn = fn; this.vm = vm; this.exp = exp; // 将watcher的实例赋值给Dep的watcher属性,方便调用,而不用传参 Dep.watcher = this; // 进行一次取值操作, 显示触发mvvm实例树某个key的get方法,从而将 Dep.watcher 添加到事件池中 // 将如song.a.b以点号分割成数组arr,将mvvm实例树的引用赋值给局部变量val let arr = exp.split('.'); let val = vm; // 循环arr 取song.a.b的值 arr.forEach(key => { val = val[key] }); // 添加完之后,释放 Dep.watcher Dep.watcher = null;}复制代码
  • 实例

    /*Watcher实例的update方法会从mvvm实例树上取出exp所对应的值,并触发fn回调,渲染视图*/Watcher.prototype.update = function () {    let arr = this.exp.split('.');    let val = this.vm;    arr.forEach(key => {        val = val[key]    });    this.fn(val);}复制代码
7.Compile类

Compile类用于将所选的el元素节点 赋值给mvvm实例树,并转为createDocumentFragment 文档片段, 之后所需要替换的文本节点进行正则匹配并替换,之后将新的文档片段统一添加到el元素节点中。

关于文档碎片可以 了解

function Compile(el, vm) {    /*获取元素节点*/    vm.$el = document.querySelector(el);        /*创建文档碎片对象*/    let fragment = document.createDocumentFragment();        /*当vm.$el.firstChild存在时,将vm.$el.firstChild依次加入到文档碎片中*/    while (child = vm.$el.firstChild) {        fragment.appendChild(child);    }        /*对html文档中的插值表达式等进行替换*/    function replace(frag) {                       Array.from(frag.childNodes).forEach(node => {                        // 插值表达式            let txt = node.textContent;            let reg = /\{\{(.*?)\}\}/g;            if (node.nodeType === 1 && reg.test(txt)) {                let arr = RegExp.$1.split('.');                let val = vm;                arr.forEach(key => {                    val = val[key]                });                // 执行替换                node.textContent = txt.replace(reg, val).trim();                                // 添加监听                new Watcher(vm, RegExp.$1, (newVal) => {                    node.textContent = txt.replace(reg, newVal).trim();                })            }                        // 双向数据绑定            if(node.nodeType ===1 ) {                let nodeAttr = node.attributes;                // console.log(nodeAttr)  Map                Array.from(nodeAttr).forEach(attr => {                    let name = attr.name;                    let value = attr.value;                    if (name.includes('v-')) {                                                let arr = value.split('.');                        let val = vm;                        arr.forEach( key => {                            val = val[key]                        })                        console.log(value)                        node.value = val                    }                    new Watcher(vm, value, (newVal) => {                        node.value = newVal;                    });                    node.addEventListener('input', e => {                        // 根据传入的绑定值的对象深度值来处理, 如果是单个值,则直接赋值, 如果是多个,则使用eval()函数处理                        if(value.split('.').length > 1) {                              eval("vm."+ value + "= e.target.value");                        } else {                            vm[value] = e.target.value                        }                                            })                })            }            if (node.childNodes && node.childNodes.length) {                replace(node)            }        })    }    replace(fragment);    vm.$el.appendChild(fragment);}复制代码

LAODED

以上是一个简单的mvvm框架的实现,当然defineProperty还是有一些问题,比如说对应数组的变更检测是办不到,而Proxy的出现则解决了这类问题,有时间的话,大家可以试试基于Proxy去实现一套简单的mvvm框架。

转载于:https://juejin.im/post/5b0cbcb1f265da090a00814d

你可能感兴趣的文章
LDAP基本命令
查看>>
最简单的Java导出Excel中的数据
查看>>
了解开源文化
查看>>
android开发,修改系统开机动画
查看>>
四层和七层负载均衡的区别
查看>>
Curator实现分布式锁
查看>>
Eclipse 快捷键
查看>>
值类型与引用类型的区别
查看>>
Session的序列化
查看>>
我的友情链接
查看>>
数据中心服务器托管VMware虚拟化网络配置最佳实践
查看>>
汇总各浏览器核心(js引擎及排版引擎)深究
查看>>
我的友情链接
查看>>
SE38标准程序ACCESS KEY被修改后处理方法
查看>>
SaltStack的配置管理--jinja
查看>>
Docker 安装Nginx
查看>>
android Window Leaked异常的解决方法
查看>>
Linux必学的60个命令(2)-文件处理
查看>>
Diamond设计思想杂碎
查看>>
Android SDK Readme.txt翻译
查看>>