一、前端MVC架构
- Model模型层:获取、存放所有的对象数据
- View表现层:呈现信息给用户
- Controller控制层:模型和视图间的纽带
二、MVC架构原理
- 简单Model层实现
function Model () { this.text = 'hello'; } - 简单Controller层实现
function Controller (model) { var that = this; this.model = model; this.handleEvent = function (e) { e.stopPropagation(); switch(e.type) { case 'click': that.handleClick(e.target); break; default: console.log(e.target); } } this.getModelByKey = function (modelKey) { return that.model[modelKey]; } this.handleClick = function (target) { that.model.text = 'world'; target.innerText = that.getModelByKey('text'); } } - 简单View层实现
function View = function (controller) { this.controller = controller; this.demoButton = document.getElementById('demo-button'); this.demoButton.innerText = this.controller.getModelByKey('text'); this.demoButton.addEventListener('click', controller); } - 简单MVC调用逻辑实现
function main () { var model = new Model(); var controller = new Controller(model); var view = new View(controller); }
三、设计双向绑定的MVC
- Model层优化:通过数据劫持实现观察者模式
function Model () { var that = this; var text = "hello"; this.listeners = []; Object.defineProperty(that, 'text', { get: function () { return text; }, set: function (value) { text = value; that.notify(); } }); } Model.prototype.subscribe = function (listener) { this.listeners.push(listener); }; Model.prototype.notify = function (value) { var that = this; this.listeners.forEach(function (listener) { listener.call(that, value); });}; - View层优化:当数据发生变更的时候,实现对应View的更改
function View (controller) { var that = this; this.controller = controller; var elements = document.querySelectorAll('[data-tw-bind]'); elements.forEach(function (element) { if (element.type === 'button') { element.innerText = controller.getModelByKey('text'); that.call = function (data) { element.innerText = data.text; }; element.addEventListener('click', controller); } }); this.controller.model.subscribe(this); } - Controller层:通过clickHandler方法对数据进行更新
function Controller (model) { ... this.clickHandler = function (target) { this.model.text = 'world'; } }
四、前端框架选型
- 考虑因素
- 框架是否能满足大部分应用的需求
- 框架的生态是否丰富
- 框架的社区支持度怎么样
- 框架的替换成本如何
- 团队成员是否能快速掌握该框架
- 框架对浏览器的支持程度如何
- 框架维护成本和难度如何
- React
- Virtual DOM
- 组件化
- 思想不局限于前端领域,还有React Native、React VR、SSR等
- 生态丰富
- 仅充当View层,构建完整的应用还需要结合路由库、执行单向流库、web API调用库、测试库、依赖管理库等
- Angular
- 大而全
- 开箱即用
- 规范性,提供了一系列开发规范和指南
- 出现问题不易修改
- 跨平台:NativeScript、Ionic
- Vue
- 易上手
- 对多页面应用友好,可直接通过引入vue.min.js进行使用
- 中文文档丰富
- 跨平台:weex等
五、启动前端应用
- 创建应用脚手架
- 通用的业务相关模块,比如登录、授权、Token管理
- 页面模板页,比如首页
- 业务模板,比如中后台应用模板
- 持续部署脚本,比如持续集成、部署脚本
- 常用的依赖,比如UI组件库
- 构建组件库
- 选择组件库
- 是否需要跨框架的组件库
- 组件库是否容易替换
- 创建组件库
- 寻找一个现成的组件库
- 构建出组件库的基础设施,从找出来的组件库中删除所有的组件,修改项目名等
- 编写一两个测试组件,引入项目中进行测试
- 持续不断地更新组件库
- 考虑浏览器的支持范围
- JavaScript兼容(polyfill)
- CSS兼容
- 响应式支持
- 分辨率适配
- 移动设备适配
六、服务端渲染
- 非JavaScript语言的同构渲染
- 基于JavaScript语言的同构渲染(以Vue.js为例)
const Vue = require('vue'); const server = require('express')(); const renderer = require('vue-server-renderer').createRenderer(); server.get('*', (req, res) => { const app = new Vue({ data: { url: req.url }, template: `<div>访问的URL是:{{ url }}</div>` }); renderer.renderToString(app, (err, html) => { if (err) { res.status(500).end('Internal Server Error'); return; } res.end(` <!DOCTYPE html> <html> <head> <title> Hello </title> </head> <body> ${html} </body> </html> `) }); }); server.listen(8080); - 预渲染
- 爬虫生成静态页面:在本地运行应用,用爬虫抓取所有页面,再上传到文件存储服务器即可
- 程序生成静态页面:在本地运行应用,内部带有真实的线上数据,由PhantomJs/Chrome Headless来渲染页面,再保存为对应的页面
- 静态站点生成器:编写一个独立的应用程序,该应用程序将从服务器获取数据,再通过模板来渲染出静态页面