JavaScript中有没有办法识别浏览器标签页?

65

我需要能够在浏览器中识别当前所在的标签页。浏览器是否提供了某些信息来确定当前标签页呢?我不需要了解其他标签页的任何信息,只需要获取当前标签页的标识符。它可以是一个随机或序列号码,也可以是日期时间戳,只要在该标签页存在期间保持相同即可。

我有一个客户端应用程序,通过HTTP连接到远程服务器进行BOSH,并且如果我在多个标签页中打开它,则每个实例都需要其自己的唯一ID,否则应用程序将失败。因此,我只需要与标签页关联的某些唯一编号,只要该标签页存在,该编号就会始终存在(例如在提供此应用程序的网站上导航时刷新页面)。这似乎是一个显而易见的、仅需在浏览器中使用的解决方案,例如window.tabId - 这就是我需要的。我因为无法解决这个看似简单的问题而受到严重的开发阻碍,但这个解决方案似乎不存在。 必须有一种方法(跨浏览器的解决方案)。

有什么想法吗?

12个回答

67

SessionStorage是按标签页/窗口存储的,因此您可以在sessionStorage中定义一个随机数,并在第一次使用时获取它(如果存在):

var tabID = sessionStorage.tabID ? 
            sessionStorage.tabID : 
            sessionStorage.tabID = Math.random();

更新:
在某些情况下,您可能会在多个标签页中拥有相同的 sessionStorage (例如当您复制标签页时)。在这种情况下,以下代码可能会有所帮助:

var tabID = sessionStorage.tabID && 
            sessionStorage.closedLastTab !== '2' ? 
            sessionStorage.tabID : 
            sessionStorage.tabID = Math.random();
sessionStorage.closedLastTab = '2';
$(window).on('unload beforeunload', function() {
      sessionStorage.closedLastTab = '1';
});

2
这段代码非常有帮助,但是当你复制窗口时,数据存储会持续存在。 - Kemen Paulos Plaza
1
会话存储在IE浏览器中无法持久化。 - Brian Cannard
6
随机选择数字并不保证它们是唯一的(即在0到99之间随机选择一个数字,并且可能始终选择11倍数)。为什么不在localStorage中使用序列号呢? - Max Waterman
1
@MaxWaterman,是的,随机数可能会发生冲突,但是Math.random返回双精度数字,其大小为64位。所以情况并不那么糟糕。在一百万次运行中,由Math.random生成的两个重复数字的概率约为1%。请查看此答案https://dev59.com/cYfca4cB1Zd3GeqPhVXJ#28220928。而且在新版本的浏览器中,`Math.random`的质量似乎更好。例如,在Chrome中内部使用的是128位发生器(https://v8.dev/blog/math-random)。(不确定它对碰撞的概率有多大影响。) - shitpoet
4
对于那些担心重复ID的人 - 只需为随机生成的ID使用基于时间的前缀即可。 - Shlomi Hassid
显示剩余2条评论

15

您需要使用HTML5,但是结合随机 GUID 的 sessionStorage 似乎是您想要的。


1
数字即使是随机选择的,也有可能出现两次,对吧?因此有可能两个标签页上的数字相同。我认为在localStorage中使用序列号会更好-当您在新标签页中打开页面时,递增该数字并将其存储在sessionStorage中。 - Max Waterman
9
不同的浏览器、私密标签、时间更改等因素,使得序列号不再比随机数更可靠。有各种方法可以创建一个 GUID(全局唯一标识符),使其尽可能地可靠 —— 发生冲突的概率非常小,不值得担心。 - jmoreno
@MaxWaterman 关于64位长数字,你可以放心地忘记碰撞的问题。甚至加密哈希算法也不使用超过128位的标识符,原因就是为了防止由随机数碰撞引起的错误。 - peterh

14

以下是一种在页面加载后仍然可用的方式来获取tabId,即使刷新后也是同一个tabId

这也解决了重复标签的问题,因为tabIdsessionStorage中清除了。

window.addEventListener("beforeunload", function (e)
{
    window.sessionStorage.tabId = window.tabId;

    return null;
});

window.addEventListener("load", function (e)
{
    if (window.sessionStorage.tabId)
    {
        window.tabId = window.sessionStorage.tabId;
        window.sessionStorage.removeItem("tabId");
    }
    else
    {
        window.tabId = Math.floor(Math.random() * 1000000);
    }

    return null;
});

通过使用 window.tabId 访问 tabId

beforeunload 事件是必需的,以便将 tabId 持久化,以便在刷新后具有相同的 ID。

load 事件需要执行以下操作:

  1. 生成初始的 tabId
  2. 在刷新后加载相同的 tabId(如果存在)。

