前端跨页面数据传输的方法研究

前言

最近在工作实现一个popup弹窗的过程中涉及到了跨页面的数据传递问题,由于之前开发的都是单页面应用,因此对于这方面就不太了解,于是今天便写下这篇文章,想好好探讨一下。

一、问题场景还原

1.场景模拟

首先我来还原一下当时的场景,在我的系统的地图上需要添加一个弹窗,由于所使用的GIS库的要求,所以弹窗内容是一个独立的html文件。同时弹窗(html文件)中的内容必需根据系统中的数据进行渲染,这就涉及到了如何将系统中的数据传递给弹窗html的问题。

现在我简单的模拟一下,我创建了两个html文件,其中index.html 代表系统:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>主页面</title>

    <style>
      .cl-button {
        width: 150px;
        padding: 10px;
        font-size: 23px;
        cursor: pointer;
      }
      .cl-map {
        display: flex;
        flex-direction: column;
        align-items: center;
        width: 90vw;
        height: 1000px;
        background-color: rgba(140, 140, 139, 0.3);
        border: 3px solid;
        margin-top: 40px;
      }
    </style>
  </head>
  <body>
    <button class="cl-button" onclick="openPopup()">打开弹窗</button>
    <div class="cl-map">
      <h1>我是地图</h1>
      <div class="cl-popup"></div>
    </div>
    <script>
      function openPopup() {
        const popup = document.querySelector('.cl-popup')

        popup.innerHTML = createIframe(600, 300, './receiver.html')
      }

      function createIframe(width, height, url) {
        return `<iframe width='${width}' height='${height}' src=${url} />`
      }
    </script>
  </body>
</html>

receiver.html代表弹窗的html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>接收页面</title>
    <style>
      html {
        background-color: #fff;
      }

      body {
        display: flex;
        flex-direction: column;
        align-items: center;
      }
    </style>
  </head>
  <body>
    <h2>我是弹窗</h2>
    <div>
      <b style="color: red">如何将数据传递给弹窗?</b>
    </div>
    <script></script>
  </body>
</html>

效果如下:

前端跨页面数据传输的方法研究

2.需要实现的功能

我最终要实现的效果是从主页面中将一些数据传递给弹窗,然后弹窗使用这些数据绘制一个echarts图表。

需要传递的数据如下:

