搭建MVC实现:
MVC 模式
MVC模式 是将程序分为三层:
M层(Model): 负责数据层
V层(View): 负责视图层
C层 (contraller): 控制器 负责交互
在MVC 模式中:
C层是 核心层: 既可以更新数据,又可以更新视图
V层: 可以获取数据
M层: 存储数据
MVC的主要思想 就在图中, 控制器 要获得 更新视图 更新数据。
返回一个对象,该对象中可以操作MVC,但是不可以直接访问。 是为了保护数据。
推演:
不使用MVC的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="div"></div>
<script>
let data = {
data: "我是一组数据"
}
// 获得元素
var div = document.getElementById('div');
// console.log(div);
div.innerHTML = data.data;
// 交互
div.onclick = function() {
this.style.color = 'red';
}
</script>
</body>
</html>
使用MVC , 先分析:
MVC中的M 数据层 : 是data
MVC中的V 视图层 : 是div元素,以及div的内容
MVC中的 C 控制层 : 作为交互 就是 onclick
MVC的架子, 为什么要暴露接口呢? 为了保护数据安全,不让直接操作 M V C 方法
为什么都是自执行函数(IIFE) 还是为了保护数据安全。
let MVC = (function() {
// M层 负责 数据层
let M = ()();
// V层 负责视图层
let V = ()();
// C层 负责交互层
let C = ()();
// 暴露接口
return {
Method(){}
}
})()
然后开始补充内容: 思路 数据层 应该有 获得数据方法 , 存储数据的方法
M层 就会是
// M层 负责 数据层
let M = (function(){
// 定义数据对象 用来存放 数据
let data = {};
return {
// 添加数据的方法
add() {},
// 获得数据的方法
get() {}
}
})();
然后思考 视图层将会有什么呢?
视图层 要有一个对象, 用来存放 视图的方法
同样为了 保护 视图的方法。 我们返回接口 。这个接口有两个方法
(为什么会这样分开呢? 观察者模式)
观察者模式博客::::
添加视图的方法
执行视图的方法
// V层 负责视图层
let V = (function(){
// 定义一个数据对象 用来存放 视图的方法
let data = {};
return {
// 咱们把 添加方法 和 执行 分开做。 添加只管添加的方法。 渲染进行执行函数的操作
// 添加视图的方法
add(){},
// 渲染视图的方法
render(){},
}
})();
思考 C层 控制层 这层主要的作用是 复制交互
要一个对象 用来存放 交互的方法
同样 为了保护 交互的方法 我们使用 return 接口
接口中 要有 添加交互的方法 和执行交互的方法
// C层 负责交互层 这层主要有很多 交互的方法
let C = (function(){
// 用来存放 交互的方法
let data = {};
return {
// 添加交互的方法
add(){},
// 初始化函数。 作用就是 交互的方法的实现
init(){}
}
})();
那么MVC返回的接口 , 也可以填充了
思考: 将要暴露什么接口: 对外界 肯定是要暴露 添加数据 添加视图 添加控制器的方法
let MVC() {
return {
// 添加数据的方法
addModel(name, data) {
M.add(name, data);
},
// 添加视图
addView(name, fn) {
V.add(name, fn);
},
// 添加控制器
addCtrl(name, fn) {
C.add(name, fn);
},
}
}()
此时大概的框架就有了
let MVC = (function() {
// M层 负责 数据层
let M = (function(){
// 定义数据对象 用来存放 数据
let data = {};
// 这样返回的方法 是为了 保护 data 数据
return {
// 添加数据的方法
add() {},
// 获得数据的方法
get() {}
}
})();
// V层 负责视图层
let V = (function(){
// 定义一个数据对象 用来存放 视图的方法
let data = {};
return {
// 咱们把 添加方法 和 执行 分开做。 添加只管添加的方法。 渲染进行执行函数的操作
// 添加视图的方法
add(){},
// 渲染视图的方法
render(){},
}
})();
// C层 负责交互层 这层主要有很多 交互的方法
let C = (function(){
let data = {};
return {
// 添加交互的方法
add(){},
// 初始化函数。 作用就是 交互的方法的实现
init(){}
}
})();
// 暴露接口
return {
// 添加数据的方法
addModel(name, data) {
M.add(name, data);
},
// 添加视图
addView(name, fn) {
V.add(name, fn);
},
// 添加控制器
addCtrl(name, fn) {
C.add(name, fn);
},
}
})()
此时就要思考了:
C层的交互 是要更新对象, 并且 C层控制 更新视图
V层的渲染 是要使用到数据的
现在变成现象解释现象了
因此可以得到。 视图方法的执行 要放在 C层
添加视图的方法, 放在 V层
添加数据的方法, 放在M层
执行的方法都放在 C层。
继续填充
M层
// M层 复杂数据层
let M = (function() {
// 定义数据对象
let data = {};
// 定义接口对象 返回了两个接口 add 接口 和 get 接口
return {
// 添加数据
add(name, value) {
// 第一步将路径层级转为数组
let pathName = name.split('.');
// console.log(pathName);
// 备份数据
let result = data;
// 循环添加数据
for (let i = 0; i < pathName.length - 1; i++) {
// 判断数据类型
if (typeof result[pathName[i]] === 'object' && result[pathName[i]] != null) {
// 说明真的是引用类型, 指向下一层
result = result[pathName[i]];
} else if (typeof result[pathName[i]] === 'undefined') {
// 强制变为对象
result[pathName[i]] = {};
// 改变对象的指向
result = result[pathName[i]];
} else {
throw new Error('不能在值类型上添加数据');
}
}
// console.log(pathName[pathName.length - 1]);
// 最后一项被赋值
if (typeof result[pathName[pathName.length - 1]] === 'undefined') {
result[pathName[pathName.length - 1]] = value;
} else {
throw new Error('已经被占用了');
}
// console.log(42, data);
},
// 获取数据
get(name) {
// 第一步将路径层级转为数组
let pathName = name.split('.');
// 备份数据
let result = data;
// 循环
for (let i = 0; i < pathName.length - 1; i++) {
// 判断当前层级是否是引用类型
if (typeof result[pathName[i]] === 'object' && result[pathName[i]] != null) {
// 指向下一层
result = result[pathName[i]];
} else {
return null;
}
}
// 遍历完成之后返回最后一项数据
return result[pathName[pathName.length - 1]];
}
}
})()
V层
// V层 负责视图层
let V = (function() {
// 定义数据对象
let data = {
// view: function() {
// }
};
// 定义接口对象
return {
// 添加视图的方法
add(name, fn) {
data[name] = fn;
},
// 渲染方法
render(name) {
return data[name](M);
}
}
})()
C层:
// C层 添加控制器
let C = (function() {
// 定义数据对象
let data = {
// ajax: function() {}
};
// 返回接口对象
return {
// 添加控制器的方法
add(name, fn) {
data[name] = fn;
},
// 初始化方法 控制器的执行
init() {
for (let i in data) {
data[i](M, V);
}
}
}
})()
整个MVC.js 的实现
// 实现MVC
let MVC = (function() {
// M层 负责数据层
let M = (function() {
// 定义数据对象
let data = {
};
// 定义接口对象
return {
// 添加数据
add(name, value) {
// 第一步将路径层级转为数组
let pathName = name.split('.');
// console.log(pathName);
// 备份数据
let result = data;
// 循环添加数据
for (let i = 0; i < pathName.length - 1; i++) {
// 判断数据类型
if (typeof result[pathName[i]] === 'object' && result[pathName[i]] != null) {
// 说明真的是引用类型, 指向下一层
result = result[pathName[i]];
} else if (typeof result[pathName[i]] === 'undefined') {
// 强制变为对象
result[pathName[i]] = {};
// 改变对象的指向
result = result[pathName[i]];
} else {
throw new Error('不能在值类型上添加数据');
}
}
// console.log(pathName[pathName.length - 1]);
// 最后一项被赋值
if (typeof result[pathName[pathName.length - 1]] === 'undefined') {
result[pathName[pathName.length - 1]] = value;
} else {
throw new Error('已经被占用了');
}
// console.log(42, data);
},
// 获取数据
get(name) {
// 第一步将路径层级转为数组
let pathName = name.split('.');
// 备份数据
let result = data;
// 循环
for (let i = 0; i < pathName.length - 1; i++) {
// 判断当前层级是否是引用类型
if (typeof result[pathName[i]] === 'object' && result[pathName[i]] != null) {
// 指向下一层
result = result[pathName[i]];
} else {
return null;
}
}
// 遍历完成之后返回最后一项数据
return result[pathName[pathName.length - 1]];
}
}
})()
// V层 负责视图层
let V = (function() {
// 定义数据对象
let data = {
// view: function() {
// }
};
// 定义接口对象
return {
// 添加视图的方法
add(name, fn) {
data[name] = fn;
},
// 渲染方法
render(name) {
return data[name](M);
}
}
})()
// C层 添加控制器
let C = (function() {
// 定义数据对象
let data = {
// ajax: function() {}
};
// 返回接口对象
return {
// 添加控制器的方法
add(name, fn) {
data[name] = fn;
},
// 初始化方法 控制器的执行
init() {
for (let i in data) {
data[i](M, V);
}
}
}
})()
// 返回一个对象,该对象中可以操作M V C ,但是不可以直接访问
return {
// 添加数据
// 之前定义方法的方式
// addModel: function() {
// 使用ES6语法 在对象字面量中 定义的方法可以省略冒号以及function关键字
addModel(name, data) {
M.add(name, data);
},
// 添加视图
addView(name, fn) {
V.add(name, fn);
},
// 添加控制器
addCtrl(name, fn) {
C.add(name, fn);
},
// 初始化方法
install() {
C.init();
}
}
})()
使用MVC之后的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="div"></div>
<!-- 引入MVC 文件 -->
<script src="MVC.js"></script>
<script>
console.log(MVC);
// 添加数据
MVC.addModel('model',{
data: "我是一组数据"
});
// 添加视图
MVC.addView('view', function(M) {
// 获得数据
let data = M.get('model');
// 获得元素
let dom = document.getElementById('div');
console.log(data);
dom.innerHTML = data.data;
// 返回元素
return dom;
});
// 添加控制器
MVC.addCtrl('ctrl', function(M, V) {
// 获得元素
let dom = V.render('view');
// 添加事件
dom.onclick = function(){
this.style.color = 'red';
}
});
// 执行控制器代码
MVC.install();
</script>
</body>
</html>
MVC在项目中的应用:
视图层: 是需要返回容器的。 为什么要返回容器呢。 就是返回整个元素? 因为元素要在控制层被使用。元素有有交互。要有事件的发生。
七个步骤
1、 获得容器元素
2、 获取数据
3、 定义大模板
4、 定义小模板
5、 定义变量 用来存储格式化之后的模板
6、 格式化模板
7、 上树
8、 返回容器元素
// 返回元素
return dom;
});
// 添加控制器
MVC.addCtrl('ctrl', function(M, V) {
// 获得元素
let dom = V.render('view');
// 添加事件
dom.onclick = function(){
this.style.color = 'red';
}
});
// 执行控制器代码
MVC.install();
</script>
````
MVC在项目中的应用:
视图层: 是需要返回容器的。 为什么要返回容器呢。 就是返回整个元素? 因为元素要在控制层被使用。元素有有交互。要有事件的发生。
七个步骤
1、 获得容器元素
2、 获取数据
3、 定义大模板
4、 定义小模板
5、 定义变量 用来存储格式化之后的模板
6、 格式化模板
7、 上树
8、 返回容器元素