共计 5007 个字符,预计需要花费 13 分钟才能阅读完成。
MVC
那时计算机世界天地混沌,浑然一体,然后出现了一个创世者,将现实世界抽象出模型形成 model,将人机交互从应用逻辑中分离形成 view,然后就有了空气、水、鸡啊、蛋什么的。
——《前端 MVC 变形记》
MVC 模式代表 Model-View-Controller(模型 - 视图 - 控制器)模式。这种模式用于应用程序的分层开发。
Model(模型)
Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中 存取数据
。
Model 定义了这个模块的数据模型。在代码中体现为数据管理者,Model 负责对数据进行获取及存放。
数据不可能凭空生成的,要么是从服务器上面获取到的数据,要么是本地数据库中的数据,也有可能是用户在 UI 上填写的表单即将上传到服务器上面存放,所以需要有数据来源。既然 Model 是数据管理者,则自然由它来负责获取数据。
MVC 允许在不改变视图的情况下改变视图对用户输入的响应方式,用户对 View 的操作交给了 Controller 处理,在 Controller 中响应 View 的事件调用 Model 的接口对数据进行操作,一旦 Model 发生变化便通知相关视图进行更新。
这里我们把需要用到的数值变量封装在 Model 中,并定义了 add、sub、getVal 三种操作数值方法。
var myapp = {}; // 创建这个应用对象
myapp.Model = function() {
var val = 0;
this.add = function(v) {if (val < 100) val += v;
};
this.sub = function(v) {if (val > 0) val -= v;
};
this.getVal = function() {return val;};
/* 观察者模式 */
var self = this,
views = [];
this.register = function(view) {views.push(view);
};
this.notify = function() {for(var i = 0; i < views.length; i++) {views[i].render(self);
}
};
};
Model 和 View 之间使用了观察者模式,View 事先在此 Model 上注册,进而观察 Model,以便更新在 Model 上发生改变的数据。
View(视图)
View(视图)是应用程序中处理 数据显示
的部分。通常视图是依据模型数据创建的。
View,视图,简单来说,就是我们在界面上看见的一切。
view 和 controller 之间使用了策略模式,这里 View 引入了 Controller 的实例来实现特定的响应策略,比如这个栗子中按钮的 click 事件:
myapp.View = function(controller) {var $num = $('#num'),
$incBtn = $('#increase'),
$decBtn = $('#decrease');
this.render = function(model) {$num.text(model.getVal() + 'rmb');
};
/* 绑定事件 */
$incBtn.click(controller.increase);
$decBtn.click(controller.decrease);
};
如果要实现不同的响应的策略只要用不同的 Controller 实例替换即可。
Controller(控制器)
Controller(控制器)是应用程序中 处理用户交互
的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
Controller 是 MVC 中的数据和视图的协调者,也就是在 Controller 里面把 Model 的数据赋值给 View 来显示(或者是 View 接收用户输入的数据然后由 Controller 把这些数据传给 Model 来保存到本地或者上传到服务器)。
myapp.Controller = function() {
var model = null,
view = null;
this.init = function() {
/* 初始化 Model 和 View */
model = new myapp.Model();
view = new myapp.View(this);
/* View 向 Model 注册,当 Model 更新就会去通知 View 啦 */
model.register(view);
model.notify();};
/* 让 Model 更新数值并通知 View 更新视图 */
this.increase = function() {model.add(1);
model.notify();};
this.decrease = function() {model.sub(1);
model.notify();};
};
这里我们实例化 View 并向对应的 Model 实例注册,当 Model 发生变化时就去通知 View 做更新,这里用到了观察者模式。
当我们执行应用的时候,使用 Controller 做初始化:
(function() {var controller = new myapp.Controller();
controller.init();})();
通讯
各部分之间的通信方式如下,所有通讯都是单向的。
- View 传送指令到 Controller
- Controller 完成业务逻辑后,要求 Model 改变状态
- Model 将新的数据发送到 View,用户得到反馈
接受用户指令时,MVC 可以分成两种方式:
一种是通过 View 接受指令,传递给 Controller。
另一种是直接通过 controller 接受指令。
MVC 模式的业务逻辑主要集中在 Controller,而前端的 View 其实已经具备了独立处理用户事件的能力,当每个事件都流经 Controller 时,这层会变得十分臃肿。而且 MVC 中 View 和 Controller 一般是一一对应的,捆绑起来表示一个组件,视图与控制器间的过于紧密的连接让 Controller 的复用性成了问题。
MVVM
MVVM 是 Model-View-ViewModel 的简写。它本质上就是 MVC 的改进版。MVVM 就是将其中的 View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。
Model
我们可以把 Model 称为 数据层,因为它仅仅关注数据本身,不关心任何行为(格式化数据由 View 的负责),这里可以把它理解为一个类似 json 的数据对象。
var data = {val: 0};
View
指的是所看到的页面,和 MVC/MVP 不同的是,MVVM 中的 View 通过使用模板语法来声明式的将数据渲染进 DOM,当 ViewModel 对 Model 进行更新的时候,会通过数据绑定更新到 View。
div id="myapp">
<div>
<span>{{val}}rmb</span>
</div>
<div>
<button v-on:click="sub(1)">-</button>
<button v-on:click="add(1)">+</button>
</div>
</div>
ViewModel
mvvm 模式的核心,它是连接 view 和 model 的桥梁。它有两个方向:
- 将
Model
转化成View
,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。 - 将
View
转化成Model
,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。
new Vue({
el: '#myapp',
data: data,
methods: {add(v) {if(this.val < 100) {this.val += v;}
},
sub(v) {if(this.val > 0) {this.val -= v;}
}
}
});
总结:
在 MVVM 的框架下视图 View
和模型 Model
是不能直接通信的。它们通过 ViewModel
来通信,ViewModel
通常要实现一个 observer 观察者,当数据发生变化,ViewModel 能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel 也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。并且 MVVM 中的 View 和 ViewModel 可以互相通信。
整体来看,比 MVC/MVP 精简了很多,不仅仅简化了业务与界面的依赖,还解决了数据频繁更新(以前用 jQuery 操作 DOM 很繁琐)的问题。因为在 MVVM 中,View 不知道 Model 的存在,ViewModel 和 Model 也察觉不到 View,这种低耦合模式可以使开发过程更加容易,提高应用的可重用性。
MVVM 流程图如下:
Vue 数据双向绑定原理
数据绑定
双向数据绑定,可以简单而不恰当地理解为一个模版引擎,但是会根据数据变更实时渲染。
不同的 MVVM 框架中,实现双向数据绑定的技术有所不同。目前一些主流的前端框架实现数据绑定的方式大致有以下几种:
- 数据劫持 (Vue)
- 发布 - 订阅模式 (Knockout、Backbone)
- 脏值检查 (Angular)
Vue 采用数据劫持 & 发布 - 订阅模式的方式,通过 ES5 提供的 Object.defineProperty()
方法来劫持(监控)各属性的 getter
、setter
,并在数据(对象)发生变动时通知订阅者,触发相应的监听回调。并且,由于是在不同的数据上触发同步,可以精确的将变更发送给绑定的视图,而不是对所有的数据都执行一次检测。
要实现 Vue 中的双向数据绑定,大致可以划分三个模块:Observer、Compile、Watcher,如图:
-
Observer 数据监听器
负责对数据对象的所有属性进行监听(数据劫持),监听到数据发生变化后通知订阅者。 -
Compiler 指令解析器
扫描模板,并对指令进行解析,然后绑定指定事件。 -
Watcher 订阅者
关联 Observer 和 Compile,能够订阅并收到属性变动的通知,执行指令绑定的相应操作,更新视图。Update()是它自身的一个方法,用于执行 Compile 中绑定的回调,更新视图。
数据劫持
一般对数据的劫持都是通过 Object.defineProperty 方法进行的,Vue 中对应的函数为 defineReactive
,其普通对象的劫持的精简版代码如下:
var foo = {
name: 'vue',
version: '2.0'
}
function observe(data) {if (!data || typeof data !== 'object') {return}
// 使用递归劫持对象属性
Object.keys(data).forEach(function(key) {defineReactive(data, key, data[key]);
})
}
function defineReactive(obj, key, value) {
// 监听子属性 比如这里 data 对象里的 'name' 或者 'version'
observe(value)
Object.defineProperty(obj, key, {get: function reactiveGetter() {return value},
set: function reactiveSetter(newVal) {if (value === newVal) {return} else {
value = newVal
console.log(` 监听成功:${value} --> ${newVal}`)
}
}
})
}
observe(foo)
foo.name = 'angular' //“监听成功:vue --> angular”复制代码
上面完成了对数据对象的监听,接下来还需要在监听到变化后去通知订阅者,这需要实现一个消息订阅器 Dep
,Watcher 通过 Dep
添加订阅者,当数据改变便触发 Dep.notify()
,Watcher 调用自己的 update()
方法完成视图更新。
推荐阅读:
【专题:JavaScript 进阶之路】
ES6 Promise
JavaScript 之深入理解闭包
ES6 尾调用和尾递归
Git 常用命令小结
参考:https://juejin.im/post/593021272f301e0058273468
我是 Cloudy,年轻的前端攻城狮一枚,爱专研,爱技术,爱分享。
个人笔记,整理不易,感谢阅读、点赞和收藏。
文章有任何问题欢迎大家指出,也欢迎大家一起交流前端各种问题!