一文读尽前端路由、后端路由、单页面应用、多页面应用

本文源自掘金 https://juejin.im/user/5b52fd...css

前端路由

  • 定义:在单页面应用,大部分页面结构不变,只改变部份内容的使用
  • 优势:用户体验好,不须要每次都从服务器所有获取,快速展示给用户
  • 缺点:使用浏览器的前进,后退键的时候会从新发送请求,没有合理地利用缓存。单页面没法记住以前滚动的位置,没法在前进,后退的时候记住滚动的位置

后端路由

经过用户请求的url导航到具体的html页面;每跳转到不一样的URL,都是从新访问服务端,而后服务端返回页面,页面也能够是服务端获取数据,而后和模板组合,返回HTML,也能够是直接返回模板HTML,而后由前端js再去请求数据,使用前端模板和数据进行组合,生成想要的HTMLhtml

先后端路由对比

  1. 从性能和用户体验的层面来比较的话,后端路由每次访问一个新页面的时候都要向服务器发送请求,而后服务器再响应请求,这个过程确定会有延迟。而前端路由在访问一个新页面的时候仅仅是变换了一下路径而已,没有了网络延迟,对于用户体验来讲会有至关大的提高。     
  2. 在某些场合中,用ajax请求,可让页面无刷新,页面变了但Url没有变化,用户就不能复制到想要的地址,用前端路由作单页面网页就很好的解决了这个问题。可是前端路由使用浏览器的前进,后退键的时候会从新发送请求,没有合理地利用缓存。

单页面的优点

  • 不存在页面切换问题,由于只在同一个页面间切换,会更流畅,并且能够附加各类动画和过分效果,用户体验更好。
  • 能够用到vue的路由和状态保持,不用担忧切换形成的数据不一样步。
  • 打包方便,有现成的脚手架能够用,也比较不容易出问题

单页面的劣势

  • 全部逻辑和业务都在一个页面上,逻辑上不是很清楚,当业务变得复杂的时候改动起来就比较麻烦
  • 鸡蛋都在一个篮子里,只要一个地方出现错误,可能致使整个页面出错
  • 全部代码都在一个页面,首次加载耗时较长,页面体积较大

只有一张Web页面的应用,是一种从Web服务器加载的富客户端,单页面跳转仅刷新局部资源 ,公共资源(js、css等)仅需加载一次 页面跳转:使用js中的append/remove或者show/hide的方式来进行页面内容的更换; 数据传递:可经过全局变量或者参数传递,进行相关数据交互前端

使用场景: 适用于高度追求高度支持搜索引擎的应用vue

多页面的优点

  • 逻辑清楚,各个页面按照功能和逻辑划分,不用担忧业务复杂度
  • 单个页面体积较小,加载速度比较有保证
  • 多页面跳转须要刷新全部资源,每一个公共资源(js、css等)需选择性从新加载
  • 页面跳转:使用window.location.href = "./index.html"进行页面间的跳转;
  • 数据传递:可使用path?account="123"&password=""路径携带数据传递的方式,或者localstorage、cookie等存储方式

使用场景: 高要求的体验度,追求界面流畅的应用webpack

多页面的劣势

  • 重复代码较多
  • 页面常常须要切换,切换效果取决于浏览器和网络状况,对用户体验会有必定负面影响
  • 没法充分利用vue的路由和状态保持,在多个页面之间共享和同步数据状态会成为一个难题

hash 模式

这里的 hash 就是指 url 后的 # 号以及后面的字符。好比说 "www.baidu.com/#hashhash" ,其中 "#hashhash" 就是咱们指望的 hash 值。web

因为 hash 值的变化不会致使浏览器像服务器发送请求,并且 hash 的改变会触发 hashchange 事件,浏览器的前进后退也能对其进行控制,因此在 H5 的 history 模式出现以前,基本都是使用 hash 模式来实现前端路由。ajax

// 监听hash变化,点击浏览器的前进后退会触发
window.addEventListener('hashchange', function(event){ 
  let newURL = event.newURL; // hash 改变后的新 url
  let oldURL = event.oldURL; // hash 改变前的旧 url
},false)

下面实现一个路由对象算法

class HashRouter{
    constructor(){
        //用于存储不一样hash值对应的回调函数
        this.routers = {};
        window.addEventListener('hashchange',this.load.bind(this),false)
    }
    //用于注册每一个视图
    register(hash,callback = function(){}){
        this.routers[hash] = callback;
    }
    //用于注册首页
    registerIndex(callback = function(){}){
        this.routers['index'] = callback;
    }
    //用于处理视图未找到的状况
    registerNotFound(callback = function(){}){
        this.routers['404'] = callback;
    }
    //用于处理异常状况
    registerError(callback = function(){}){
        this.routers['error'] = callback;
    }
    //用于调用不一样视图的回调函数
    load(){
        let hash = location.hash.slice(1),
            handler;
        //没有hash 默认为首页
        if(!hash){
            handler = this.routers.index;
        }
        //未找到对应hash值
        else if(!this.routers.hasOwnProperty(hash)){
            handler = this.routers['404'] || function(){};
        }
        else{
            handler = this.routers[hash]
        }
        //执行注册的回调函数
        try{
            handler.apply(this);
        }catch(e){
            console.error(e);
            (this.routers['error'] || function(){}).call(this,e);
        }
    }
}

再来一个例子vue-cli

