实现一个MVVM

想弄明白MVVM具体是怎么实现的,于是查找到这一篇,也可以去github上看源码。

https://github.com/DMQ/mvvm

GitHub - vuejs/vue: 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

我们要实现一个类MVVM简单版本的Vue框架,就需要实现一下几点:

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

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

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

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


通过解读代码,重新画图

1.怎么绑定观察者/怎么收集?

在编译模板时,如果遇到需要绑定观察者的时候,就实例化一个Watcher为watcher添加一个回调函数function(value, oldValue)一旦数据发生变化就执行回调函数,由回调函数更新视图

 2.怎么建立依赖/怎么添加订阅的?

在编译模板时,如果遇到需要绑定观察者的时候,就实例化一个Watcher(见下图),watcher的构造函数会让全局Dep.target指向当前watcher实例,并执行this.value = this.get();这个get就是上图中下面红框的get,这个get里面会触发Observer.js中的GET,在GET中添加订阅者

3.怎么更新视图的?

数据更新会触发set,set中执行dep.notify()来通知所有的sub(订阅者)执行sub.update(),Wathcer(sub)实例会执行run函数,run对比新旧值,如果改变则执行回调函数,由回调函数更新视图


 

面试题:

  1. 改变了一个Data中数据后,怎么更新到实际页面的DOM,这个过程是怎样的?怎么监听变化及更新视图的。

        数据修改触发SET函数,SET函数执行dep.notify()通知每一个订阅者执行更新函数。更新函数里实际上执行了run函数实现立即更新,run函数判断新旧值是否相等,不等则执行回调函数更新视图。

  1. 模板里面,对于一个字段的引用是怎么收集的?怎么绑定观察者的。

        在解析模板时,如果遇到需要引用data数据的字段,就实例化一个Watcher,Watcher的构造函数会执行Watcher原型上的get方法,这个get会通过读取数据的方式触发数据的GET方法,在GET方法中添加订阅者到dep订阅者收集器中。

  1. 对于这个字段的依赖是在什么时候建立的呢,是怎么建立的呢?即怎么添加订阅的。

上一问的蓝字部分回答了这个问题。Watcher的构造函数中会执行Watcher原型上的get方法,这个get会通过读取数据的方式触发数据的GET方法,在GET方法中添加订阅者到dep订阅者收集器中。


Observer.js:

// 定义一个观察者

class watcher {

  constructor(vm, expre, cb) {

    this.vm = vm

    this.expre = expre

    this.cb =cb

    // 把旧值保存起来

    this.oldVal = this.getOldVal()

  }

  // 获取旧值

  getOldVal() {

    // 将watcher放到targe值中

    Dep.target = this

    // 获取旧值

    const oldVal = compileUtil.getVal(this.expre, this.vm)

    // 将target值清空

    Dep.target = null

    return oldVal

  }

  // 更新函数

  update() {

    const newVal = compileUtil.getVal(this.expre, this.vm)

    if(newVal !== this.oldVal) {

      this.cb(newVal)

    }

  }

}

​

​

// 定义一个观察者集合

class Dep {

  constructor() {

    this.subs = []

  }

  // 收集观察者

  addSub(watcher) {

    this.subs.push(watcher)

  }

  //通知观察者去更新

  notify() {

    this.subs.forEach(w => w.update())

  }

}

​

​

​

// 定义一个Observer类通过gettr,setter实现数据的监听绑定

class Observer {

  constructor(data) {

    this.observer(data)

  }

  // 定义函数解析data,实现数据劫持

  observer (data) {

    if(data && typeof data === 'object') {

      // 是对象遍历对象写入getter,setter方法

      Reflect.ownKeys(data).forEach(key => {

        this.defineReactive(data, key, data[key]);

      })

    }

  }

  // 数据劫持方法

  defineReactive(obj,key, value) {

    // 递归遍历

    this.observer(data)

    // 实例化一个dep对象

    const dep = new Dep()

    // 通过ES5的API实现数据劫持

    Object.defineProperty(obj, key, {

      enumerable: true,

      configurable: false,

      get() {

        // 当读当前值的时候,会触发。

        // 订阅数据变化时,往Dep中添加观察者

        Dep.target && dep.addSub(Dep.target)

        return value

      },

      set: (newValue) => {

        // 对新数据进行劫持监听

        this.observer(newValue)

        if(newValue !== value) {

          value = newValue

        }

        // 告诉dep通知变化

        dep.notify()

      }

    })

  }

}

compile.js:

function Compile(el, vm) {
    this.$vm = vm;
    this.$el = this.isElementNode(el) ? el : document.querySelector(el);

    if (this.$el) {
        this.$fragment = this.node2Fragment(this.$el);
        this.init();
        this.$el.appendChild(this.$fragment);
    }
}

