前端面试高频题:MPA和SPA

245 阅读8分钟

可以跳过的闲话

在聊SPA和MPA之前我们先扯一扯前端网页的发展史。

互联网古早时期是没有前端这个词的,1999年IE5在ActiveX中引入了一个对象:MSXML2.XMLHTTP,这个就是XMLHttpRequest的前身,直到2005年Ajax的出现,web迈入2.0时代。当时浏览器太多了,大家相互撕逼不断就衍生出了不少兼容性的js库,其中最出名的就是jQuery(操作Dom的工具),jQuery有多火?开发者们把2009—2016年这段时间称为jQuery时代,个人感觉他火的根本原因是Sizzle选择器的出现,简化了前端的编程思路,到现在被人吐槽的链式调用写法就是从这里开始风靡的。

jQuery时代,也是前后端开始分离的时代,因为种种原因前端开始朝模块化发展(RequireJS+SeaJS、CommonJS)。开发者们开始觉得jQuery在处理HTML字符串的时候需要大量拼接,后期维护很麻烦,是不是可以把需要处理的HTML写成单独的文件?把变量直接插入到HTML里面?很快前端模板(template)就这么被创造出来了,紧接着前端路由诞生了,至此SPA的雏形已经形成。最炸裂的来了,来自Ruby界的高手写了个Nodejs(基于chrome的V8)。纯前端的开发者们欢呼雀跃,可以自己整活儿了。至此jQuery时代进入尾声。

与此同时,伴随着业务场景的复杂程度和数据交互请求量的增加,开发者们就开始思考如何更好的把变量的值更新到Dom上,模板引擎就是将Mode层的变量自动反馈到View视图,但是当时几乎所有模板引擎都没办法解决一个问题:无法做到最小化更新,细化到某个文本节点。2009年AngularJS的出现就把这个问题解决了,在内存中进行数据比对(脏检查),只更新对应有变化的Dom。这个就是MVVM模型,区别于MVC模型就多了一个数据绑定功能,但是这个是非常难实现的。伴随着NodeJS的快速发展,前端有了自己的CLI,前端工程化慢慢起步。

2016年三大前端框架开始流行,网站从web page进化成了web application,开启了黄金的SPA时代。jQuery时代至此落幕。

一、概念

1、SPA = SinglePage Web Application

是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,这种方法避免了页面之间切换打断用户体验。

在单页面应用中,所有必要的代码(html、css、js)都通过单个页面的加载而检索,或者根据需要(正常情况下是为了响应用户操作)动态装载适当的资源并添加到页面,页面在任何时间点都不会重新加载,也不会将控制转移到其他页面。

简单说:就是只有一个页面的web应用,进入页面只需要加载一次相关资源(css、js),所有的内容都在这一个页面里面,对每个功能内容做组件化,单页面应用跳转其实就是切换组件,刷新局部资源。

2、MPA = MultiPage Application

有多个独立的页面的应用,每次页面切换,资源都需要重新加载

二、场景应用

目前来说SPA多见于web应用(vue、react等),MPA多见于app和客户端(早期web也是mpa),从性能上面区分使用场景,对体验度和流畅度有较高要求的应用(经常需要切换页面和数据传递)选SPA,对SEO需求较高的且想快速开发的使用MPA。

三、区别

SPAMPA
构成只有一个主要页面,其他都是组件页面每个页面都是一个主页面
渲染客户端渲染(CSR)、预渲染纯服务端渲染(SSR)
刷新方式局部刷新,不会触发html请求整页刷新,依赖url请求
url模式hash/history模式链接跳转(a标签/js的跳转方法)
SEO捕获不了直接在页面上加关键字
数据传递成熟的工具很多(基于ES6的BOM对象History/hashchange事件)url传参、cookie、localStorage
安全性容易遭受攻击
资源共用共用,只需在外层部分加载一次不共用,每个页面都需要加载
用户体验页面之间切换快,流畅页面切换需要加载css、js资源等待时候相对较长
过渡动画容易实现,且体验较好依赖js插件库实现,且体验不好
html文件请求第一次进入页面的时候会请求一个html文件,刷新清除。切换到其他组件,此时路径发生变化但是并没有新的hrml文件请求,页面内容也变化了每一次页面跳转的时候,后台服务器都会给返回一个新的html文件
成本开发成本越来越低、维护容易:最大优势是可重用的后端代码,强解耦合前后端分离,api可以提供给多端使用,加快了多端开发的过程开发成本低,维护成本基于项目复杂程度,业务繁琐不易于后期维护,如果前后端不分离前后端的代码粘连性高,增加后期维护成本

