单页面与多页面应用开发对比以及单页面路由实现原理

本文对比了多页面应用(MPA)与单页面应用(SPA)的特点。MPA为传统网页设计,每次页面跳转需重新加载;SPA则采用单一页面,通过JavaScript操作DOM实现动态页面变化,提供更流畅的用户体验,常用于现代WebApp。文中还提供了SPA路由实现的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MPA与SPA简介

MPA

MPA (Multi-page Application) 多页面应用指的就是最传统的 HTML 网页设计,早期的网站都是这样的设计,所之称为「网页设计」。使用 MPA 在使用者浏览 Web 时会依据点击需求切换页面,浏览器会不停的重载页面 (Reload),整个操作也常感觉卡卡。如果使用这样的设计在 Web App 中,使用者体验比较差,整体流畅度扣分。但进入门槛低,简单套个 jQuery 就可以完成。

SPA

SPA (Single-page Application) 顾名思义在 Web 设计上使用单一页面,利用 JavaScript 操作 Dom 的技术实现各种应用,现今在介面上算是非常受欢迎的设计,搭配 AJAX 使得整体页面反应速度相当迅速,配合上路由懒加载等手段可以达到Native应用的体验。

图解

在这里插入图片描述
在这里插入图片描述

对比

在这里插入图片描述

实现流程

在这里插入图片描述

代码实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>原生实现hash和browser两种路由模式</title>
</head>
<body>
    <div class="router_box">
        <a href="/home" class="router" replace="true">主页</a>
        <a href="/news" class="router">新闻</a>
        <a href="/team" class="router">团队</a>
        <a href="/about100" class="router">关于</a>
    </div>
    <div id="router-view"></div>

    <script>
        function Router(params){
            // 记录routes配置
            this.routes = params.routes || [];
            // 记录路由模式
            this.mode = params.mode || 'hash';

            // 初始化
            this.init = function(){
                // 绑定路由响应事件
                var that = this;
                document.querySelectorAll(".router").forEach((item,index)=>{
                    item.addEventListener("click",function(e){
                        // 阻止a标签的默认行为
                        if ( e && e.preventDefault ){
                            e.preventDefault(); 
                        }else{
                            window.event.returnValue = false;  
                        } 
                        
                        if (that.mode == 'hash'){
                            // 判断是replace方法还是push方法
                            if (this.getAttribute("replace")){
                                var i = window.location.href.indexOf('#')
                                // 通过replace方法直接替换url
                                window.location.replace(
                                    window.location.href.slice(0, i >= 0 ? i : 0) + '#' + this.getAttribute("href")
                                )
                            }else{
                                // 通过赋值追加
                                window.location.hash = this.getAttribute("href");
                            }
                        }else{
                            if (this.getAttribute("replace")){
                                window.history.replaceState({}, '', window.location.origin+this.getAttribute("href"))
                            }else{
                                window.history.pushState({}, '', window.location.origin+this.getAttribute("href"))
                            }
                            // 手动调用更新内容方法
                            that.routerChange();
                        }
                        
                    }, false);
                });
                // 监听路由改变
                if (this.mode == 'hash'){
                    window.addEventListener("hashchange",()=>{
                        this.routerChange();
                    });
                }else{
                    window.addEventListener('popstate', e => {
                        console.log(123);
                        this.routerChange();
                    })
                }
                // 第一次进入的时候更新视图
                this.routerChange();
            },
            // 路由改变监听事件
            this.routerChange = function(){
                if (this.mode == 'hash'){
                    let nowHash=window.location.hash;
                    let index=this.routes.findIndex((item,index)=>{
                        return nowHash == ('#'+item.path);
                    });

                    if(index>=0){
                        // 查找到对应路由
                        document.querySelector("#router-view").innerHTML=this.routes[index].component;
                    }else {
                        // 没找到对应路由,查找有没有*
                        let defaultIndex=this.routes.findIndex((item,index)=>{
                            return item.path=='*';
                        });
                        // 查找到*,执行重定向
                        if(defaultIndex>=0){
                            const i = window.location.href.indexOf('#')
                            window.location.replace(
                                window.location.href.slice(0, i >= 0 ? i : 0) + '#' + this.routes[defaultIndex].redirect
                            )
                        }
                    }
                }else{
                    let path = window.location.href.replace(window.location.origin, '');
                    let index=this.routes.findIndex((item,index)=>{
                        console.log('path...', path, 'item.path...', item.path);
                        return path == item.path;
                    });
                    if(index>=0){
                        // 查找到对应路由
                        document.querySelector("#router-view").innerHTML=this.routes[index].component;
                    }else {
                        // 没找到对应路由,查找有没有*
                        let defaultIndex=this.routes.findIndex((item,index)=>{
                            return item.path=='*';
                        });
                        // 查找到*,执行重定向
                        if(defaultIndex>=0){
                            window.history.replaceState({}, '', window.location.origin+this.routes[defaultIndex].redirect)
                            this.routerChange();
                        }
                    }
                }
            }
            // 调用初始化
            this.init();
        }
        new Router({
            mode: 'history',
            routes:[
                { path: '/home', component: '<h1>主页</h1><h4>新一代前端工程师:我们啥都会</h4>' },
                { path: '/news', component: '<h1>新闻</h1><h4>今天2018-11-5</h4>' },
                { path: '/team', component: '<h1>团队</h1><h4>WEB前端工程师</h4>' },
                { path: '/about', component: '<h1>关于</h1><h4>好好上班</h4>' },
                { path:'*', redirect:'/home'}
            ]
        });
    </script>
