|
深入浅出浏览器的history对象
深入浅出浏览器的history对象
郭键涛@贝壳找房
贝壳产品技术
贝壳产品技术 “贝壳产品技术公众号”作为贝壳官方产品技术号,致力打造贝壳产品、技术干货分享平台,面向互联网/O2O开发/产品从业者,每周推送优质产品技术文章、技术沙龙活动及招聘信息等。欢迎大家关注我们。 242篇内容
2021年03月06日 00:20
1 history简介History 对象包含用户(在浏览器窗口中)访问过的 URL,它是 window 对象的一部分,可通过 window.history 属性对其进行访问。history对象在前端应用中至关重要,所有单页应用的路由都是基于history对象。2 导读本文会先简单介绍history对象的一些属性,然后会重点介绍history对象的一些实际应用,以此来帮助我们加深对history对象的理解。3 属性介绍 图1:history的属性上图是我在控制台打印的history对象,下面我们简单介绍一下这些属性。3.1 属性值length:返回浏览器历史列表中的 URL 数量。scrollRestoration: 滚动恢复属性允许web应用程序在历史导航上显式地设置默认滚动恢复行为。该属性有两个可选值,默认为auto,将恢复用户已滚动到的页面上的位置。另一个值为:manual,不还原页上的位置,用户必须手动滚动到该位置。state:返回一个表示历史堆栈顶部的状态的值,这是一种可以不必等待popstate事件而查看状态的方式。3.2 方法history.pushState(object, title, url)方法接受三个参数,object 为随着状态保存的一个对象,title为新页面的,url为新的网址。replaceState(object, title, url) 与pushState的唯一区别在于该方法是替换掉history栈顶元素。history.go(x) 去到对应的url历史记录。history.back() 相当于浏览器的后退按钮。history.forward() 相当于浏览器的前进按钮。3.3 事件popstate事件:popstate事件会在以下的情况触发:同一个文档的浏览历史发生变化时触发。调用history.pushState()和history.replaceState()方法不会触发。而用户点击浏览器的前进/后退按钮时会触发,调用history对象的back()、forward()、go()方法时,也会触发。popstate事件的回调函数的参数为event对象,该对象的state属性为随状态保存的那个对象。3.4 理解3.4.1 问题介绍了history对象,我们先抛出几个小问题:history对象可变吗?history.length既然代表浏览器历史列表中的URL数量,那么这个数量可以无限多吗?location.href与history.pushState有什么区别?如果我从A域名跳转到了B域名,那么history.back()会回到哪里?popstate事件的触发条件是什么?3.4.2 解答下面我们来依次解答这几个问题,初步加深对history对象的理解。问题1:history对象可变吗?探索图2:给history赋值我们给history赋值为空对象,然后打印一下history,可以看到history不为空对象。结论window.history对象是不可变的。问题2:history.length既然代表浏览器历史列表中的URL数量,那么这个数量可以无限多吗?探索图3:探索history.length我们首先打印出history.length,发现结果为3;然后我们添加100条记录,再次打印history.length,发现值为50。结论history.length并不会无限大。问题3:location.href与history.pushState有什么区别?探索图4:打印history.length我们以百度h5页面来举例,首先我们进入http:www.baidu.com,同时打印一下history对象,length为2。图5:知乎页面图6:打印history.length接下来我们使用location.href = 'https://www.zhihu.com'来进行跳转,发现页面跳转到了知乎,此时我们再打印一下history,发现length变为了3。图7:百度h5页面图8:打印history.length此时我们点击浏览器的返回,再次回到百度h5页面,打印一下history,依然为3。图9:pushState跳转其他域名此时我们使用history.pushState(null, ' ', https://www.zhihu.com'),发现抛出一个错误,意思就是pushState是不能用来在不同域名之间跳转的。图10:pushState跳转当前域名图11:百度h5页面接下来我们使用history.pushState(null, ' ', /a'),发现页面的url后面添加了一个'/a'路径,但是观察控制台,发现并没有往服务器再发送任何请求。图12:location.href跳转图13:跳转后效果我们再使用一下location.href = '/a',发现浏览器再次发起了文档请求,页面变为了Not Found。结论使用location.href跳转后页面会发起新的文档请求,而history.pushState不会。location.href可以跳转到其他域名,而history不能。location.href与history都会往历史列表中添加一条记录。问题4:如果我从A域名跳转到了B域名,那么history.back()会回到哪里?探索图14:百度h5页面还是以百度h5页面为例图15:location.href跳转知乎我们使用location.href = 'https://www.zhihu.com'进行跳转图16:history.back回退图17:百度页面接着,使用history.back()方法,页面又回到了百度页面。结论从A域名跳转到了B域名,那么调用history.back()会回到A域名。问题5:popstate事件的触发条件是什么?探索图18:监听popstate事件首先我们监听一下popstate事件,然后我依次调用了location.href,location.hash,history.go,history.back,history.forward,history.pushState,history.replaceState方法,得出如下结论。结论因为location.href是刷新式的跳转,所以这个打印信息是肯定打印不出来的,在刷新的时候这个监听函数就已经失效了,所以这里不讨论location.href会不会触发popstate事件。跟location.href类似的还有history.go(0),因为history.go(0)也会直接刷新页面,所以这个监听函数也会失效,也不会打印出信息。location.hash是会触发popstate事件的,同样会触发popstate的还有history.back,history.forward,history.go。history.pushState,history.replaceState都不会触发popstate事件。4 应用通过以上几个问题,我们初步了解了history对象,下面我们来看一下它的一些实际应用。4.1 单页应用history最常见的使用就是搭建前端单页应用。使用history.pushState方法可以改变地址栏的路径而不用刷新页面,所以这使得我们只需要在第一次进入页面的时候去请求一次html,后续的页面呈现则交由js来控制,根据不同url路径来加载不同的js模块。使用history路由需要注意的是服务器需要做好处理 URL 的准备,因为当用户在url为'/a/b/c'的页面进行刷新操作,服务器很有可能会因为匹配不到路径而返回404状态码,应当对这样的路径也都返回html文件。4.2 交互操作问题另一类比较常见的,就是一些交互实现类。比如说以下交互:1.在创建/编辑页面,用户修改了表单以后,如果退出的时候,给出二次弹窗确认。2.在移动端的列表页,点击筛选框会弹出一个浮层,当用户点击app的后退按钮时,把浮层关闭掉,而不是回退页面。3.当前处在页面A,点击跳转到页面B,由页面B内请求发现当前用户无权限,于是跳转到错误页C,如何避免用户在C页面点击浏览器的回退按钮再次回到B页面。解答分析交互1与交互2是同一类问题,原理都是点击浏览器的前进与后退按钮都会触发popstate事件,监听这个popstate事件,一旦触发,便给出一个弹窗。需要注意的是,当popstate事件触发的时候,历史地址记录就已经被回退了,我们无法阻止这个回退,所以在回退之前,我们需要使用history.pushState(null,null,document.URL)方法去主动再添加一条当前url的记录,当popstate事件触发的时候,虽然回退了一条记录,但是url并不会改变,也就达到了停留在当前页面的目的。关于交互3,我们要学会使用history.replace方法,如果我们一直使用pushState或者location.href进行跳转的话,那么此时历史记录是这样的A—B—C,但是如果我们从B到C跳转的时候使用history.replace的话,B记录就会被替换为C记录,那么历史记录就会变为A—C,此时从C页面点击返回按钮就可以直接返回A页面。实例下面我给出一个点击浏览器的后按钮后弹窗的效果,供大家参考。还是以百度h5页面举例,在'/a'页面,我点击返回的时候,会弹出禁止返回的弹窗。图19:弹窗提示具体代码如下,可在控制台使用:history.pushState(null,null,'/a')window.addEventListener('popstate',()=>{alert('禁止返回')})history.pushState(null,null,document.URL)4.3 各种路由框架的基础路由框架通常都有三种模式:browserHistory,hashHistory,memoryHistory,其中browserHistory的实现就是依赖于window.history对象,下面我们先来想两个问题,然后接着来实现一个简单的前端单页路由。问题用window.history.pushState和路由框架的pushState有什么区别?既然使用history.pushState无法触发popstate事件,那么路由框架又是如何在pushState的时候加载不同组件的呢?为什么使用pushState跳转以后,history对象的state里都有一个属性key?解答下面咱们来分析一下这几个问题。实验图20:掘金前端板块首先我们掘金的首页,点击前端板块,发现在进入'/frontend'路径时,并没有发送html请求,说明这是一个单页应用,下面我们再返回首页,使用history.pushState(null, null, '/frontend')来进入前端板块,看看会发生什么。图21:pushState以后的页面可以看到,此时url已经变了,但是页面并没有渲染出前端模块:图22:vue-router-push函数我们顺势来看一看vue-router的源码,我们可以看到它调用了一个pushState函数,我们来看看这个函数:图23:vue-router-pushState函数并没有看出什么特别的地方,这儿的pushState就是调用了history.pushState函数。不过从这里我们看出了问题3的答案,vue-router在使用push函数的时候调用了history.pushState方法,而这里在使用history.pushState函数时往里面加了一个key。图24:key属性我们可以看到这个key的值就是一个时间,有什么特殊含义吗?后来查阅官方文档,得出了这样的解释:当一个 history 通过应用程序的 push 或 replace 跳转时,它可以在新的 location 中存储 “location state” 而不显示在 URL 中,这就像是在一个 HTML 中 post 的表单数据。在 DOM API 中,这些 hash history 通过 window.location.hash = newHash 很简单地被用于跳转,且不用存储它们的location state。但我们想全部的 history 都能够使用location state,因此我们要为每一个 location 创建一个唯一的 key,并把它们的状态存储在 session storage 中。当访客点击“后退”和“前进”时,我们就会有一个机制去恢复这些 location state。我们再回到之前的问题一与问题二,既然这个pushState没有什么特别的,我们再来看一看这个transitionTo函数。图25:vue-router-transitionTo函数我发现了这段代码,这里调用了该路由的回调函数。众所周知,我们注册一个路由一般是采用这种形式router.route('/111', state => { contentDOM.innerHTML = '111';});这里就是执行了state => { contentDOM.innerHTML = '111'; }这个回调函数,所以问题就清楚了,路由框架的pushState不仅调用了history.pushState方法,还调用了该路由对应的回调函数来渲染了对应的组件。结论所以我们得出结论,路由框架的pushState与history.pushState是不一样的,路由框架的pushState不仅调用了history.pushState改变了url,更重要的是它还多了一步操作,即根据这个url销毁了旧组件,渲染了新组件;至于state里面的key值,则是为了兼容hashHistory。前端路由demo下面我们来实现一个前端路由的demo,现在已经有一个html,我们需要为它写一个Router,实现如下效果:图26:前端路由demoABCD简单分析一下:首先发布订阅模式肯定少不了,注册路由的时候,需要将每个路由所对应的回调函数存储起来,在路由变化的时候执行对应的回调函数。只监听popSate是不够的,页面初始化的时候,以及pushState的时候,都需要执行对应的回调函数去主动更新一下组件。还有一个问题,就是需要阻止这几个a标签的默认事件。经过以上对history的理解,这个简单的Router已经不难实现了,下面直接给出完整代码:ABCD5 总结本文首先介绍了history对象的各个属性,然后介绍了它的一些应用,希望本文能在实际工作中对大家有所帮助。在前端路由这块儿除了window.history以外,其他知识点以及相关应用还有很多。对于location对象、搭建多页应用等其他知识,大家感兴趣的话可以去深入探究。6 参考jqhtml.com: 单页应用的部署方案https://www.jqhtml.com/43510.html掘金: 性能 & 集成 —— History APIhttps://juejin.cn/post/6844903773266001933#heading-4react-router: react-router文档http://react-guide.github.io/react-router-cn/docs/guides/basics/Histories.html#hashHistoryvue: vue源码https://github.com/vuejs/vue-router/blob/ca80c4442c85329b950de483a596aae0d91e7ca8/dist/vue-router.js#L2190MDN: history对象https://developer.mozilla.org/zh-CN/docs/Web/API/History
预览时标签不可点
FE33大前端69FE · 目录#FE上一篇手把手教你使用dumi快速搭建组件库下一篇tapable 源码解读关闭更多小程序广告搜索「undefined」网络结果
|
|