記一次小坑–關於window.open()

nikolausliu發表於2019-03-04

今天在公司的後臺專案中遇到一個這樣的需求:點選一個按鈕,傳送一個請求,然後用請求到的data中的url開啟一個新視窗(跳轉到另一個後臺)。看起來應該沒什麼問題,很快程式碼寫好了(vue專案,以下是虛擬碼,主要表達下思路):

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事件的方式模擬使用者行為,避開瀏覽器的攔截。

有了上面的思路,我把虛擬碼修改如下:

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有值了,就把新視窗的引用重定向。讓我們祈禱這次別出什麼么蛾子。程式碼修改如下:

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;
      // 先開啟一個空的新視窗,再請求
      this.newWin = 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節中查閱學習。

相關文章