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


    前端路由

    定义:在单页面应用,大部分页面结构不变,只改变部分内容的使用

    优点:用户体验好,不需要每次都从服务器全部获取,快速展现给用户

    缺点:使用浏览器的前进,后退键的时候会重新发送请求,没有合理地利用缓存。单页面无法记住之前滚动的位置,无法在前进,后退的时候记住滚动的位置

    后端路由

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

    前后端路由对比

    从性能和用户体验的层面来比较的话,后端路由每次访问一个新页面的时候都要向服务器发送请求,然后服务器再响应请求,这个过程肯定会有延迟。而前端路由在访问一个新页面的时候仅仅是变换了一下路径而已,没有了网络延迟,对于用户体验来说会有相当大的提升。     

    在某些场合中,用ajax请求,可以让页面无刷新,页面变了但Url没有变化,用户就不能复制到想要的地址,用前端路由做单页面网页就很好的解决了这个问题。但是前端路由使用浏览器的前进,后退键的时候会重新发送请求,没有合理地利用缓存。

    单页面的优势

    不存在页面切换问题,因为只在同一个页面间切换,会更流畅,而且可以附加各种动画和过度效果,用户体验更好。

    可以用到vue的路由和状态保持,不用担心切换造成的数据不同步。

    打包方便,有现成的脚手架可以用,也比较不容易出问题

    单页面的劣势

    所有逻辑和业务都在一个页面上,逻辑上不是很清楚,当业务变得复杂的时候改动起来就比较麻烦

    鸡蛋都在一个篮子里,只要一个地方出现错误,可能导致整个页面出错

    所有代码都在一个页面,首次加载耗时较长,页面体积较大

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

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

    多页面的优势

    逻辑清楚,各个页面按照功能和逻辑划分,不用担心业务复杂度

    单个页面体积较小,加载速度比较有保证

    多页面跳转需要刷新所有资源,每个公共资源(js、css等)需选择性重新加载

    页面跳转:使用window.location.href = "./index.html"进行页面间的跳转;

    数据传递:可以使用path?account="123"&password=""路径携带数据传递的方式,或者localstorage、cookie等存储方式

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

    多页面的劣势

    重复代码较多

    页面经常需要切换,切换效果取决于浏览器和网络情况,对用户体验会有一定负面影响

    无法充分利用vue的路由和状态保持,在多个页面之间共享和同步数据状态会成为一个难题

    hash 模式

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

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

    // 监听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);
            }
        }
    }

    再来一个例子

    <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);

    vi设计http://www.maiqicn.com 办公资源网站大全https://www.wode007.com

    history 模式

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

    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();
  • 相关阅读:
    不要在init和dealloc函数中使用accessor
    Xcode6.3真机测试无法选择目标机器问题
    Objective-C基础知识
    深入理解dispatch_sync
    AFNetworking 2.0教程
    使用pngcrush压缩png图片
    自定义custom Tab Bar
    CocoaPods 安装相关问题
    iOS 编程之使用Precompile Prefix Header
    Block传值
  • 原文地址:https://www.cnblogs.com/qianxiaox/p/13767031.html
Copyright © 2020-2023  润新知