Hash URL跨窗口通信机制详解 
Hash URL跨窗口通信是一种在不同窗口或应用间传递数据的轻量级方法,通过修改和读取URL的哈希部分实现数据传递,特别适用于前端应用间的消息传递。本文详细介绍相关的API、使用方法和底层原理。
一、基础概念 
1.1 URL结构 
一个完整的URL通常包含以下部分:
https://example.com:443/path/to/page?query=value&name=test#hashValue- 协议(protocol): 
https: - 主机名(hostname): 
example.com - 端口(port): 
443 - 路径(pathname): 
/path/to/page - 查询参数(search): 
?query=value&name=test - 哈希(hash): 
#hashValue 
1.2 哈希(Hash)部分特性 
哈希部分具有以下特点:
- 不会随HTTP请求发送到服务器
 - 修改哈希不会导致页面刷新
 - 可以通过JavaScript读取和修改
 - 修改后可以被添加到浏览器历史记录中
 
二、核心API介绍 
2.1 URL与URLSearchParams 
2.1.1 URL对象 
URL是一个Web API,用于解析、构造、规范化和编码URL。
// 创建URL对象
// // 1. 首先使用 decodeURIComponent 函数解码 callback 参数,将其中的百分号编码(如 %20)转换回原始字符
// 2. 然后使用解码后的 URL 字符串创建一个新的 URL 对象
// 3. 这样就可以方便地操作 URL 的各个部分(如域名、路径、查询参数、哈希值等)
const callbackUrl = new URL(decodeURIComponent(callback));
// 修改URL的各个部分
callbackUrl.hostname = '192.162.1.113';  // 修改主机名
callbackUrl.port = '3000';               // 修改端口
callbackUrl.protocol = window.location.protocol;  // 修改协议
// 修改哈希部分
// encodeURIComponent 函数用于对 URL 组件进行编码,将特殊字符转换为 URL 安全的形式
// 它会将除了字母、数字、(-_.!~*'()) 这些字符外的所有字符转换为百分号编码
// 例如:空格 → %20、/ → %2F、+ → %2B、中文 → %E4%B8%AD%E6%96%87
// 在这里,我们对 objectKey 进行编码,确保其中的特殊字符不会破坏 URL 的结构
// 这对于传递包含特殊字符的数据(如文件路径、中文内容等)非常重要
callbackUrl.hash = `signature=${encodeURIComponent(objectKey)}`;
// 转换为字符串
window.location.href = callbackUrl.toString();2.1.2 URLSearchParams 
URLSearchParams是处理URL查询字符串的接口,提供了读取、修改、添加和删除URL参数的方法。
// 从URL中获取查询参数
// 创建URLSearchParams对象,用于解析URL中的查询参数
// window.location.search返回URL中的查询字符串部分,包括问号(?)
// 例如:对于URL "https://example.com/page?name=test&id=123"
// window.location.search将返回 "?name=test&id=123"
// URLSearchParams提供了一组方法来方便地操作这些查询参数
// 如get()获取参数值、set()设置参数值、has()检查参数是否存在等
const urlParams = new URLSearchParams(window.location.search);
const projectId = urlParams.get('projectId');
const sid = urlParams.get('sid');
// 添加或修改查询参数
urlParams.set('scanned', scanFlag);
callbackUrl.search = urlParams.toString();2.2 编码解码函数 
2.2.1 encodeURIComponent与decodeURIComponent 
这对函数用于对URL组件进行编码和解码,确保特殊字符能在URL中安全传输。
// 编码:将特殊字符转换为URL安全的形式
callbackUrl.hash = `signature=${encodeURIComponent(objectKey)}`;
// 解码:将编码后的字符串转换回原始形式
const callback = decodeURIComponent(urlParams.get('callback'));2.2.2 编码解码原理 
编码(
encodeURIComponent):将非字母数字字符转换为%后跟两位十六进制数字- 空格→
%20 /→%2F+→%2B- 等等
 
- 空格→
 解码(
decodeURIComponent):将%后跟两位十六进制数字转换回原始字符
2.3 Blob与File对象 
2.3.1 Blob对象 
Blob(Binary Large Object)表示二进制数据的对象。
// 从Canvas获取Blob对象
const blob = await new Promise(resolve => {
  signatureCanvas.value.toBlob(resolve, 'image/png');
});2.3.2 File对象 
File对象继承自Blob,包含额外的文件信息如名称和修改日期。
// 从Blob创建File对象
const file = new File([blob], `signature_${sid}.png`, { type: 'image/png' });2.4 location对象属性与方法 
window.location对象提供了当前文档的URL信息和导航能力。
// 读取URL的哈希部分
const hash = window.location.hash;
// 读取URL的查询字符串部分
const search = window.location.search;
// 修改URL(会导航到新URL)
window.location.href = callbackUrl.toString();
// 替换当前历史记录(不增加新历史记录)
window.history.replaceState({}, '', `${window.location.pathname}?projectId=${projectId}`);三、Hash URL跨窗口通信实现 
3.1 基本原理 
Hash URL跨窗口通信利用了:
- 修改哈希部分不会导致页面刷新
 - 可以通过JavaScript访问哈希值
 - URL可以在不同窗口/应用间传递
 
3.2 实现步骤 
- 发送端:将数据编码后附加到URL的哈希部分
 - 接收端:解析URL哈希部分,提取并解码数据
 
