1.传统前端架构
如果按照以前传统前端的方式去架构的话,随着前端业务逻辑的增多,开发和维护会变得越来越困难,请看: 这是我去年做的华为项目Telkomsel Moovigo的目录,主要业务逻辑的代码有65个之多(每个包含page、fss、js),虽然页面、样式、逻辑代码确实是分开的,但是都是合在同一个文件里面。
去年做的另一个华为项目Digicel也是同样:
做这些项目的团队,就我们前端人员每个团队都有10人左右,维护相对简单,一人搞定6~7个就OK,那么问题来了,假如1人来搞定所有呢?因为是外包,所以这样做没太大问题,只要控制了成本,在时间少的情况下做出来且赚钱就行。有些创业公司是自主研发做产品的,如果还是这样做,会出现什么问题呢? 请看,这是我现在负责重构的微信端产品代码目录: 卧槽,这么简单,这么清爽,完全没问题呀? 这样分的好处确实有,html文件夹里面就是界面,css文件夹里面就是样式,js文件夹里面就是业务逻辑,“结构”看起来特别清晰。 但是当你打开后,近100个html、js、css文件都在各自的同一个文件夹里面,请看: 这样写最主要的两个问题是:
- 难复用
- 难维护
可能有些人不理解难复用的意思,复用怎么困难了?不是有现成的代码吗,复制粘贴重新创建一个改一改不就行了吗?这就是复用了呀!非也。 复用的意思是在不改变现有代码的基础上,依赖以前写好的代码,进行二次开发。在程序的世界,有句话一直很经典:“不要重复造轮子”。举个例,如果将每个页面看成是一辆汽车,一辆汽车由轮胎,中控,发动机等构成,且可以自由组装。同理,前端页面可以抽成很多组件,每个页面都是由这些组件组装而成。
维护的意思是需要在已上线的产品上新增业务;或者去改动有相同业务的页面。不管是修改页面上的图片、样式、位置、业务、Bug、性能都算维护。如果重复造轮子的页面越来越多,维护改动的地方就越多,到最后会越来越麻烦,改动一处,动则n处。为了解决这些问题,最好的方式就是实现组件化.
2.什么是MVVM框架?
从三层架构说起,一个完整应用可以分成持久层、业务层、表现层三部分。 持久层即根据业务实现表及其映射关系,完善数据的CRUD功能; 业务层即根据业务实现对应的接口,调用持久层的东西为每个页面需要掉什么接口,返回什么数据进行封装; 表现层即根据业务实现相应的交互页面,调用业务层的东西实现数据的展现及CRUD。
三层架构各自其责,其实最痛苦的莫过于表现层。因为持久层和业务层的最终实现在表现层。 表现层是最重要的环节,做前端就是做交互,因为产品好坏的决定因素是用户的交互体验,当然其他两层也同样重要,因为表现层依赖于它们。 表现层说简单点就是做页面,三个字说起来容易,实际却不易,因为做一个页面,要做它的交互、数据展现、性能优化,还要理解页面与页面之间的关系等。页面多了,重复做这些事情就很麻烦。
为了更好的说明表现层,于是乎表现层出现了MVC架构,MVC是三个单词的首字母缩写,它们是Model(模型)、View(视图)和Controller(控制)。 刚入行web前端的人员可能是这样理解的,HTML就是Model,CSS就是View,JS就是Controller。 因为他们想的: 模型层就是指结构,HTML语义化就是在说结构呢; 视图层就是指展现效果,CSS样式就是给HTML标签实现效果呢, 控制层就是指操作,JS就是操作HTML、CSS、调用接口。 如果这样理解就大错特错啦,这里有通过 JavaScript实现的一个基础MVC模型,请注意的是:MVC 不是一种技术,仅是一种理念。
/** 模拟Model,View,Controller */
var M = {}, V = {}, C = {};
/** Model 负责存放数据 */
M.data = "hello world";
/** View 负责将数据呈现到屏幕上 */
V.render = function (M) { alert(M.data); }
/** Controller 作为一个 M 和 V 的桥梁 */
C.handleOnload = function () { V.render(M); }
/** 在网页读取的时候呼叫Controller */
window.onload = C.handleOnload;
又说多了,现在来讲什么是MVVM? 但是要讲清楚它之前,就必须讲MVC,因为根据它衍生出了MVP(这里不做讲解)和MVVM架构。 所以MVVM是MVC的衍生物,也是一种架构。
那么,MVC、MVVM两者的区别是什么呢? 依据一个简单的实例来讲解:
实现这个页面,一般思路是: 1.掉接口获取数据 2.在回调里使用获取的值渲染页面 3.添加事件
var PageView={
initialize:function(){
var $this=this;
$this.el=$('#main');
$this.fetch(function(record){ //1.
$this.render(record); //2.
$this.addEvent(); //3.
});
}
};
PageView.initialize();
在该代码中,模型(Model)就是从接口返回的record;视图(View)就是将获取的record设值到页面的render方法;控制(Controller)就是根据业务调取接口去获取数据的fetch方法。 执行的顺序是Model -> View -> Controller。
再举个稍复杂点例子,如图: 1.进入发起页面
2.添加数据
3.跳转成功发布的页面
当用户打开发起页面添加数据,点提交跳转到成功发布的页面,这一过程的顺序就是View -> Controller -> Model -> View。用户看到发起的页面是View,点提交这一步骤是Controller、提交完成数据保存到数据库是Model,从发起页面跳转到成功发布的页面是View。
submit: function(el) {
var $this = this;
if (basis.pub.isDisable(el)) return;
basis.pub.disable(el); //禁用
basis.pub.clear();
basis.wait.show();
var postData = {
createKey: config.data.createKey,
openID: config.params.openID,
title: $('.q-title', $this.el).val(), //e
desc: $('.q-desc', $this.el).val(), //e
endType: $('.endType', $this.el).val(), //e
picTemplateID: $('.picTemplateID', $this.el).val(), //e
isOpenAnswer: $('.isOpenAnswer', $this.el).val(), //e
itemimg: [],
itemUrlDesc: [],
questionID:config.params.questionID,
isCopy:config.params.copy
};
$('.q-img-options .has-img', $this.el).each(function() {
var tag = $(this),
next = tag.next('.img-item-desc');
postData.itemimg.push(tag.children('.itemimg').val()); //e
if (next.length) {
postData.itemUrlDesc.push(next.children('.itemUrlDesc').val().replace(/,/ig, ','));//e
}
});
postData.itemimg = postData.itemimg.join(','); //e
postData.itemUrlDesc = postData.itemUrlDesc.join(','); //e
...
}
上面的问题在于: 1.用户点击发布时,提交表单前需要挨个获取DOM的val值(标注e的),这样写的坏处就是重复去获取不同DOM,重复去设值。 2.当用户提交后跳转到成功发布的页面,这样写的坏处就是跳转后需要重新加载该页面的所有资源(html、css、js、图片等),还需要调很多接口获取数据,等数据获取后又要重复去获取DOM,然后根据业务改变其样式,展示出最终的页面效果。
MVC流程图:
MVC特点:所有通信都是单向的
同样的页面用MVVM来实现,该是怎样的呢?从简单的实例入手,这里我使用了Vue.js,代码如下: HTML代码:
<div id="main">
<div class="top box">
<div class="left box-flex-1">
<h1 class="vote_create_count">{{ user.vote_create_count }}</h1><span>累计发起</span>
</div>
<div class="right box-flex-1">
<h1 class="can_vote_count"> {{ user.can_vote_count }} </h1><span>今日可发</span>
</div>
</div>
<div class="mid-name">
<h2 class="nikename">{{ user.nikename }}</h2>
</div>
<div class="user-head" >
<div class="head-image" style='background:url( {{ user.headimage }} ) no-repeat;background-size:100%'></div>
</div>
</div>
JavaScript代码:
var vm = new Vue({
el:'#main',
data:{
user: {}
},
created:function(){
var self=this;
this.fetch(function(record){
self.user=record;
});
},
methods:{
fetch:function(callback){
...
basis.req.ajax(ops);
},
addEvent:function(){
...
}
}
});
在该代码中,模型(Model)就是vm实例对象的data里的值,如:user;视图(View)就是上面整个Html代码,如:{{xxx}};ViewModel就是vm实例对象,它建立了模型和视图的桥梁。
通过ViewModel能实现View和Model的实时更新,这样写的好处在于: 1.解决不需要重复去获取DOM、重复去操作DOM等事情,关注点能重点放在业务及页面组成上 2.方便组件化即复用 3.接口返回的数据是什么,前端马上展示什么,你变我也变
MVVM流程图:
MVVM特点:实现数据和视图的双向绑定
3.Vue.js的简单介绍
Vue.js的作者为**Evan You,任职于Google Creative Lab,虽然Vue是一个个人项目,但在发展前景上个人认为绝不输于Google的AngularJs,不要小看一个个人项目,比如一个人创建了backbone.js、underscore.js、coffeescript的前端大神Jeremy Ashkenas**,我们在使用其框架的时候还不是风生水起。
引用Vue的官网(http://cn.vuejs.org/)所介绍的那样,其主要特点有两个:
Vue.js的目标是通过尽可能简单的API实现响应的数据绑定和组合的视图组件。
1.响应的数据绑定
Vue.js的核心是一个响应的数据绑定系统,它让数据与DOM保持同步非常简单。 在使用jQuery/Zepto.js手工操作DOM时,我们的代码常常是命令式的、重复的与易错的。 Vue.js拥抱数据驱动的视图概念。 通俗地讲,它意味着我们在普通的HTML模板中使用特殊的语法将DOM“绑定”到底层数据。一旦创建了绑定,DOM将与数据保持同步。每当修改了数据,DOM便相应地更新。这样我们应用中的逻辑就都是直接修改数据,不必与DOM更新搅在一起。这让我们的代码更容易撰写、理解与维护。
实例1: html代码:
<!-- 这是我们的 View -->
<div id="example-1">
Hello {{ name }}!
</div>
js代码:
// 这是我们的 Model
var exampleData = {
name: 'Vue.js'
}
// 创建一个 Vue 实例或 "ViewModel"
// 它连接 View 与 Model
var exampleVM = new Vue({
el: '#example-1',
data: exampleData
})
效果:
Hello Vue.js!
看起来这跟单单渲染一个模板非常类似,但是Vue.js在背后做了大量工作。并且DOM会自动响应数据的变化。我们如何知道?打开你的浏览器的控制台,修改exampleData.name,你将看到上例相应地更新。
总结一下就是使用Vue.js我们不需要撰写任何DOM操作代码:被绑定增强的HTML模板是底层数据状态的声明式的映射,数据不过是存在了普通的JavaScript对象里。我们的视图完全由数据驱动。
实例2: html代码:
<div id="example-2">
<p v-if="greeting">Hello!</p>
</div>
js代码:
var exampleVM2 = new Vue({
el: '#example-2',
data: {
greeting: true
}
})
效果:
Hello!
这里我们遇到新东西。你看到的 v-if 特性被称为指令。指令带有前缀 v-,以指示它们是 Vue.js 提供的特殊特性。并且如你所想象的,它们会对绑定的目标元素添加响应式的特殊行为。继续在控制台设置 exampleVM2.greeting 为 false,你会发现 “Hello!” 消失了。
第二个例子演示了我们不仅可以绑定DOM文本到数据,也可以绑定DOM结构到数据。而且,Vue.js也提供一个强大的过渡效果系统,可以在Vue插入/删除元素时自动实现过渡效果。
也有一些其它指令,每个都有特殊的功能。例如v-for指令用于显示数组元素,v-bind指令用于绑定HTML特性。更多内容可以去官网查看API。
2.组合的视图组件
组件系统是Vue.js另一个重要概念,因为它提供了一种抽象,让我们可以用独立可复用的小组件来构建大型应用。如果我们考虑到这点,几乎任意类型的应用的界面都可以抽象为一个组件树:
实际上,一个典型的用Vue.js构建的大型应用将形成一个组件树。后续将详述组件,不过这里有一个假想的例子,看看使用了组件的应用模板是什么样的: html代码:
<div id="app">
<app-nav></app-nav>
<app-view>
<app-sidebar></app-sidebar>
<app-content></app-content>
</app-view>
</div>
你可能已经注意到Vue.js组件非常类似于自定义元素——它是Web组件规范的一部分。实际上Vue.js的组件语法参考了该规范。例如Vue组件实现了Slot API与is特性。但是,有几个关键的不同:
- Web组件规范仍然未完成,并且没有浏览器实现。相比之下,Vue.js组件不需要任何补丁,并且在所有支持的浏览器(IE9及更高版本)之下表现一致。必要时,Vue.js组件也可以放在原生自定义元素之内。
- Vue.js组件提供了原生自定义元素所不具备的一些重要功能,比如组件间的数据流,自定义事件系统,以及动态的、带特效的组件替换。
组件系统是用Vue.js构建大型应用的基础。另外,Vue.js生态系统也提供了高级工具与多种支持库,它们和Vue.js一起构成了一个更加“框架”性的系统。
最近还在继续深入学习Vue.js,最后想说: 个人认为前端的一些技术都是融会贯通的,学习一门语言或者框架本身并不是为了学习它的技术,最重要的是学习它的思维,只有思维层面得到了延伸,学习其他技术的时候才会得心应手。Vue带给我们的是前端一种解决问题的新的思维。
一句话概括:
现阶段前端人员最大的问题不是技术实现,而是思维转变。