四、SPA原理

SPA底层大致包括以下几个方面:

单页加载:SPA在初始加载只会加载一个html文件,通常是一个空白的主页面。所有的页面内容都通过ajax请求或者websocket技术从服务器获取,通过动态替换DOM内容并同步修改url地址,来模拟多页应用的效果。

前端路由:SPA使用前端路由来处理URL和页面之间的映射关系。通过监听浏览器URL的变化,SPA能够根据不同的URL加载相应的内容,而无需重新请求服务器。

数据驱动视图:SPA采用了数据驱动的视图模型,目前当下流行的MVVM的架构模式。数据和视图之间建立了双向绑定的关系,当数据发生变化时,视图会自动更新,反之亦然。

前端框架:为了更好的支持SPA开发,前端框架提供了一系列工具和功能,简化了SPA的实现和维护。这些框架通常提供了路由管理、状态管理、组件化等功能,使得开发者可以更高效的构建复杂的SPA应用。

综上,我们发现SPA有三个问题需要解决,第一个如何改变URL不去请求服务端资源,从而减轻服务端压力还可以拥有MPA的用户体验,前端路由这个时候就出现了;第二个如何Model层数据发生变化,自动反馈给视图View,反之也一样,数据双向绑定这就来了;第三个因为SPA的特性用户首次访问应用会有个等待时间,如何渲染可以减少等待时间?

今天先说说第一个前端路由,它其实是页面的状态管理器,能保存用户页面状态,方便搜索引擎的收录,实现方法如下:

1.使用window.location.hash属性及窗口的onhashchange事件,可以实现监听浏览器地址hash值变化,执行加载相应的内容。hash值是不会随请求发送到服务器端的,所以改变hash的值并不会重新加载页面;

// 定义 Router 
class Router { 
    constructor () { 
        this.routes = {}; 
        // 存放路由path及callback 
        this.currentUrl = ''; 
        // 监听路由change调用相对应的路由回调 
        window.addEventListener('load', this.refresh, false); 
        window.addEventListener('hashchange', this.refresh, false);
    } 
    route(path, callback){ 
        this.routes[path] = callback; 
    } 
    push(path) { 
        this.routes[path] && this.routes[path]() 
    } 
} 
// 使用 router 
window.miniRouter = new Router(); 
miniRouter.route('/', () => console.log('page1')) 
miniRouter.route('/page2', () => console.log('page2'))
miniRouter.push('/') // page1 
miniRouter.push('/page2') // page2

2.history 对象保存了当前窗口访问过的所有页面网址。history 对象发生改变时,只会改变页面的路径,不会刷新页面。每当 history 对象出现变化时,就会触发 popstate事件。

在 history 路由中,用到的history的方法:

  1. back():后退到上一个路由;
  2. forward():前进到下一个路由,如果有的话;
  3. go(number):进入到任意一个路由,正数为前进,负数为后退;
  4. pushState(obj, title, url):前进到指定的 URL,不刷新页面;
  5. replaceState(obj, title, url):用 url 替换当前的,不刷新页面;

调用这几种方式时,都会只是修改了当前页面的 URL,不会刷新页面。如果有面试官问起这个问题“如何仅修改页面的 URL,而不发送请求”,那么答案就是这 5 种方法。但是前 3 个方法只是路由历史记录的前进或者后退,无法跳转到指定的 URL。

写在最后

不难发现,MPA和SPA其实是互补的,日常实际开发里面也会把两者结合起来使用。写的比较浅显,SPA的东西还有很多,文章参考了一些技术分享,自己在梳理知识的同时希望能帮助到大家,如有问题欢迎大佬们指正!