资讯专栏INFORMATION COLUMN

从了解Hash和Html5 History 到简单实现路由

Prasanta / 1377人阅读

摘要:原因在于将状态对象保存在用户的磁盘上,以便在用户重启浏览器时使用,我们规定了状态对象在序列化表示后有的大小限制。新不必须为绝对路径。新必须与当前同源,否则会抛出一个异常。注意绝对不会触发事件,即使新的与旧的仅哈希不同也是如此。

Hash

hash 属性是一个可读可写的字符串,该字符串是 URL 的锚部分(从 # 号开始的部分),在页面中的hash有多种功能意义:

锚点
url: http://www.example.com/index.html#jump
dom:  或者 

浏览器读取到hash之后自动滚动到该对应元素所在位置的可视区域内

不附加在请求上

意味着它不管怎么变化都不会影响请求URL,即它只针对浏览器的.

浏览器: http://www.example.com/index.html#jump
服务器: http://www.example.com/index.html

注意: 有种情况是你会在URL上带#符号,但是你本意不是作为hash使用的,例如回调地址或者传参之类,这时候浏览器只会当做hash处理,所以需要先转码.

// 未转码
浏览器: http://www.example.com/index.html?test=#123
服务器: http://www.example.com/index.html?test=

// 转码
浏览器: http://www.example.com/index.html?test=%23123
服务器: http://www.example.com/index.html?test=%23123
改变访问历史但不会触发页面刷新

这个大家都知道,尽管它不会跳转也不会刷新,但是你能通过点击浏览器前进后退发现它也会被添加去访问历史记录里.(低版本IE不考虑)

缺点

搜索引擎不友好

难以追踪用户行为

思路

当URL的片段标识符更改时,将触发hashchange事件 (跟在#符号后面的URL部分,包括#符号),然后根据hash值做些路由跳转处理的操作.具体参数可以访问location查看

http://www.example.com/index.html#jump

最基本的路由实现方法监听事件根据location.hash判断界面



  
    
    
    
    Document
  
  
    
    

具体代码可以查看hash_demo.html

History

DOM window 对象通过 history 对象提供了对浏览器的会话历史的访问。它暴露了很多有用的方法和属性,允许你在用户浏览历史中向前和向后跳转

向前和向后跳转
window.history.back();
window.history.forward();
跳转到 history 中指定的一个点

你可以用 go() 方法载入到会话历史中的某一特定页面, 通过与当前页面相对位置来标志 (当前页面的相对位置标志为0).

window.history.go();
添加历史记录中的条目

不会立即加载页面的情况下改变了当前URL地址,往历史记录添加一条条目,除非刷新页面等操作

history.pushState(state, title , URL);

状态对象

state是一个JavaScript对象,popstate事件的state属性包含该历史记录条目状态对象的副本。

状态对象可以是能被序列化的任何东西。原因在于Firefox将状态对象保存在用户的磁盘上,以便在用户重启浏览器时使用,我们规定了状态对象在序列化表示后有640k的大小限制。如果你给 pushState() 方法传了一个序列化后大于640k的状态对象,该方法会抛出异常。如果你需要更大的空间,建议使用 sessionStorage 以及 localStorage.

标题

Firefox 目前忽略这个参数,但未来可能会用到。在此处传一个空字符串应该可以安全的防范未来这个方法的更改。或者,你可以为跳转的state传递一个短标题。

URL

新的历史URL记录。新URL不必须为绝对路径。如果新URL是相对路径,那么它将被作为相对于当前URL处理。新URL必须与当前URL同源,否则 pushState() 会抛出一个异常。该参数是可选的,缺省为当前URL。

注意: pushState() 绝对不会触发 hashchange 事件,即使新的URL与旧的URL仅哈希不同也是如此。

更改历史记录中的当前条目

不会立即加载页面的情况下改变了当前URL地址,并改变历史记录的当前条目,除非刷新页面等操作

history.replaceState(state, title , URL);
popstate 事件

每当活动的历史记录项发生变化时, popstate 事件都会被传递给window对象。如果当前活动的历史记录项是被 pushState 创建的,或者是由 replaceState 改变的,那么 popstate 事件的状态属性 state 会包含一个当前历史记录状态对象的拷贝。

获取当前状态

页面加载时,或许会有个非null的状态对象。这是有可能发生的,举个例子,假如页面(通过pushState() 或 replaceState() 方法)设置了状态对象而后用户重启了浏览器。那么当页面重新加载时,页面会接收一个onload事件,但没有 popstate 事件。然而,假如你读取了history.state属性,你将会得到如同popstate 被触发时能得到的状态对象。

你可以读取当前历史记录项的状态对象state,而不必等待popstate 事件

思路

监听点击事件禁止默认跳转操作,手动利用history实现一套跳转逻辑,根据location.pathname渲染界面.



  
    
    
    
    Document
  
  
    
    

具体代码可以查看html5_demo.html
注意,该方法不支持本地运行,只能线上运作或者启动服务器查看效果

html5_demo.html:26 Uncaught DOMException: Failed to execute "pushState" on "History": A history state object with URL "file:///C:/b" cannot be created in a document with origin "null" and URL "file:///C:/work/project/router_demo/src/html5_demo.html".
    at HTMLAnchorElement. (file:///C:/work/project/router_demo/src/html5_demo.html:26:15)
(anonymous) @ html5_demo.html:26
简单封装路由库 API

基本的路由方法:

router.push(url, onComplete)

router.replace(url, onComplete)

router.go(n)

router.back()

router.stop()



  
    router
  

  
    
  • console.log("push a"))">push a
  • console.log("push b"))">push b
  • console.log("replace c"))">replace c
  • go
  • back
  • stop