Compile.prototype = {
    constructor: Compile,
    node2Fragment: function(el) {
        var fragment = document.createDocumentFragment(),
            child;

        // 将原生节点拷贝到fragment
        while (child = el.firstChild) {
            fragment.appendChild(child);
        }

        return fragment;
    },

    init: function() {
        this.compileElement(this.$fragment);
    },

    compileElement: function(el) {
        var childNodes = el.childNodes,
            me = this;

        [].slice.call(childNodes).forEach(function(node) {
            var text = node.textContent;
            var reg = /\{\{(.*)\}\}/;

            if (me.isElementNode(node)) {
                me.compile(node);

            } else if (me.isTextNode(node) && reg.test(text)) {
                me.compileText(node, RegExp.$1.trim());
            }

            if (node.childNodes && node.childNodes.length) {
                me.compileElement(node);
            }
        });
    },

    compile: function(node) {
        var nodeAttrs = node.attributes,
            me = this;

        [].slice.call(nodeAttrs).forEach(function(attr) {
            var attrName = attr.name;
            if (me.isDirective(attrName)) {
                var exp = attr.value;
                var dir = attrName.substring(2);
                // 事件指令
                if (me.isEventDirective(dir)) {
                    compileUtil.eventHandler(node, me.$vm, exp, dir);
                    // 普通指令
                } else {
                    compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
                }

                node.removeAttribute(attrName);
            }
        });
    },

    compileText: function(node, exp) {
        compileUtil.text(node, this.$vm, exp);
    },

    isDirective: function(attr) {
        return attr.indexOf('v-') == 0;
    },

    isEventDirective: function(dir) {
        return dir.indexOf('on') === 0;
    },

    isElementNode: function(node) {
        return node.nodeType == 1;
    },

    isTextNode: function(node) {
        return node.nodeType == 3;
    }
};

// 指令处理集合
var compileUtil = {
    text: function(node, vm, exp) {
        this.bind(node, vm, exp, 'text');
    },

    html: function(node, vm, exp) {
        this.bind(node, vm, exp, 'html');
    },

    model: function(node, vm, exp) {
        this.bind(node, vm, exp, 'model');

        var me = this,
            val = this._getVMVal(vm, exp);
        node.addEventListener('input', function(e) {
            var newValue = e.target.value;
            if (val === newValue) {
                return;
            }

            me._setVMVal(vm, exp, newValue);
            val = newValue;
        });
    },

    class: function(node, vm, exp) {
        this.bind(node, vm, exp, 'class');
    },

    bind: function(node, vm, exp, dir) {
        var updaterFn = updater[dir + 'Updater'];

        updaterFn && updaterFn(node, this._getVMVal(vm, exp));

        new Watcher(vm, exp, function(value, oldValue) {
            updaterFn && updaterFn(node, value, oldValue);
        });
    },

    // 事件处理
    eventHandler: function(node, vm, exp, dir) {
        var eventType = dir.split(':')[1],
            fn = vm.$options.methods && vm.$options.methods[exp];

        if (eventType && fn) {
            node.addEventListener(eventType, fn.bind(vm), false);
        }
    },

    _getVMVal: function(vm, exp) {
        var val = vm;
        exp = exp.split('.');
        exp.forEach(function(k) {
            val = val[k];
        });
        return val;
    },

    _setVMVal: function(vm, exp, value) {
        var val = vm;
        exp = exp.split('.');
        exp.forEach(function(k, i) {
            // 非最后一个key,更新val的值
            if (i < exp.length - 1) {
                val = val[k];
            } else {
                val[k] = value;
            }
        });
    }
};


var updater = {
    textUpdater: function(node, value) {
        node.textContent = typeof value == 'undefined' ? '' : value;
    },

    htmlUpdater: function(node, value) {
        node.innerHTML = typeof value == 'undefined' ? '' : value;
    },

    classUpdater: function(node, value, oldValue) {
        var className = node.className;
        className = className.replace(oldValue, '').replace(/\s$/, '');

        var space = className && String(value) ? ' ' : '';

        node.className = className + space + value;
    },

    modelUpdater: function(node, value, oldValue) {
        node.value = typeof value == 'undefined' ? '' : value;
    }
};

watcher.js:

function Watcher(vm, expOrFn, cb) {
    this.cb = cb;
    this.vm = vm;
    this.expOrFn = expOrFn;
    this.depIds = {};

    if (typeof expOrFn === 'function') {
        this.getter = expOrFn;
    } else {
        this.getter = this.parseGetter(expOrFn.trim());
    }

    this.value = this.get();
}

