实现一个简单的MVVM框架

2021-08-31 21:42:23 Vue 大约 3 分钟

在实现mvvm之前我们需要先来认识下什么是mvvm?

image-20210830234126983

# 什么是MVVM ?

MVVM(Model–view–viewmodel)是一种软件架构模式 (opens new window);MVVM有助于将图形用户界面 (opens new window)的开发与业务逻辑 (opens new window)后端 (opens new window)逻辑(数据模型)的开发分离 (opens new window)开来,这是通过置标语言 (opens new window)或GUI代码实现的。

  • MVC
    • Model 数据 → View 视图 → Controller 控制器;
  • MVP:
    • Model模型→ View 视图 → Presenter表现层;
    • 由MVC模式进化而来;
  • MVVM:
    • Model 模型 → View 视图 → ViewModel 视图模型;
    • 三者的关系:view 可以通过事件绑定的方式影响 modelmodel 可以通过数据绑定的形式影响到viewviewModel是把 modelview 连起来的连接器;

# MVVM模式的组成部分

模型:是指代表真实状态内容的领域模型 (opens new window)(面向对象),或指代表内容的数据访问层 (opens new window)(以数据为中心)。

视图:就像在MVC (opens new window)MVP (opens new window)模式中一样,视图是用户在屏幕上看到的结构、布局和外观(UI)。

视图模型:视图模型是暴露公共属性和命令的视图的抽象。

提示

MVVM没有MVC模式的控制器,也没有MVP模式的presenter,有的是一个绑定器。在视图模型中,绑定器在视图和数据绑定器 (opens new window)之间进行通信。

# MVVM 框架的三大要素

  • 数据响应式:监听数据变化并在视图中更新。
    • Object.defineProperty()
    • Proxy
  • 模板引擎:提供描述视图的模版语法
    • 插值:{{}}
    • 指令:v-bind,v-on,v-model,v-for,v-if
  • 渲染:如何将模板转换为html
    • 模板 => vdom => dom

# 编写MVVM框架

# 数据响应式

const obj = {}

// 封装响应式函数
function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
        get() {
            console.log(`get ${key}:${val}`); // get name:小明
            return val
        },
        set(newVal) {
            if (newVal !== val) {
                console.log(`set ${key}:${newVal}`); // set name:小红
                val = newVal
            }
        },
    })
}

// 把 obj 转化为 响应式;
defineReactive(obj, 'name', '小明');

obj.name

obj.name = '小红'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 结合视图

<!DOCTYPE html>
<html lang="en">
<body>
	<h1 id="#app"></h1>
</body>

<script>
    const obj = {}
    const appEl = document.getElementById('#app')
    function render(val) {
        appEl.innerText = val;
    }
    // 封装响应式函数
    function defineReactive(obj, key, val) {
        Object.defineProperty(obj, key, {
            get() {
                return val
            },
            set(newVal) {
                if (newVal !== val) {
                    // 数据变化,更新视图
                    render(newVal)
                    val = newVal
                }
            },
        })
    }

    // 把 obj 转化为 响应式;
    defineReactive(obj, 'date', '');

    // 每秒更新数据
    setInterval(() => {
        obj.date = new Date().toLocaleTimeString();
    }, 1000)

</script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

以上代码可以在浏览器中直接运行,可以看到实现响应式的大致原理;

# 为所有属性添加响应式

const obj = {
    eat: {
        panda: '熊猫'
    },
    drink: '喝'
}

// 封装响应式函数
function defineReactive(obj, key, val) {
+    observe(val);
    Object.defineProperty(obj, key, {
        get() {
            console.log(`get ${key}:${val}`);
            return val
        },
        set(newVal) {
            if (newVal !== val) {
                console.log(`set ${key}:${newVal}`);
                val = newVal
            }
        },
    })
}

// 遍历观察者为所有对象添加响应式;
function observe (obj) {
    if (!(obj instanceof Object) || obj === null || obj === undefined) return;
    for (const key in obj) {
        defineReactive(obj, key, obj[key]);
    }
}

observe(obj);

obj.eat.panda

obj.drink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

新增加方法 observe 遍历对象中的所有属性并转化为响应式;

# 解决新添加属性

function set(obj, key, val) {
    defineReactive(obj, key, val);
}

// 设置新属性
set(obj, 'beverages', '饮料');
1
2
3
4
5
6

defineProperty无法观察到添加或删除属性的变化,所以我们需要自己触发响应式;

提示

defineProperty() 不支持数组,所以数组的监听需要其他处理方式;

上次编辑于: 2023年7月4日 09:36