一、什么是MVVM
MVVM就是model-view-viewModel的简写。它本质上就是MVC的改进版,MVVM就是将其中的view的状态和行为抽象化,视图UI和业务逻辑分开;让我们只要关注逻辑的实现。
- M -> Model 数据保存和数据处理
- V -> View 视图 HTML
- VM -> viweModel 驱动 视图改变驱动数据改变 数据改变驱动视图改变
实现方法
在实现MVVM库前,我们需要先理清下整体思路,先看下以下流程图:
- 实现compile,对模板进行编译,编译元素、方法、事件绑定等
- 实现observe,对数据进行监听,并且数据变化时执行update函数
1、新建html文件以及入口文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./app.js"></script>
</body>
</html>
新建入口文件app.js
/**
* MVVM
* VM 驱动VM ViewModel
* V View视图
* M Model 数据保存和数据处理
* 视图改变通过VM驱动数据改变
* 数据改成通过VM驱动视图改变
*/
import { createApp, useReactive } from './MVVM'
function App() {
const state = useReactive({
count: 0,
name: '张三'
})
const add = (num) => {
console.log(num, 'num')
state.count += num
console.log(state.count)
}
const minus = (num) => {
state.count -= num
console.log(state)
}
const changeName = (num) => {
state.name = num
console.log(state)
}
return {
template: `
<h1>{{ count }}</h1>
<h2>{{ name }}</h2>
<button @click="add(1)">+</button>
<button @click="minus(1)">-</button>
<button @click="changeName('李四')">changeName</button>
`,
state,
methods: {
add,
minus,
changeName
}
}
}
createApp(
App(),
document.querySelector('#app')
)
2、实现render函数以及Observe(reactive)
MVVM -> index.js
export { createApp } from './render'
export { useReactive } from './reactive'
export { eventFormat } from './compile/event'
export { stateFormat } from './compile/state'
MVVM -> render.js
import { bindEvent } from './compile/event'
import { eventFormat, stateFormat } from './index'
export function createApp(
{
template,
state,
methods
},
rootDom
) {
rootDom.innerHTML = render(template, state)
// 绑定事件
bindEvent(methods)
}
function render(template, state) {
template = eventFormat(template)
template = stateFormat(template, state)
return template
}
export function update(statePool, key, value) {
const allElements = document.querySelectorAll('*')
let oItem = null,
_mark = null;
statePool.forEach(item => {
// 当state的最后一个key 和 更新的key相等时
if(item.state[item.state.length - 1] === key) {
for(let i = 0; i < allElements.length; i++) {
oItem = allElements[i]
_mark = parseInt(oItem.dataset.mark)
// 当mark相等时
if(item.mark === _mark) {
// 直接更改元素内容
oItem.innerHTML = value
}
}
}
})
}
reactive -> index.js
import { isObject } from "../shared/utils";
import { mutableHandler } from "./mutableHandler";
export function useReactive(target) {
return createReactObject(target, mutableHandler)
}
function createReactObject(target, baseHandler) {
if(!isObject(target)) {
return target
}
const observe = new Proxy(target, baseHandler)
return observe
}
reactive -> mutableHandler.js
import { useReactive } from ".";
import { statePool } from "../compile/state";
import { update } from "../render";
import { hasOwnProperty, isObject } from "../shared/utils";
const get = createGetter(),
set = createSetter();
function createGetter() {
return function(target, key, receiver) {
const res = target[key]
console.log('获取:', key, res)
if(isObject(res)) {
return useReactive(res)
}
return res
}
}
function createSetter() {
return function(target, key, value, receiver) {
const isKeyExist = hasOwnProperty(target, key),
oldValue = target[key];
if(!isKeyExist) { // 不存在的属性 新增
target[key] = value
} else if(oldValue !== value) { // 存在 并且新value不等于旧value
target[key] = value
update(statePool, key, value)
// update
}
return target
}
}
const mutableHandler = {
get,
set
}
export {
mutableHandler
}
3、实现compile
compile -> event.js
import { reg_fnArg, reg_fnName, reg_onClick, reg_type } from "../shared/regExp"
import { randomNum, checkType } from "../shared/utils"
let eventPool = []
/**
*
* eventPool
* {
* mark: 唯一标识,
* handler: 方法,
* type: 'click'
* }
*/
export function eventFormat(template) {
return template.replace(reg_onClick, function(node, key) {
const _mark = randomNum() // 唯一标识
const _type = template.match(reg_type)[1] // 事件类型
eventPool.push({
mark: _mark,
handler: key.trim(),
type: _type
})
// 删除dom上的事件
return `data-mark="${_mark}"`
})
}
export function bindEvent(methods) {
const allElements= document.querySelectorAll('*')
let _mark = 0,
oItem = null;
eventPool.forEach(item => {
for(let i = 0; i < allElements.length; i++) {
oItem = allElements[i]
_mark = parseInt(oItem.dataset.mark) // 找到绑定在dom上的data-mark
// 相等的情况下就给其绑定事件函数
if(item.mark === _mark) {
oItem.addEventListener(item.type, function() {
const fnName = item.handler.match(reg_fnName)[1] // 找到方法名
const fnArg = checkType(item.handler.match(reg_fnArg)[1]) // 找到参数 并且处理参数类型
methods[fnName](fnArg) // 执行方法
}, false)
}
}
})
}
compile -> state.js
import { reg_html, reg_tag, reg_var } from "../shared/regExp"
import { randomNum } from "../shared/utils"
export let statePool = []
let o = 0
/**
* statePool
* {
* mark: _mark 唯一标识
* state: value
* }
*/
export function stateFormat(template, state) {
let _state = {}
template = template.replace(reg_html, function(node, key) {
const _mark = randomNum(),
_tag = node.match(reg_tag)[1];
_state.mark = _mark
statePool.push(_state)
_state = {}
return `<${_tag} data-mark="${_mark}">{{${key}}}</${_tag}>`
})
template = template.replace(reg_var, function(node, key) {
let _var = key.trim(),
i = 0;
// 可能会是state.obj.name 组成数组
const _varArr = _var.split('.')
// 循环 找到最后一个的值
while(i < _varArr.length) {
_var = state[_varArr[i]]
i++
}
_state.state = _varArr
statePool[o].state = _varArr
o++
return _var
})
return template
}
4、一些工具函数和正则
shared -> utils.js
import { reg_check_str, reg_str } from "./regExp"
function isObject(target) {
return typeof target === 'object' && target !== null
}
function hasOwnProperty(target, key) {
return Object.prototype.hasOwnProperty.call(target, key)
}
function randomNum() {
return new Date().getTime() + parseInt(Math.random() * 10000)
}
function checkType(str) {
// 参数带有'' "" 统一处理成 ''
if(reg_check_str.test(str)) {
return str.replace(reg_str, '')
}
// 参数是字符串的true 直接返回true或者false
switch(str) {
case 'true':
return true
case 'false':
return false
default:
break
}
// 其余直接处理成数字类型
return Number(str)
}
export {
isObject,
hasOwnProperty,
randomNum,
checkType
}
shared -> regExp.js
const reg_onClick = /@click\=\"(.+?)\"/g // 匹配dom元素上绑定的事件
const reg_type = /@(.*?)\=/ // 匹配绑定的事件类型 click等
const reg_fnName = /^(.*?)\(/ // 匹配绑定的事件方法名
const reg_fnArg = /\((.*?)\)/ // 匹配绑定的事件的参数
const reg_check_str = /^[\'|\"].*?[\'|\"]$/ // 匹配绑定的事件的参数的参数
const reg_str = /[\'|\"]/g // 匹配参数字符串
const reg_html= /\<.*?\>\{\{(.*?)\}\}\<\/.*?>/g // 匹配html元素 包含标签和内容 完全匹配出来
const reg_tag = /\<(.*?)\>/ // 匹配标签
const reg_var = /\{\{(.*?)\}\}/g // 匹配{{}}里面的值
export {
reg_onClick,
reg_type,
reg_fnName,
reg_fnArg,
reg_check_str,
reg_str,
reg_html,
reg_tag,
reg_var
}