</body>
</html>

兼容问题

ie9及以下不支持html5 history新特性,可引入history库(https://github.com/devote/HTML5-History-API)

# 单页面多页面开发及应用 # 单页面: - 什么是单页面单页面是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html, js, css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入。 - 跳转仅加载一次,用于PC公务网站 - 页眉 -> 第一页,第二页, 第三页 -> 脚本 # 单页面技术: -AngularJS是一个全面的客户端侧框架。其模板基于双向UI数据绑定。数据绑定是一种自动方法,在模型改变时更新视图,以及在视图改变时更新模型。其HTML模板在浏览器中编译。编译步骤创建纯HTML,浏览器将其重新渲染到实时视图。该步骤会在随后的页面浏览中重复。在传统的服务器端HTML编程中,控制器模型等概念在服务器进程中进行交互以产生新的HTML视图。在AngularJS框架中,控制器模型状态在客户端的浏览器中维护,从而使生成新页面不依赖服务器的交互。 -Ember.js是基于模型-视图-控制器(MVC)软件架构模型的客户端侧JavaScript Web应用程序框架。它允许开发人员在一个框架中通过常用的习惯用语最佳实践来创建可伸缩的单页面应用程序。该框架提供丰富的对象模型、声明性双向数据绑定、计算属性,Handlebars.js提供的自动更新模板,以及一个路由器管理应用程序状态。 -Meteor.js是一个专门为单页应用设计的全栈(客户端-服务器)JavaScript框架。它具有比Angular、Ember或ReactJS更简单的数据绑定特性且使用--Distributed Data Protocol一个发布/订阅来自动将数据更改传播到客户端,无需开发人员编写任何同步代码。全栈反应确保从数据库到模板的所有层都可以在必要时自动更新。诸如服务器端渲染等生态系统包则解决搜索引擎优化(SEO)等问题。 -Aurelia是一个适用于移动设备、桌面网页的JavaScript客户端框架。它类似AngularJS,但更新、更符合标准,并采用模块化举措。Aurelia使用下一代ECMAScript编写。 -Vue.js(通常称为Vue)是一个用于构建用户界面的开源渐进式JavaScript框架。 -React(通常写为React.js或ReactJS)是一个构建用户界面的JavaScript函式庫。它由Facebook、Instagram个人开发者以及企业社区维护。React最大的优势是易于使用——基本上任何熟悉HTML的开发人员都可以创建React应用程序。另一个所称的优势是可能使用相同的技术堆栈来同时创建Web移动应用程序。有多家公司使用ReactRedux库来让开发人员创建复杂但可扩展的Web应用程序。 -Fulcro是一个全栈库,它采用Netflix的Falcor,Facebook的RelayOm Next对反应性,功能性,数据驱动软件进行改编的数据驱动原则。 -单页面做seo(搜索引擎优化): -根据部分进行优化为每个关键字创建一个或或每个关键字并为其分配一个适当的ID,该页面上的内部链接将链接至ID。 -优化页面速度对于单页网站SEO来说至关重要。 - 做了seo 以后,单页面还是单页面 # 单页面优点: -减小服务器压力。 如果不用单页面(spa),如果每次切换页面的时候,都向服务器发送一个请求,服务器返回一个html文件;但是如果使用了单页面,在切换时,不需要请求服务器,只要通过本地的js来切换即可。并且服务器端就不需要配置路由,完全做到了前后端分离 -增强用户体验,增加app的使用流畅性。 使用spa之后,页面在切换的时候非常流畅,完全没有那种不断刷新的感觉,而是非常快的就有了响应,因为js运行速度很快,所以js在做本地路由的时候,就会非常快。 -单页应用没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并有“闪烁”现象。 -单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑页面合成,吞吐能力会提高几倍。 -良好的前后端分离。后端不再负责模板渲染、输出页面工作,后端API通用化,即同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端。 -单页面缺点: -首次加载耗时比较多。 解决办法:可以采用基于HTTP Chunk 的首屏数据渐进式预加载方案,该方案总体减少了单页应用1.2s的首屏呈现时间。首屏数据渐进式预加载的优化思路也得到了体现: 优化首屏数据加载节点的速度。 预先加载首屏数据,使得多个串行节点并行化。 -SEO问题,不利于百度,360等搜索引擎收录。 解决办法:可以采用prerender服务,它拿到请求够,直接在服务端的一个js engine里,运行这个HTML(就像浏览器做的那样),直到内容动态填完之后,再广播一个事件告诉phantomjs“内容已经好了,可以返回给爬虫了”,这样爬虫就拿到了一份完整的HTML,就在浏览器里渲染完的一样。目前流行的框架都有对应的服务器渲染框架 -如有造成Css命名冲突。 解决办法:我们可以使用Sass、LESSStylus等CSS预处理器,在一定程度上可以解决该问题。 -前进、后退、地址栏、书签等,都需要程序进行管理,页面的复杂度很高,需要一定的技能水平开发成本高。 # 多页面: -什么是多页面:每一次页面跳转的时候,后台服务器都会给返回一个新的html文档,这种类型的网站也就是多页网站,也叫做多页应用 - 跳转需要刷新所有资源,用于app 或 客户端 - 页眉 -> 第一页 -> 脚本,页眉 -> 第二页 -> 脚本,页眉 -> 第三页 -> 脚本 -多页面优点:给用户提供一个完美的视觉方向,重点是没有很多的菜单,简洁明了的SEO管理。由于可以针对每页一个关键字优化应用程序 -多页面缺点:后端移动客户端不能同时使用,前端后端开发紧密结合。开发变得相当复杂。开发人员需要为客户端服务器端使用框架。这导致应用程序开发时间更长。 # 区别: -组成:单页面由一个外壳页面多个页面组成, 多页面由多个完整页面组成 -css(资源公用):单页面为共用,只需要外壳部分加载,多页面则不共用,每个页面单独加载 -刷新方式:单页面页面局部更改或刷新,多页面整页刷新 -url模式:单页面 a.com/#/pageone 多页面 a.com/pageone.html -用户体验:单页面页面切换快,用户体验良好,多页面加载缓慢,流畅度不够,用户体验较差 -转场动画:单页面容易实现多页面无法实现 -数据传递:单页面容易,多页面需要依赖url传参,或cookie,localSrorage等 -搜索引擎优化:单页面需要单独方案,实现较为困难,不利于SEO搜索,可利用SSR优化,多页面实现方法容易 -适用范围:单页面高要求的体验度,追求页面的流畅,多页面追求高度支持搜索引擎的应用 -开发成本:单页面较高,需要专业的框架,多页面较低,但页面重复代码较多 -维护成本:单页面相对容易,双页面相对复杂
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值