3.3 代码示例:电子签名场景 
3.3.1 生成签名并编码到URL哈希 
// 确认签名并上传
const confirmSignature = async () => {
  try {
    // 将Canvas内容转为Blob
    const blob = await new Promise(resolve => {
      signatureCanvas.value.toBlob(resolve, 'image/png');
    });
    // 获取会话ID参数
    const urlParams = new URLSearchParams(window.location.search);
    const sid = urlParams.get('sid');
    // 创建File对象
    const file = new File([blob], `signature_${sid}.png`, { type: 'image/png' });
    // 上传文件并获取ObjectKey
    const objectKey = await ossService.uploadFile(file);
    if (objectKey) {
      // 获取回调URL
      const callback = urlParams.get('callback');
      if (callback) {
        // 解码并创建URL对象
        const callbackUrl = new URL(decodeURIComponent(callback));
        
        // 配置回调地址(本地开发环境)
        if (callbackUrl.hostname === 'localhost') {
          callbackUrl.hostname = '192.168.1.116';
          callbackUrl.port = '3000';
          callbackUrl.protocol = window.location.protocol;
        }
        // 将签名数据编码到URL哈希部分
        callbackUrl.hash = `signature=${encodeURIComponent(objectKey)}`;
        
        // 导航到回调URL
        window.location.href = callbackUrl.toString();
      }
    }
  } catch (error) {
    console.error('上传签名失败:', error);
  }
};3.3.2 从URL哈希中提取签名数据 
// 从URL中提取项目ID和签名URL
const getCleanProjectIdAndSignature = () => {
  // 获取projectId参数
  const urlParams = new URLSearchParams(window.location.search);
  const projectId = urlParams.get('projectId');
  // 从哈希中获取签名URL
  const hash = window.location.hash;
  const signatureUrl = hash.startsWith('#signature=')
    ? decodeURIComponent(hash.replace('#signature=', ''))
    : '';
    
  return { projectId, signatureUrl };
};
// 使用解构赋值获取数据
const { projectId, signatureUrl } = getCleanProjectIdAndSignature();
// 如果有签名URL,更新表单状态
if (signatureUrl) {
  formState.auditSignatureImage = signatureUrl;
  
  // 清除哈希,保持URL整洁
  window.history.replaceState({}, '', `${window.location.pathname}?projectId=${projectId}`);
}3.4 跨域考量 
当在不同域之间进行通信时,需要考虑同源策略限制。代码示例中使用了iframe来实现跨域通信:
// 通知PC端二维码已被扫描
if (callback) {
  const callbackUrl = new URL(decodeURIComponent(callback));
  
  // 添加scanned参数以通知PC端
  const urlParams = new URLSearchParams(callbackUrl.search);
  urlParams.set('scanned', scanFlag);
  callbackUrl.search = urlParams.toString();
  // 创建隐藏iframe实现跨域通信
  const iframe = document.createElement('iframe');
  iframe.style.display = 'none';
  iframe.src = callbackUrl.toString();
  document.body.appendChild(iframe);
  // 3秒后移除iframe
  setTimeout(() => {
    if (document.body.contains(iframe)) {
      document.body.removeChild(iframe);
    }
  }, 3000);
}四、高级用例 
4.1 二维码签名流程 
示例代码中实现了移动设备扫码签名并返回签名数据的完整流程:
生成带参数的签名页面URL
javascriptconst getSignaturePageUrl = () => { const currentUrl = window.location.href; const protocol = window.location.protocol; const ipAndPort = getLocalIP(); const baseUrl = `${protocol}//${ipAndPort}`; scanSessionId.value = `sign_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; return `${baseUrl}/m/sign?sid=${props.sid}&callback=${encodeURIComponent(currentUrl)}&scanFlag=${scanSessionId.value}`; };将URL转为二维码,用户通过移动设备扫描
移动设备上进行签名,完成后将数据通过URL哈希传回
检测URL参数变化,接收签名数据
javascriptconst checkSignatureUrl = () => { const urlParams = new URLSearchParams(window.location.search); const signatureUrl = urlParams.get('signatureUrl'); const scannedFlag = urlParams.get('scanned'); if (scannedFlag && scannedFlag === scanSessionId.value) { showQRModal.value = false; clearAllIntervals(); router.push('/examine-project'); return; } if (signatureUrl) { emit('signature-complete', decodeURIComponent(signatureUrl)); const newUrl = window.location.pathname + window.location.search.replace(/[?&]signatureUrl=[^&]*/, ''); window.history.replaceState({}, '', newUrl); showQRModal.value = false; clearAllIntervals(); router.push('/examine-project'); } };
4.2 安全考量 
使用Hash URL传递敏感信息存在安全风险:
- URL可能会被记录在浏览器历史、服务器日志中
 - 对于非HTTPS连接,URL可能被网络监听者获取
 
最佳实践:
- 始终使用HTTPS
 - 避免在URL中传递敏感信息
 - 实现数据的过期机制
 - 考虑使用更安全的通信机制(如WebSockets、postMessage)处理敏感数据
 
五、总结 
Hash URL跨窗口通信是一种简单但强大的技术,适用于:
- 跨窗口/应用数据传递
 - 状态持久化(保存在URL中)
 - 基于URL的路由和导航
 
通过合理使用URL、URLSearchParams、编码/解码函数和Blob/File对象,可以实现多种复杂的前端通信场景,如示例中的移动设备扫码签名流程。
在现代Web应用开发中,了解这些API的使用方法和底层原理,对于构建无缝的用户体验至关重要。