实现一个简易的MVVM

这个实现会包含以下核心部分:

  1. Observer: 用于监听对象属性变化
  2. Dep: 依赖收集器
  3. Watcher: 订阅者,用于更新视图
  4. Compile: 编译模板,解析指令
  5. MVVM: 主要入口类

下面是一个基本的实现:

// Observer: 监听对象属性变化
class Observer {
  constructor(data) {
    this.observe(data);
  }

  observe(data) {
    if (!data || typeof data !== 'object') {
      return;
    }
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key]);
      this.observe(data[key]); // 递归观察子属性
    });
  }

  defineReactive(obj, key, val) {
    const dep = new Dep();
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        Dep.target && dep.addDep(Dep.target);
        return val;
      },
      set(newVal) {
        if (newVal === val) return;
        val = newVal;
        dep.notify(); // 通知所有订阅者
      }
    });
  }
}

// Dep: 依赖收集器
class Dep {
  constructor() {
    this.subs = [];
  }

  addDep(sub) {
    this.subs.push(sub);
  }

  notify() {
    this.subs.forEach(sub => sub.update());
  }
}

// Watcher: 订阅者
class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm;
    this.key = key;
    this.cb = cb;
    this.value = this.get();
  }

  get() {
    Dep.target = this;
    const value = this.vm.$data[this.key];
    Dep.target = null;
    return value;
  }

  update() {
    const value = this.vm.$data[this.key];
    if (value !== this.value) {
      this.value = value;
      this.cb.call(this.vm, value);
    }
  }
}

// Compile: 编译模板
class Compile {
  constructor(el, vm) {
    this.vm = vm;
    this.el = document.querySelector(el);
    this.fragment = null;
    this.init();
  }

  init() {
    if (this.el) {
      this.fragment = this.nodeToFragment(this.el);
      this.compileElement(this.fragment);
      this.el.appendChild(this.fragment);
    }
  }

  nodeToFragment(el) {
    const fragment = document.createDocumentFragment();
    let child = el.firstChild;
    while (child) {
      fragment.appendChild(child);
      child = el.firstChild;
    }
    return fragment;
  }

  compileElement(el) {
    const childNodes = el.childNodes;
    Array.from(childNodes).forEach(node => {
      if (node.nodeType === 1) {
        // 元素节点
        this.compile(node);
      } else if (node.nodeType === 3) {
        // 文本节点
        const text = node.textContent;
        const reg = /\{\{(.*)\}\}/;
        if (reg.test(text)) {
          const key = RegExp.$1.trim();
          new Watcher(this.vm, key, function(value) {
            node.textContent = text.replace(reg, value);
          });
        }
      }
    });
  }

  compile(node) {
    const nodeAttrs = node.attributes;
    Array.from(nodeAttrs).forEach(attr => {
      const attrName = attr.name;
      if (attrName.startsWith('v-')) {
        const exp = attr.value;
        const dir = attrName.substring(2);
        if (dir === 'model') {
          new Watcher(this.vm, exp, function(value) {
            node.value = value;
          });
          node.addEventListener('input', e => {
            this.vm.$data[exp] = e.target.value;
          });
        }
      }
    });
  }
}

// MVVM
class MVVM {
  constructor(options) {
    this.$el = options.el;
    this.$data = options.data;
    new Observer(this.$data);
    new Compile(this.$el, this);
    this.proxy(this.$data);
  }

  proxy(data) {
    Object.keys(data).forEach(key => {
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          return this.$data[key];
        },
        set(newVal) {
          this.$data[key] = newVal;
        }
      });
    });
  }
}

// 使用示例
const vm = new MVVM({
  el: '#app',
  data: {
    message: 'Hello MVVM!'
  }
});

这个简易的MVVM实现包含了基本的数据绑定和视图更新功能。你可以这样使用它:

<div id="app">
  <input v-model="message">
  <p>{{message}}</p>
</div>

<script>
  const vm = new MVVM({
    el: '#app',
    data: {
      message: 'Hello MVVM!'
    }
  });
</script>

关注微信公众号温暖前端,不定期分享前端知识点和前端资料↓↓↓

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是一个简单的WPF MVVM登录DEMO的代码示例: XAML代码: ``` <Window x:Class="LoginDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Login Demo" Height="250" Width="400"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Grid.Row="0" Grid.Column="0" Content="Username:" Margin="10"/> <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Username}" Margin="10"/> <Label Grid.Row="1" Grid.Column="0" Content="Password:" Margin="10"/> <PasswordBox Grid.Row="1" Grid.Column="1" Password="{Binding Password}" Margin="10"/> <Button Grid.Row="2" Grid.Column="1" Content="Login" Command="{Binding LoginCommand}" Margin="10"/> <TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding ErrorMessage}" Foreground="Red" Margin="10"/> <CheckBox Grid.Row="4" Grid.Column="1" Content="Remember me" IsChecked="{Binding RememberMe}" Margin="10"/> </Grid> </Window> ``` ViewModel代码: ``` using System; using System.ComponentModel; using System.Windows; using System.Windows.Input; namespace LoginDemo { public class LoginViewModel : INotifyPropertyChanged { private string _username; private string _password; private bool _rememberMe; private string _errorMessage; public string Username { get { return _username; } set { _username = value; OnPropertyChanged("Username"); } } public string Password { get { return _password; } set { _password = value; OnPropertyChanged("Password"); } } public bool RememberMe { get { return _rememberMe; } set { _rememberMe = value; OnPropertyChanged("RememberMe"); } } public string ErrorMessage { get { return _errorMessage; } set { _errorMessage = value; OnPropertyChanged("ErrorMessage"); } } public ICommand LoginCommand { get; set; } public LoginViewModel() { LoginCommand = new RelayCommand(Login); } private void Login(object parameter) { if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password)) { ErrorMessage = "Please enter both username and password."; } else if (Username == "admin" && Password == "password") { ErrorMessage = ""; MessageBox.Show("Login successful!"); } else { ErrorMessage = "Invalid username or password."; } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } ``` 其中,RelayCommand是一个自定义的ICommand实现,用于绑定按钮的Command属性,代码如下: ``` using System; using System.Windows.Input; namespace LoginDemo { public class RelayCommand : ICommand { private readonly Action<object> _execute; private readonly Predicate<object> _canExecute; public RelayCommand(Action<object> execute, Predicate<object> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) { return _canExecute?.Invoke(parameter) ?? true; } public void Execute(object parameter) { _execute(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } } } ``` 在MainWindow的构造函数中,将ViewModel与View绑定: ``` public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new LoginViewModel(); } } ``` 这样,就完成了一个简单的WPF MVVM登录DEMO。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

温暖前端

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

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

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

打赏作者

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

抵扣说明:

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

余额充值