一、单页面应用分析
- 构建成本
- 本地构建流程处理
- 持续集成服务器
- 当框架提供的构建流程不符合需求的时候,需要重写构建脚本
- 学习成本
- 前端技术更新快
- 旧框架维护学习成本
- 新框架学习成本
- 后台渲染成本
- 预渲染,向搜索引擎提供一份可以被索引的HTML代码
- 同构应用,由后端运行JavaScript代码生成对应的HTML代码
- 混合式后台渲染,由后端解析前端模板,生成对应的HTML代码
- 应用架构的复杂性
- 前端需要处理更加复杂的应用交互
- 后台的数据通过API接口直接暴露,需要更严谨考虑数据安全的问题
- 复杂的应用,需要考虑增加BFF层(服务于前端的后端)
- 代码逻辑需要重复校验,比如权限和敏感的表单数据提交,需要同时在前端和后端进行校验
- 为了提供可供前端测试的API,需要搭建Mock Server和造Mock数据
二、简单多页面应用分析
- 选择UI库及框架
- 在多页面时代,由于前端应用的简单化,几乎打部分前端团队都拥有自己的前端框架和UI库
- 随着复杂度的提升及开源社区的活跃,大部分中小公司逐步采用外部框架解决方案
- jQuery和Bootstrap仍然好用
- jQuery大大地简化了HTML与JavaScript的操作
- 因为大量旧网站的存在,目前jQuery仍然是使用最广泛的前端框架
- Bootstrap由于其响应式的处理能力,使得开发人员可以更关注于实现,而不是CSS样式,也因此成了一些开源CMS、框架所提供的默认UI框架
- 如果想快速使用多页面技术来开发应用,那么使用jQuery的生态能感受到显著的开发优势,再将Bootstrap作为UI框架来提升开发体验
- 不使用框架
- 对于只是简单地显示、隐藏DOM等操作的应用来说,不需要使用框架
- HTML新标准形成后,DOM的操作进一步简化,比如原生的querySelectorAll等属性,为了提升兼容性,还可以配置一个pollyfill
三、复杂多页面应用设计
- 模板与模板引擎原理
- 简单的HTML可以通过字符串拼接或者模板字符串的方式来生成,如:
html += '<button type="button"' + ' i-id="' + id + '"' + style + (val.disabled ? 'disabled' : '') + (val.autofocus ? 'autofocus class="ui-dialog-autofocus"' : '') + '>' + val.value + '</button>'; - 生成复杂的HTML使用模板引擎
- 基于字符串的模板引擎设计
- 代表框架:Mustache、Handlebars.js
- 在更新DOM的时候会更新整个DOM节点
- 模板引擎实现
/** * 模板解析器 */ function simple(template, data) { return template.replace(/\{\{([\w\.]*)\}\}/g, function(str, key) { var keys = key.split("."); var value = data[keys.shift()]; for (var i = 0; i < keys.length; i++) { if (value) { value = value[keys[i]]; } } return (typeof value !== undefined && value !== null) ? value: ""; }); } /** * 使用示例 */ var data = { user: { name: 'Phodal', info: { address: { city: '漳州' } } } } var temp = simple("<p>用户:{{user.name}},地址:{{user.info.address.city}}<p>", data); document.getElementById("result").innerHTML = temp; - 基于JavaScript的模板引擎设计
- 将模板编译为某种SDL(领域特别语言),比如HyperScript或者Javascript对象(代码+数据)
- 在使用时,调用JavaScript来渲染出DOM节点
- 当发生变更时,通过DOM Diff算法来替换对应的修改节点
- 双向绑定原理及实践
- 视图变化实时地让数据模型变化
/** * 在输入模型中输入数据时,监听数据的变化,再将这些值传递到模型中进行处理 */ var inputData = ''; var inputText = document.querySelector('#inputText'); inputText.addEventListener('input', function (event) { inputData = event.target.value; }); - 数据变化时,更新视图
var button = document.querySelector('#change-button'); button.addEventListener('click', function (event) { inputText.value = 'change'; }); - 双向绑定几种实现方式:
- 手动绑定:两个单向绑定的结合,通过手动set和get数据来触发UI或数据变化
- 脏检查机制:在发生指定的事件(如HTTP请求、DOM事件)时,遍历数据相应的元素,然后进行数据比较,对变化的数据进行操作
- 数据劫持:通过hack的方式(Object.defineProperty())对数据的getter和setter进行劫持,在数据变化时,通知相应的数据订阅者,以触发相应的监听回调
var o = {}; var bValue; Object.defineProperty(o, "b", { get: function () { return bValue; }, set: function (newValue) { bValue = newValue; }, enumerable: true, configurable: true }); o.b = 38; - 前端路由原理及实践:监听路由的变化,调用函数来处理对应的逻辑
- 两种路由类型
- History模式
- back:返回前一页
- forward:在浏览器记录中前往下一页
- go:在当前页面相对位置从浏览器历史记录记载页面
- pushState:按指定的名称和URL将数据push进会话历史栈
- replaceState:指定的数据、名称和URL,更新历史栈上最新的入口
- Hash模式:
- 监听hashchange事件:window.addEventListener('hashchange', functionRef, false)
- Hash值的改变不会导致页面重新加载
- Hash值由浏览器控制,不会发送到服务器端
- Hash值的改变会记录在浏览器的访问历史中,因此可以在浏览器中前进和后退
- 自造Hash路由管理器
- add:创建理由集、添加路由的key及其对应的函数
function Router () { this.routes = {}; this.currentUrl = ''; } Router.prototype.add = function (path, callback) { this.routes[path] = callback || function () {}; } - refresh:解析出当前路由的key,再根据key从路由集中找到并调用对应的路由处理函数
Router.prototype.refresh = function () { this.currentUrl = location.hash.replace(/^#*/, ''); this.routes[this.currentUrl](); } - load:初始化路由相应的监听事件
Router.prototype.load = function () { window.addEventListener('load', this.refresh.bind(this), false); window.addEventListener('hashchange', this.refresh.bind(this), false); } - navigate:跳转到对应的路由
Router.prototype.navigate = function (path) { path = path ? path : ''; location.href = location.href.replace(/#(.*)$/, '#' + path); }
四、避免散弹式架构
- 散弹式架构应用
- Backbone轻量级MVC
- jQuery使用户能更加方便地处理HTML、events、实现动画效果
- Mustache模板引擎
- Require.js依赖模块管理
- 如何降低散弹性架构的出现频率
- 统一交互处理
- 按页面拆分脚本
- 用ID而非class
- 其它唯一选择器
推荐阅读:
参考资料:
- 《前端架构:从入门到微前端》