Watcher.prototype = {
    constructor: Watcher,
    update: function() {
        this.run();
    },
    run: function() {
        var value = this.get();
        var oldVal = this.value;
        if (value !== oldVal) {
            this.value = value;
            this.cb.call(this.vm, value, oldVal);
        }
    },
    addDep: function(dep) {
        // 1. 每次调用run()的时候会触发相应属性的getter
        // getter里面会触发dep.depend(),继而触发这里的addDep
        // 2. 假如相应属性的dep.id已经在当前watcher的depIds里,说明不是一个新的属性,仅仅是改变了其值而已
        // 则不需要将当前watcher添加到该属性的dep里
        // 3. 假如相应属性是新的属性,则将当前watcher添加到新属性的dep里
        // 如通过 vm.child = {name: 'a'} 改变了 child.name 的值,child.name 就是个新属性
        // 则需要将当前watcher(child.name)加入到新的 child.name 的dep里
        // 因为此时 child.name 是个新值,之前的 setter、dep 都已经失效,如果不把 watcher 加入到新的 child.name 的dep中
        // 通过 child.name = xxx 赋值的时候,对应的 watcher 就收不到通知,等于失效了
        // 4. 每个子属性的watcher在添加到子属性的dep的同时,也会添加到父属性的dep
        // 监听子属性的同时监听父属性的变更,这样,父属性改变时,子属性的watcher也能收到通知进行update
        // 这一步是在 this.get() --> this.getVMVal() 里面完成,forEach时会从父级开始取值,间接调用了它的getter
        // 触发了addDep(), 在整个forEach过程,当前wacher都会加入到每个父级过程属性的dep
        // 例如:当前watcher的是'child.child.name', 那么child, child.child, child.child.name这三个属性的dep都会加入当前watcher
        if (!this.depIds.hasOwnProperty(dep.id)) {
            dep.addSub(this);
            this.depIds[dep.id] = dep;
        }
    },
    get: function() {
        Dep.target = this;
        var value = this.getter.call(this.vm, this.vm);
        Dep.target = null;
        return value;
    },

    parseGetter: function(exp) {
        if (/[^\w.$]/.test(exp)) return; 

        var exps = exp.split('.');

        return function(obj) {
            for (var i = 0, len = exps.length; i < len; i++) {
                if (!obj) return;
                obj = obj[exps[i]];
            }
            return obj;
        }
    }
};

MVVM.js

function MVVM(options) {
    this.$options = options;
    var data = this._data = this.$options.data, me = this;
    // 属性代理,实现 vm.xxx -> vm._data.xxx
    Object.keys(data).forEach(function(key) {
        me._proxy(key);
    });
    observe(data, this);
    this.$compile = new Compile(options.el || document.body, this)
}

MVVM.prototype = {
	_proxy: function(key) {
		var me = this;
        Object.defineProperty(me, key, {
            configurable: false,
            enumerable: true,
            get: function proxyGetter() {
                return me._data[key];
            },
            set: function proxySetter(newVal) {
                me._data[key] = newVal;
            }
        });
	}
};
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的MVVM Demo示例,主要实现了数据绑定和视图模型。 首先,我们需要创建一个模型类和视图模型类。 模型类: ``` class User { String name; int age; User(this.name, this.age); } ``` 视图模型类: ``` import 'package:flutter/material.dart'; import 'user.dart'; class UserViewModel extends ChangeNotifier { User _user; UserViewModel(User user) { _user = user; } String get name => _user.name; set name(String value) { _user.name = value; notifyListeners(); } int get age => _user.age; set age(int value) { _user.age = value; notifyListeners(); } } ``` 在视图上,我们可以将数据绑定到`UserViewModel`, ``` import 'package:flutter/material.dart'; import 'package:mvvm_demo/user.dart'; import 'package:mvvm_demo/user_view_model.dart'; import 'package:provider/provider.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'MVVM Demo', home: ChangeNotifierProvider<UserViewModel>( create: (context) => UserViewModel(User('User Name', 20)), child: HomePage(), ), ); } } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { final userViewModel = Provider.of<UserViewModel>(context); return Scaffold( appBar: AppBar( title: Text('MVVM Demo Home'), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( margin: EdgeInsets.all(16.0), child: TextField( decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Name', ), onChanged: (value) { userViewModel.name = value; }, ), ), Container( margin: EdgeInsets.all(16.0), child: TextField( decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Age', ), onChanged: (value) { userViewModel.age = int.parse(value); }, ), ), Container( margin: EdgeInsets.all(16.0), child: Text( 'User Name: ${userViewModel.name} \nUser Age: ${userViewModel.age}'), ), ], ), ); } } ``` 在这个示例中,我们使用`provider`库来管理视图模型的状态,当视图模型中的数据发生变化时,我们可以通过`notifyListeners()`方法通知视图重新渲染。 希望这个简单的MVVM Demo示例对你有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值