0%

记一次小坑--关于window.open()

今天在公司的后台项目中遇到一个这样的需求:点击一个按钮,发送一个请求,然后用请求到的data中的url打开一个新窗口(跳转到另一个后台)。看起来应该没什么问题,很快代码写好了(vue项目,以下是伪代码,主要表达下思路):

1
2
3
4
5
6
7
8
9
10
11
clickHandle() {
api.get('xxx', params).then(response => {
let data = response.data;
if(data.code === 0 ) {
let url = data.data.url || '';
if (url) {
window.open(url);
}
}
});
}

愉快地测试下,发现并没有弹出新窗口,短促的慌乱之后,发现是浏览器拦截了新窗口的打开。。。

搜索引擎告诉我,非用户行为导致的打开新窗口可能会被部分浏览器拦截(在谷歌浏览器中,它的表现形式是在地址栏的末尾有一个通知图标,点击通知可以选择允许还是继续拦截)。搜索引擎还说,可以用手动触发dom事件的方式模拟用户行为,避开浏览器的拦截。

有了上面的思路,我把伪代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
clickHandle() {
api.get('xxx', params).then(response => {
let data = response.data;
if(data.code === 0 ) {
let url = data.data.url || '';
if (url) {
// window.open(url);
// 在dom中添加a标签-注册点击事件打开新窗口-触发点击事件-从dom中移除a标签
let a = document.createElement('a');
a.id = 'temp';
document.body.appendChild(a);
a.addEventListener('click', function(){
window.open(url);
});
a.click();
document.body.removeChild(a);
}
}
});
}

很好,再次愉快地测试下,然而,浏览器还是拦截了。。。

笑容逐渐淫荡。笑容逐渐凝固。摔。

搜索引擎再次告诉我,只要是在异步回调里执行的window.open()都会被拦截

好,整理下思路:这次我们不在异步回调里window.open()了。点击按钮先打开新窗口,把新窗口的引用保存在data里。请求拿到url后,把url也保存在data里。然后我们watch一下url,url有值了,就把新窗口的引用重定向。让我们祈祷这次别出什么幺蛾子。代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
export default {
data() {
return {
url: '',
newWin: null // 新窗口的引用
}
},
watch: {
url(newVal, oldVal) {
if(newVal && this.newWin) {
this.newWin.location.href = newVal;
// 重定向后把url和newWin重置
this.url = '';
this.newWin = null;
}
}
},
methods: {
clickHandle() {
let _this = this;
// 先打开一个空的新窗口,再请求
window.open();
api.get('xxx', params).then(response => {
let data = response.data;
if(data.code === 0 ) {
_this.url = data.data.url || '';
}
});
}
}
}

谨慎地测试一下,这次它打开了,它真的打开了。不能说很高兴吧,一般高兴。

然而下一秒,我发现我高兴早了:窗口是打开了,可是新窗口中的页面一直在加载动画的状态。我以前觉得这个加载动画挺好看的,就是一个缺了一块扇形的大圆不断地吃着一个个排成一线的小圆的动画,现在我觉得它吃的好像有点多了。

笑容逐渐变态。算了,不笑了。

这次没用搜索引擎,发现上面那个问题,是因为打开的新窗口会保留旧窗口的sessionStorage(至于为什么sessionStorage会阻止我那个新页面的加载就不在这篇文章的讨论范围之内了)。知道原因后,解决方法就简单了,在重定向之前,把新窗口的sessionStorage清空就好了。也就是在上面代码的这行代码this.newWin.location.href = newVal;之前加上一行this.newWin.sessionStorage.clear()就好了。代码就不放了。

这一次的测试没出什么问题。

总结

浏览器会拦截新窗口的打开也是为了保护用户体验,可以屏蔽一些广告。需要注意的是新窗口的引用newWin其实就是一个新的window对象,自然能使用上面的清除sessionStorage的方法。更多的关于window对象的知识,像是新窗口与打开它的窗口之间的通信等,可以在《Javascript高级程序设计》的第8章第1节中查阅学习。