* 我不知道为什么应该执行 return null 或者根本不返回。只是读到不返回可能会导致函数无法工作 :(


等待加载事件是必要的吗?为什么不在脚本执行时同步设置tabId?是否有一些浏览器不会在重复的标签页中执行脚本,但仍会触发加载事件? - Marcin Majkowski
使用 beforeunload 会产生其他负面后果 - 请参阅 MDN。 - Julian Knight

9

由于我没有找到任何简单的JavaScript函数像是 windows.currentTabIndex,所以当每个浏览器标签页被加载时,我编写了一些JavaScript代码来为其修复一个ID。

function defineTabID()
    {
    var iPageTabID = sessionStorage.getItem("tabID");
      // if it is the first time that this page is loaded
    if (iPageTabID == null)
        {
        var iLocalTabID = localStorage.getItem("tabID");
          // if tabID is not yet defined in localStorage it is initialized to 1
          // else tabId counter is increment by 1
        var iPageTabID = (iLocalTabID == null) ? 1 : Number(iLocalTabID) + 1;
          // new computed value are saved in localStorage and in sessionStorage
        localStorage.setItem("tabID",iPageTabID);
        sessionStorage.setItem("tabID",iPageTabID);
        }
    }

这段代码将最后一个标签页的索引值保存在localStorage中,以便更新新标签页的当前值,并在sessionStorage中固定页面的ID!

我必须在我的应用程序的每个页面中调用defineTabId()函数。由于我使用JSF模板进行开发,因此只在一个地方定义它 :-)


但是如果该代码在两个同时打开的选项卡中运行,会发生什么情况?在getItem - setItem调用附近可能存在竞争条件。 - shitpoet
如果两个不同的代码同时执行此代码,则程序会出现错误。但在我的情况下,不可能在2个并行代码中生成多个选项卡,因为创建新选项卡与人类操作相关,并且一次只能打开一个选项卡。 - schlebe
如果由于某种原因可以打开多个选项卡,则必须以不同的方式调用我的函数。例如,必须先在创建其他选项卡的选项卡中调用此函数,以便在同一线程中顺序创建多个tabid! - schlebe

6

3

如果你在使用React,我做了一个巧妙的小钩子:

import {useEffect, useMemo} from 'react'
import uniqid from 'uniqid'

export const TAB_ID_KEY = 'tabId'

export default () => {
  const id = useMemo(uniqid, [])

  useEffect(() => {
    if (typeof Storage !== 'undefined') {
      sessionStorage.setItem(TAB_ID_KEY, id)
    }
  }, [id])

  return id
}

将此代码放置在您的基本App/Page组件上,或者放在某个您知道一定会被加载的位置。

代码将在组件挂载后运行,并为当前选项卡在会话存储中设置唯一ID,这是该选项卡特有的存储空间。

复制标签页将为新标签页获取新的ID,因为组件将再次挂载且效果将被运行,从而覆盖先前选项卡的ID。


1
那么,如果你从不读取存储在 sessionStorage 中的 id,将其放入其中有什么意义呢?此外,“uniqid”的文档建议在浏览器中仅使用时间作为后备,因此使用简单的 Date.now() 应该可以解决问题。 - thesdev

2

可能的解决方案之一是使用“window.name”属性,但我不想使用它,因为其他人也可以使用它。

我发现了另一个可能的解决方案:使用sessionStorage。它受到FF3.5+,Chrome4+,Safari4+,Opera10.5+和IE8+的支持。


2
在您的 Service Worker 中,“fetch”事件处理程序中可以获取选项卡 ID(或 clientId)。如果您需要将该 clientId 发送到您的页面,则只需使用 client.postMessage()。
在您的 Service Worker 中:
self.addEventListener("fetch", function(event) {
 event.waitUntil(async function() {

  if (!event.clientId) return;

  const client = await clients.get(event.clientId);

  if (!client) return;

  client.postMessage({
   clientId: event.clientId,
  });

 }());
});

在您的页面脚本中:
navigator.serviceWorker.addEventListener('message', event => {
 console.log(event.data.clientId);
});

Sources: Client.postMessage()


据我了解,客户端id在标签页刷新之间不会被保留,并且会生成一个新的id,这是正确的吗?我基于我的经验 使用此演示进行测试 在macOS Chrome 91.0.4472.114上。注意:该演示使用postMessage()向SW发送消息以识别客户端,而不是使用Fetch API,但我认为这没有区别。 - evan.bovie

1
如果没有用户在2毫秒内打开3个标签页的可能性,可以使用时间戳并将其存储到window.name中,并将其用作查找中的键。 window.name值将一直保留在标签页中,直到关闭它。

时间戳


0

另一种主题的变化:将此放置在您的HTML的头部分中

    <head><script>
           var tabId=sessionStorage.getItem("tabId");
           if (!tabId||(tabId!==sessionStorage.getItem("tabClosed"))){
               tabId=Math.random().toString(36).substr(2)+Date.now().toString(36).substr(5);
               sessionStorage.setItem("tabId",tabId);
           }
           sessionStorage.removeItem("tabClosed");
           window.addEventListener("unload",function(){
               sessionStorage.setItem("tabClosed",tabId);
           });
           document.title=tabId;
    </script></head>

它的工作原理:

首先,通过将其放置在头部,立即解析tabId,因此任何加载脚本都可以访问tabId,如果这对您很重要的话。

当页面加载时,在新标签页上从sessionStorage读取将意味着tabId为空,在这种情况下,会发明一个新的ID。

每当关闭选项卡时,将写入tabClosed值,并在重新加载选项卡时立即删除该值。

当您复制选项卡时,会复制会话信息,因此,tabClosed值将不再存在,因此也会发明一个新的ID。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接