<body>
    <div id="nav">
        <a href="#/page1">page1</a>
        <a href="#/page2">page2</a>
        <a href="#/page3">page3</a>
        <a href="#/page4">page4</a>
        <a href="#/page5">page5</a>
    </div>
    <div id="container"></div>
</body>
let router = new HashRouter();
let container = document.getElementById('container');

//注册首页回调函数
router.registerIndex(()=> container.innerHTML = '我是首页');

//注册其余视图回到函数
router.register('/page1',()=> container.innerHTML = '我是page1');
router.register('/page2',()=> container.innerHTML = '我是page2');
router.register('/page3',()=> container.innerHTML = '我是page3');
router.register('/page4',()=> {throw new Error('抛出一个异常')});

//加载视图
router.load();
//注册未找到对应hash值时的回调
router.registerNotFound(()=>container.innerHTML = '页面未找到');
//注册出现异常时的回调
router.registerError((e)=>container.innerHTML = '页面异常,错误消息:<br>' + e.message);

history 模式

在 HTML5 以前,浏览器就已经有了 history 对象。但在早期的 history 中只能用于多页面的跳转:npm

history.go(-1);       // 后退一页
history.go(2);        // 前进两页
history.forward();     // 前进一页
history.back();      // 后退一页

在 HTML5 的规范中,history 新增了如下几个 API

history.pushState();         // 添加新的状态到历史状态栈
history.replaceState();      // 用新的状态代替当前状态
history.state                // 返回当前状态对象

因为 history.pushState() 和 history.replaceState() 能够改变 url 同时,不会刷新页面,因此在 HTML5 中的 histroy 具有了实现前端路由的能力。

对于单页应用的 history 模式而言,url 的改变只能由下面四种方式引发:

  • 点击浏览器的前进或后退按钮
  • 点击 a 标签
  • 在 JS 代码中触发 history.pushState 函数
  • 在 JS 代码中触发 history.replaceState 函数

下面实现一个路由对象

class HistoryRouter{
    constructor(){
        //用于存储不一样path值对应的回调函数
        this.routers = {};
        this.listenPopState();
        this.listenLink();
    }
    //监听popstate
    listenPopState(){
        window.addEventListener('popstate',(e)=>{
            let state = e.state || {},
                path = state.path || '';
            this.dealPathHandler(path)
        },false)
    }
    //全局监听A连接
    listenLink(){
        window.addEventListener('click',(e)=>{
            let dom = e.target;
            if(dom.tagName.toUpperCase() === 'A' && dom.getAttribute('href')){
                e.preventDefault()
                this.assign(dom.getAttribute('href'));
            }
        },false)
    }
    //用于首次进入页面时调用
    load(){
        let path = location.pathname;
        this.dealPathHandler(path)
    }
    //用于注册每一个视图
    register(path,callback = function(){}){
        this.routers[path] = callback;
    }
    //用于注册首页
    registerIndex(callback = function(){}){
        this.routers['/'] = callback;
    }
    //用于处理视图未找到的状况
    registerNotFound(callback = function(){}){
        this.routers['404'] = callback;
    }
    //用于处理异常状况
    registerError(callback = function(){}){
        this.routers['error'] = callback;
    }
    //跳转到path
    assign(path){
        history.pushState({path},null,path);
        this.dealPathHandler(path)
    }
    //替换为path
    replace(path){
        history.replaceState({path},null,path);
        this.dealPathHandler(path)
    }
    //通用处理 path 调用回调函数
    dealPathHandler(path){
        let handler;
        //没有对应path
        if(!this.routers.hasOwnProperty(path)){
            handler = this.routers['404'] || function(){};
        }
        //有对应path
        else{
            handler = this.routers[path];
        }
        try{
            handler.call(this)
        }catch(e){
            console.error(e);
            (this.routers['error'] || function(){}).call(this,e);
        }
    }
}

再来一个例子

<body>
    <div id="nav">
        <a href="/page1">page1</a>
        <a href="/page2">page2</a>
        <a href="/page3">page3</a>
        <a href="/page4">page4</a>
        <a href="/page5">page5</a>
        <button id="btn">page2</button>
    </div>
    <div id="container">

    </div>
</body>
let router = new HistoryRouter();
let container = document.getElementById('container');

//注册首页回调函数
router.registerIndex(() => container.innerHTML = '我是首页');

//注册其余视图回到函数
router.register('/page1', () => container.innerHTML = '我是page1');
router.register('/page2', () => container.innerHTML = '我是page2');
router.register('/page3', () => container.innerHTML = '我是page3');
router.register('/page4', () => {
    throw new Error('抛出一个异常')
});

document.getElementById('btn').onclick = () => router.assign('/page2')


//注册未找到对应path值时的回调
router.registerNotFound(() => container.innerHTML = '页面未找到');
//注册出现异常时的回调
router.registerError((e) => container.innerHTML = '页面异常,错误消息:<br>' + e.message);
//加载页面
router.load();

从零开始构建一个webpack项目
总结几个webpack打包优化的方法
总结前端性能优化的方法
几种常见的JS递归算法
搭建一个vue-cli的移动端H5开发模板
封装一个toast和dialog组件并发布到npm
一文读尽前端路由、后端路由、单页面应用、多页面应用
关于几个移动端软键盘的坑及其解决方案
浅谈JavaScript的防抖与节流

相关文章
相关标签/搜索