前端单页面应用路由实现原理

一、前端单页面路由本质

HTML5中对浏览器history对象方法进行了扩展,前端单页面应用的路由实现本质上都是基于history对象方法的封装实现用户url访问的存储记录与页面更新。

二、history对象新增方法

1.pushState(state,title,url)

该方法会在历史记录中新增一条记录,改变浏览器的url,但是不刷新页面

pushState方法接受三个参数,

  • state:一个与添加的记录相关联的状态对象,主要用于popstate事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填null
  • title:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。
  • url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
var stateObj={foo:'bar'}
history.pushState(starteObj,'','2.html')

添加新纪录后,浏览器的地址栏立刻显示`hello.com/2.html,但不会跳转到2.html,也不会检查2.html是否存在,它只是成为浏览历史中的最新记录

总之,pushState()方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应,使用该方法后,就可以使用history.state属性读出状态对象

var stateObj={foo:'bar'}
history.pushState(starteObj,'','2.html')
history.state //=> {foo:"bar"}

注意:如果pushState的URL参数设置了一个新的hash值,并不会触发hashchange事件。

2.replaceState(state,title,url)

replaceState方法的作用是替换当前的历史记录,其他的都与pushState()方法一模一样。

假定当前网页是example.com/example.html

history.pushState({page: 1}, 'title 1', '?page=1')
// URL 显示为 http://example.com/example.html?page=1

history.pushState({page: 2}, 'title 2', '?page=2');
// URL 显示为 http://example.com/example.html?page=2

history.replaceState({page: 3}, 'title 3', '?page=3');
// URL 显示为 http://example.com/example.html?page=3

history.back()
// URL 显示为 http://example.com/example.html?page=1

history.back()
// URL 显示为 http://example.com/example.html

history.go(2)
// URL 显示为 http://example.com/example.html?page=3

3.popState事件

popstate事件是window对象上的事件,配合pushState()和replaceState()方法使用。当同一个文档(可以理解为同一个网页,不能跳转,跳转了就不是同一个网页了)的浏览历史出现变化时,就会触发popstate事件。

上面我们说过,调用pushState()或者replaceState()方法都会改变当前的历史记录,仅仅调用pushState()方法或replaceState()方法 ,并不会触发该事件,另外一个条件是用户必须点击浏览器的倒退按钮或者前进按钮,或者使用js调用history.back()或者history.forward()等方法。

所以,记住popstate事件触发的条件

1. 处在同一个文档(同一个html页面)
2. 文档的浏览历史(即history对象)发生改变

只要符合这两个条件,popstate事件就会触发

具体例子:

//index.html
<head>
    <script>
        window.onpopstate=function(){
            alert('location '+document.location+',state '+JSON.stringify(event.state))
        }
    </script>
</head>
<body>
    <!--第二步 -->
    <button onclick="window.history.back()">后退</button>
    <button onclick="window.history.forward()">前进</button>
    <!--第一步 -->
    <button onclick="window.history.pushState(null,'','1.html')">pushState</button>    
</body>

先点击pushState按钮,在点击后退按钮,就会触发popstate事件

三、自定义方法记录用户访问轨迹

 前文已经说明,页面跳转时一定会触发调用pushState或replaceState方法,因此,用户访问信息的记录实现即通过对pushState和replaceState方法的拦截,在执行行为前执行拦截方法就能够采集到用户访问url信息。

// 改写思路:拷贝 window 默认的 replaceState 函数,重写 history.replaceState 在方法里插入我们的采集行为,在重写的 replaceState 方法最后调用,window 默认的 replaceState 方法

collect = {}

collect.onPushStateCallback : function(){}  // 自定义的采集方法

(function(history){
    var replaceState = history.replaceState;   // 存储原生 replaceState
    history.replaceState = function(state, param) {     // 改写 replaceState
       var url = arguments[2];
       if (typeof collect.onPushStateCallback == "function") {
             collect.onPushStateCallback({state: state, param: param, url: url});   //自定义的采集行为方法
       }
       return replaceState.apply(history, arguments);    // 调用原生的 replaceState
    };
 })(window.history);

四、使用history api实现简单的单页面路由

在介绍实例前先介绍下location对象,location对象提供了与当前窗口中加载的文档有关的信息。它包含以下属性:

属性名例子说明
hostwww.hello.com:8080返回服务器名称和端口号(如果有的话)
hostnamewww.hello.com返回服务器名称,不带端口号
hrefhttp://www.hello.com返回当前加载页面的完整url
pathname/user/ming返回url中的目录
hash#content返回url中的hash,如果没有返回空字符串
search?q=javascript返回Url的查询字符串,这个字符串以问号开头

我们在下方的示例中需要用到pathname属性拿到访问的路径

一个简单的history模式单页面路由实现如下:

//1. 路由规则
const routes={
    '/user':user, //user是引入的视图   import user from './view/user' 
    '/about':about
}
//2. 路由控制类
class Router {
  start() {
    // 点击浏览器后退/前进按钮时会触发window.onpopstate事件, 我们在这时切换到相应页面
    // https://developer.mozilla.org/en-US/docs/Web/Events/popstate
    window.addEventListener('popstate', () => {
      this.load(location.pathname)
    })

    // 打开页面时加载当前页面 在单页面入口文件中要调用start方法
    this.load(location.pathname)
  }

  // 前往path, 变更地址栏URL, 并加载相应页面
  go(path) {
    // 变更地址栏URL
    history.pushState({}, '', path)
    // 加载页面
    this.load(path)
  }

  // 加载path路径的页面
  load(path) {
    // 首页
    if (path === '/') path = '/foo'
    // 创建页面实例
    const view = new routes[path]()
    // 调用页面方法, 把页面加载到document.body中
    view.mount(document.body)
  }
}

Router类的作用是控制页面根据当前Url切换

  • start()

    • 作用1: 监听onpopstate事件,在浏览器前进或后退时加载相应的页面
    • 作用2: 打开页面时加载当前页面,需要在单页面的入口文件引入,并执行
  • go(path)

    • 跳转到path对应的页面
  • load(path)

    • 加载path路径的页面

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值