lxinr/interview-question

2021/02/09 - 前端路由的实现原理

lxinr opened this issue · 0 comments

lxinr commented

hash模式

hash值的改变不会向服务端发送请求,hash的改变会触发hashchange事件,浏览器的前进后退也能对其进行控制

基本实现:

export default class HashRouter {
  constructor() {
    // 使用一个map来保存路由信息
    this.routers = new Map()
    this._listener()
  }
  // 监听路由变动
  _listener() {
    const _handler = this.handler.bind(this)
    window.addEventListener('hashchange',_handler,false)
    // 模拟注册完成之后执行一次,用于index
    setTimeout(() => {
      _handler()
    }, 0)
  }
  // 注册路由
  register(hash = '', callback) {
    this.routers.set(hash, callback)
  }
  // 注册路由首页
  registerIndex(callback) {
    this.routers.set('index', callback)
  }
  // 注册404页
  registerNotFound(callback) {
    this.routers.set('404', callback)
  }
  // 用于执行回调函数
  handler() {
    const hash = location.hash.slice(1)
    let cb
    if(hash) {
      if(!this.routers.has(hash)) {
        cb = this.routers.get('404')
      } else {
        cb = this.routers.get(hash)
      }
    } else {
      cb = this.routers.get('index')
    }

    cb.call(this)
  }

}

测试:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>hash路由</title>
  <style>
    #app {
      margin: 15px 0;
      box-sizing: border-box;
      width: 100%;
      padding: 15px;
      border: 1px solid #696969;
      height: 200px;
    }
  </style>
</head>
<body>
  <header>
    <a href="#page1">page1</a>
    <a href="#page2">page2</a>
    <a href="#page3">page3</a>
    <a href="#page4">page4</a>
  </header>
  <section id="app">

  </section>
  <script type="module">
    import HashRouter from './hashRouter/router.mjs'
    
    window.addEventListener('load', () => {
      const router = new HashRouter()
      const app = document.getElementById('app')
      const routes = [
        { path: 'page1', cb: () =>  app.innerHTML = '这是page1'},
        { path: 'page2', cb: () =>  app.innerHTML = '这是page2'},
        { path: 'page3', cb: () =>  app.innerHTML = '这是page3'}
      ]

      routes.forEach(item => {
        router.register(item.path, item.cb)
      })

      router.registerIndex(() => app.innerHTML = '这是首页呀呀呀')
      router.registerNotFound(() => app.innerHTML = '找不到你要的页面')
    })
  </script>
</body>
</html>

history模式

history对象提供了对浏览器的会话历史的访问

  • 使用 back()(向后跳转), forward()(向前跳转)和 go()(指定跳转)方法来完成在用户历史记录中向后和向前的跳转

  • history.pushState()history.replaceState()可以改变 url,且不会刷新页面,其中history.pushState()为向现有历史记录中添加一条新的记录,history.replaceState()为替换当前页面的历史记录,且都会触发popstate事件

  • 通过监听popstate事件来获知history变动

// state - 合法的 Javascript 对象,可以用在 popstate 事件中
// title - 当前大多数浏览器都会忽略的参数,可用null代替
// url - 有效的url,新URL必须与当前URL同源,否则会有异常,用于更新浏览器的地址栏
history.pushState(state, title[, url])
history.replaceState(state, title[, url])