const chartData = {
  name:'狮驼岭区域',
  time:['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  data:[150, 230, 224, 218, 135, 147, 260]
}

二、数据传递方法

1.使用客户端存储来传递数据

(1)思路

基本思路就是使用一种客户端存储方式,在主页面将数据存储到浏览器中,当打开弹窗时再从浏览器的存储空间里读取这些数据。

客户端存储的方式有cookie、sessionStorage、localStorage和 indexedDB ,这里为了方便我直接使用sessionStorage。

(2)实现

数据传递:

//传递数据d的方法
function transmitData(data) {
  sessionStorage.setItem('chartData', JSON.stringify(data))
}

数据接收:

let chartData
// 接收数据
function receiveData() {
  chartData = JSON.parse(sessionStorage.getItem('chartData'))
}

前端跨页面数据传输的方法研究(3) 注意点

在使用客户端存储方案的时候有两个点需要注意:

第一,客户端存储的特点是存储库都是与页面源绑定的,也就是说只有来自同一个域(子域不可以)、在相同的端口上使用相同的协议的页面才可以访问同一个存储空间。

也正是这个原因在之前我在工作中没有选择客户端存储的方案。

第二,如果使用的是Web Storage(sessionStorage和localStorage),它只能存储字符串数据,非字符串数据会被转换为字符串。

sessionStorage.setItem('value', 110)
console.log(typeof  sessionStorage.getItem('value'))//String

从上面这个例子中就可以看到,我存入了一个数字,取出来了之后就变成了字符串。

因此如果存储的数据是一个对象的话我们在读取时就很难将其还原。解决方法是将数据序列化,转化为JSON格式进行存储。

const data = {
  num: 110,
}
sessionStorage.setItem('value', JSON.stringify(data))
console.log(JSON.parse(sessionStorage.getItem('value')))//{num: 110}

在存储数据时使用JSON.stringify()进行序列化;在读取数据的时候使用JSON.parse()进行反序列化。

2.使用查询字符串传递数据

(1) 思路

基本思路就是利用弹窗的URL中的查询字符串传递数据。至于查询字符串,就是指URL问号之后直到末尾的内容,也就是常说的“将需要传递的数据拼在URL的后面”。

例如,我在主页面中,给弹窗页面的URL加上一段查询字符串

前端跨页面数据传输的方法研究

然后再在弹窗页面中通过location.search读取

前端跨页面数据传输的方法研究

前端跨页面数据传输的方法研究

(2) 实现

数据传递:

// 打开弹窗
function openPopup() {
    // 获取弹窗包装元素节点
    const popup = document.querySelector('.cl-popup')
    // 将弹窗页面嵌入,通过查询字符串传递数据
    popup.innerHTML = createIframe(
      600,
      300,
      './receiver.html?' + transmitData(chartData)
    )
  }

  // 创建iframe
  function createIframe(width, height, url) {
    return `<iframe width='${width}' height='${height}' src=${url} />`
  }

  //传递数据的方法 —— 生成查询字符串
  function transmitData(data) {
    const search = new URLSearchParams()
    Object.entries(data).forEach((el) => {
      search.append(el[0],JSON.stringify(el[1]))
    })

    return search.toString()
  }

数据接收:

let chartData = {}
// 接收数据
function receiveData() {
  const search = new URLSearchParams(location.search)

  for (const [key, value] of search) {
    chartData[key] = JSON.prase(value)
  }
}

最终效果:

前端跨页面数据传输的方法研究

(3) 注意点

首先是和之前一样,也要注意将需要传递的数据进行序列化,否则在传递对象和数组的时候会出现问题。

其次是这里我使用了URLSearchParams类型来辅助我操作查询字符串,这个类接收一个查询字符作为参数,它的·实例具有以下方法可以帮助我们操纵查询字符串:

append() 插入一个指定的键/值对作为新的搜索参数
delete() 删除一个指定的搜索参数
entries() 返回与搜索参数键值对二维数组相关联的迭代器
get() 获取指定搜索参数的第一个值
getAll() 获取指定搜索参数的所有值
has() 判断是否存在此搜索参数
keys() 返回与搜索参数键数组相关联的迭代器
values() 返回与搜索参数键值数组相关联的迭代器
set() 给一个搜索参数设置新值
sort() 按键名排序
tostring() 返回搜索参数组成的字符串,可直接使用在 URL 上

3.通过窗口window对象传递数据

(1) 思路

如果子页面使用iframe嵌入主页面的,那我们就可以尝试获取到子页面的window对象,然后直接调用子页面中的函数将数据作为函数的参数传递过去,或者直接将数据存储为子页面的全局变量。

获取到子页面window对象的方法有三种:

  1. iframe节点对象 的 contentWindow 属性
  2. 执行window.open所返回的窗口对象
  3. 利用iframe[name]或者索引从父页面的window.frames中获取

(2)实现

可以通过window.frames获取到主页面中的所有子页面,然后通过name属性找到弹窗对应的子页面。

  function openPopup() {
    const popup = document.querySelector('.cl-popup')

    popup.innerHTML = createIframe(600, 300, './receiver.html')

        
    //从window.frames中找到弹窗页面的window对象
    let popupWindow = window.frames['popup']

    popupWindow.onload = function(){
      // 调用弹窗页面的初始化方法,并将数据作为参数传入
      popupWindow.initChart(chartData)
    }

  }

也可以从弹窗iframe元素节点对象的contentWindow属性中获取到子页面的window对象。

      function openPopup() {
        const popup = document.querySelector('.cl-popup')

        popup.innerHTML = createIframe(600, 300, './receiver.html')

        let popupWindow = document.querySelector('#popupIframe').contentWindow

        popupWindow.onload = function () {
          popupWindow.initChart(chartData)
        }
      }

4.通过postMessage传递数据

(1) postMessage简介

postMessage是window对象中的一个方法,它主要用于跨文档传递消息(不同源或不同工作线程),当然像我们模拟这种同源的页面间也可以用它来进行通讯。

tip: 如果想要给子页面传递数据,需要调用子页面window对象上的postMessage

postMessage()方法接收三个参数:

  1. message,需要传递的消息,这个参数最初被设计为只能传递字符串,后被改为可以传任何类型的数据,但是并非所有浏览器都支持这一改动,所以建议还是最好只传字符串。
  2. targetOrigin,目标页面的URL , 可以是 * 表示无限制,也可以是一个URL
  3. transfer,可选的可传输对象的数组(只与工作线程相关)

当接收到postMessage发送的信息后,window对象上会触发message事件。message事件的·event包含以下的内容:

  1. data,postMessage第一个参数传递的数据
  2. origin,发送信息的文档源
  3. source,发送信息的文档中window对象的代理,主要是为了使用其中的postMessage方法回复消息。如果是相同源的情况下source就是window对象,如果是不同源的情况source中可能只有postMessage可用。

(2) 实现

function openPopup() {
  const popup = document.querySelector('.cl-popup')

  popup.innerHTML = createIframe(600, 300, './receiver.html')

  // 获取弹窗页面的window对象
  let popupWindow = window.frames['popup']

  // 向弹窗页面传递消息
  popupWindow.postMessage(
    JSON.stringify(chartData),
    location.origin + '/博客代码/跨页面通讯的若干问题/receiver.html'
  )
  
  // 弹窗页面侦听message事件,接收参数
  popupWindow.onmessage = function (event) {
    if (event.source !== this.parent) return;
    // 将postMessage所传递的数据加入到子页面的全局作用域中
    this.echartData = JSON.parse(event.data)
  }
}

(3) 疑问

看到postMessage可以解决跨域问题,所以我就萌生了一个大胆的想法,能否在我的主页面中嵌入天猫的网页,然后使用postMessage实现将数据传给天猫呢?

于是进行尝试

function openPopup() {
  const popup = document.querySelector('.cl-popup')

  popup.innerHTML = createIframe(
    600,
    300,
    'https://www.tmall.com/?page_offline=true'
  )

  // let popupWindow = document.querySelector('iframe').contentWindow
  let popupWindow = window.frames['popup']

  popupWindow.postMessage(
    JSON.stringify('欢迎来到天猫'),
    'https://www.tmall.com/?page_offline=true'
  )

  popupWindow.onmessage = function (event) {
    if (event.source !== this.parent) return;

    // 将postMessage所传递的数据加入到子页面的全局作用域中
    const message = JSON.parse(event.data)

    alert(message);
  }
}

结果失败:

前端跨页面数据传输的方法研究

报错显示postMessage执行失败,并且提示我源不同。这让我很费解,我只能理解为像天猫这样的成熟的网址是做了相应的保护措施的。


参考资料

  1. 前端HTML网页之间传递数据多种办法,附代码案例_html传值给另一个html-CSDN博客
  2. vue组件向HTML文件传送数据,HTML向vue组件发送数据。vue组件与HTML文件间进行交互的方法。_html与vue通信-CSDN博客
  3. Vue中嵌入html页面并相互通信_AKA皮卡Q-华为云开发者联盟
  4. js解决url传递中文参数乱码问题_js url 中文乱码-CSDN博客
  5. window.postMessage – Web API 接口参考 | MDN

原文链接:https://juejin.cn/post/7332858431571263515 作者:Carlos_sam

(0)
上一篇 2024年2月9日 上午11:03
下一篇 2024年2月9日 下午4:06

相关推荐

发表回复

登录后才能评论