1. 问题重现:一个看似简单的需求,怎么就“坏”了?
最近在做一个Vue的后台管理系统,里面有个需求,需要在主页面里嵌入一个内嵌的页面,也就是我们常说的iframe。这个iframe的内容不是固定的,需要根据用户点击不同的菜单或者按钮,动态地切换它里面显示的内容。听起来是不是挺简单的?我当时也是这么想的,不就是改一下iframe标签的src属性嘛,v-bind一下,一个响应式数据就搞定了。
于是我就写出了类似这样的代码:
<template>
<div>
<button @click="loadPageA">加载页面A</button>
<button @click="loadPageB">加载页面B</button>
<iframe ref="myIframe" :src="https://blog.csdn.net/iframeUrl" width="100%" height="600px"></iframe>
</div>
</template>
<script>
export default {
data() {
return {
iframeUrl: ''
}
},
methods: {
loadPageA() {
this.iframeUrl = 'https://example.com/page-a';
},
loadPageB() {
this.iframeUrl = 'https://example.com/page-b';
}
}
}
</script>
功能上线初期,一切正常。用户点按钮,iframe内容唰一下就变了,美滋滋。但没过多久,测试同事和用户就开始反馈一个诡异的问题:在切换了几次iframe内容后,点击浏览器的后退按钮,或者调用Vue Router的$router.go(-1),页面并没有回到切换iframe之前的状态,而是直接跳出了当前页面,或者行为完全不可预测。
这可就麻烦了。用户的使用路径被打乱了,体验非常差。我一开始还以为是Vue Router的配置问题,或者是组件生命周期哪里没处理好,排查了一圈,最后才发现“罪魁祸首”就是这个我亲手写的、看起来人畜无害的动态src绑定。
1.1 根源剖析:为什么动态修改src会“污染”历史记录?
要理解这个bug,我们得先抛开Vue,看看浏览器最基础的行为。当我们创建一个iframe并设置其src时,浏览器会向这个地址发起请求,加载页面。这里有一个关键细节:如果这个src指向的地址与父页面(也就是你的Vue应用)同源,那么这次加载行为,会被浏览器视为一次页面导航。
什么叫“同源”?简单说,就是协议(http/https)、域名、端口号完全一致。比如你的Vue应用跑在https://admin.myapp.com,你的iframe加载的是https://admin.myapp.com/some-page,这就是同源。
浏览器有个机制,对于同源的iframe,每次通过修改iframe.src或者iframe.contentWindow.location.href来加载新页面,都会在浏览器全局的window.history对象中新增一条历史记录。注意,是全局的,不是你Vue应用内部路由的那个历史记录栈。
想象一下这个场景:
- 你进入Vue应用的
/home页面。(历史记录:[home]) - 你点击按钮,把iframe的src从空改为
/page-a。(历史记录变为:[home, /page-a]) - 你再点击按钮,把iframe的src改为
/page-b。(历史记录变为:[home, /page-a, /page-b])
此时,你调用this.$router.go(-1),Vue Router期望的是在自己的路由栈里后退一步。但浏览器看到的历史记录栈顶是/page-b,它执行的是全局后退,于是iframe的内容可能变回了/page-a,而你的Vue路由可能根本没动,或者发生了更混乱的跳转。这就是路由“失控”和“混乱”的根本原因——两套历史记录系统(Vue Router的和浏览器全局的)打架了。
2. 解决方案:location.replace 的妙用
知道了病因,开药方就有的放矢了。我们的核心目标很明确:在动态改变同源iframe内容时,阻止浏览器新增历史记录。
这时候,location.replace()方法就该登场了。它和直接赋值src或location.href最大的区别在于:replace()会用新的页面替换当前历史记录条目,而不是新增一条。
把它应用到iframe里,就是操作iframe内部窗口的location对象:
this.$refs.myIframe.contentWindow.location.replace(newUrl);
这个方法会加载新的URL,但当前iframe在浏览器历史记录中的位置被新页面“覆盖”了。这样一来,无论你在同一个ifram

362

被折叠的 条评论
为什么被折叠?