初始化
import Router from "../router"

window.router = new Router("view", {
  routes: [
    {
      path: "/a",
      component: "

a

" }, { path: "/b", component: "

b

" }, { path: "/c", component: "

c

" }, { path: "*", redirect: "/index" } ] }, "hash")// 或者"html5"
router类
import HashHstory from "./HashHistory";
import Html5History from "./Html5History";

export default class Router {
  constructor(wrapper, options, mode = "hash") {
    this._wrapper = document.querySelector(`#${wrapper}`)
    if (!this._wrapper) {
      throw new Error(`你需要提供一个容器元素插入`)
    }
    // 是否支持HTML5 History 模式
    this._supportsReplaceState = window.history && typeof window.history.replaceState === "function"
    // 匹配路径
    this._cache = {}
    // 默认路由
    this._defaultRouter = options.routes[0].path
    this.route(options.routes)
    // 启用模式
    this._history = (mode !== "hash" && this._supportsReplaceState) ? new Html5History(this, options) : new HashHstory(this, options)
  }

  // 添加路由
  route(routes) {
    routes.forEach(item => this._cache[item.path] = item.component)
  }

  // 原生浏览器前进
  go(n = 1) {
    window.history.go(n)
  }

  // 原生浏览器后退
  back(n = -1) {
    window.history.go(n)
  }

  // 增加
  push(url, onComplete) {
    this._history.push(url, onComplete)
  }

  // 替换
  replace(url, onComplete) {
    this._history.replace(url, onComplete)
  }

  // 移除事件
  stop() {
    this._history.stop()
  }
}
Hash Class
export default class HashHistory {
  constructor(router, options) {
    this.router = router
    this.onComplete = null
    // 监听事件
    window.addEventListener("load", this.onChange)
    window.addEventListener("hashchange", this.onChange)
  }

  onChange = () => {
    // 匹配失败重定向
    if (!location.hash || !this.router._cache[location.hash.slice(1)]) {
      window.location.hash = this.router._defaultRouter
    } else {
      // 渲染视图
      this.router._wrapper.innerHTML = this.router._cache[location.hash.slice(1)]
      this.onComplete && this.onComplete() && (this.onComplete = null)
    }
  }

  push(url, onComplete) {
    window.location.hash = `${url}`
    onComplete && (this.onComplete = onComplete)
  }

  replace(url, onComplete) {
    // 优雅降级
    if (this.router._supportsReplaceState) {
      window.location.hash = `${url}`
      window.history.replaceState(null, null, `${window.location.origin}#${url}`)
    } else {
      // 需要先看看当前URL是否已经有hash值
      const href = location.href
      const index = href.indexOf("#")
      url = index > 0
        ? `${href.slice(0, index)}#${url}`
        : `${href}#${url}`
      // 域名不变的情况下不会刷新页面
      window.location.replace(url)
    }

    onComplete && (this.onComplete = onComplete)
  }

  // 移除事件
  stop() {
    window.removeEventListener("load", this.onChange)
    window.removeEventListener("hashchange", this.onChange)
  }
}
HTML5 Class
export default class Html5Hstory {
  constructor(router, options) {
    this.addEvent()
    this.router = router
    this.onComplete = null
    // 监听事件
    window.addEventListener("popstate", this.onChange)
    window.addEventListener("load", this.onChange)
    window.addEventListener("replaceState", this.onChange);
    window.addEventListener("pushState", this.onChange);
  }

  // pushState/replaceState不会触发popstate事件,所以我们需要自定义
  addEvent() {
    const listenWrapper = function (type) {
      const _func = history[type];
      return function () {
        const func = _func.apply(this, arguments);
        const e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return func;
      };
    };
    history.pushState = listenWrapper("pushState");
    history.replaceState = listenWrapper("replaceState");
  }

  onChange() {
    // 匹配失败重定向
    if (location.pathname === "/" || !this.router._cache[location.pathname]) {
      window.history.pushState(null, "", `${window.location.origin}${this.router._defaultRouter}`);
    } else {
      // 渲染视图
      this.router._wrapper.innerHTML = this.router._cache[location.pathname]
      this.onComplete && this.onComplete() && (this.onComplete = null)
    }
  }

  push(url, onComplete) {
    window.history.pushState(null, "", `${window.location.origin}${url}`);
    onComplete && (this.onComplete = onComplete)
  }

  replace(url, onComplete) {
    window.history.replaceState(null, null, `${window.location.origin}${url}`)
    onComplete && (this.onComplete = onComplete)
  }

  // 移除事件
  stop() {
    window.removeEventListener("load", this.onChange)
    window.removeEventListener("popstate", this.onChange)
    window.removeEventListener("replaceState", this.onChange)
    window.removeEventListener("pushState", this.onChange)
  }
}

完整代码可以拷贝router_demo

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106464.html

相关文章

  • vue-router源码阅读学习

    摘要:我们在回到的构造函数中,往下看是模式的选择,一共这么几种模式一种和三种。把我们继续回到中,首先继承构造函数。表示信息,表示成功后的回调函数,表示失败的回调函数。是三种的实例对象,然后分情况进行操作,方法就是给赋值穿进去的回调函数。 如同分析vuex源码我们首先通过一个简单例子进行了解vue-router是如何使用的,然后在分析在源码中是如何实现的 示例 下面示例来自于example/b...

    Jason 评论0 收藏0
  • 【Vue.js 牛刀小试】:第十二章 - 使用 Vue Router 实现 Vue 中的前端路由控制

    摘要:而路由则是使用了中新增的事件和事件。总结这一章主要是介绍了如何使用在中构建我们的前端路由。 系列目录地址 一、基础知识概览 第一章 - 一些基础概念(posted at 2018-10-31) 第二章 - 常见的指令的使用(posted at 2018-11-01) 第三章 - 事件修饰符的使用(posted at 2018-11-02) 第四章 - 页面元素样式的设定(posted a...

    cpupro 评论0 收藏0
  • 简述vue-router实现原理

    摘要:源码解读阅读请关注下代码注释打个广告哪位大佬教我下怎么排版啊,不会弄菜单二级导航扑通是什么首先,你会从源码里面引入,然后再传入参数实例化一个路由对象源码基础类源码不选择模式会默认使用模式非浏览器环境默认环境根据参数选择三种模式的一种根据版 router源码解读 阅读请关注下代码注释 打个广告:哪位大佬教我下sf怎么排版啊,不会弄菜单二级导航(扑通.gif) showImg(https:...

    Cristalven 评论0 收藏0
  • 简述vue-router实现原理

    摘要:源码解读阅读请关注下代码注释打个广告哪位大佬教我下怎么排版啊,不会弄菜单二级导航扑通是什么首先,你会从源码里面引入,然后再传入参数实例化一个路由对象源码基础类源码不选择模式会默认使用模式非浏览器环境默认环境根据参数选择三种模式的一种根据版 router源码解读 阅读请关注下代码注释 打个广告:哪位大佬教我下sf怎么排版啊,不会弄菜单二级导航(扑通.gif) showImg(https:...

    Ajian 评论0 收藏0

发表评论

0条评论

Prasanta

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<