前言
最近在工作实现一个popup弹窗的过程中涉及到了跨页面的数据传递问题,由于之前开发的都是单页面应用,因此对于这方面就不太了解,于是今天便写下这篇文章,想好好探讨一下。
一、问题场景还原
1.场景模拟
首先我来还原一下当时的场景,在我的系统的地图上需要添加一个弹窗,由于所使用的GIS库的要求,所以弹窗内容是一个独立的html文件。同时弹窗(html文件)中的内容必需根据系统中的数据进行渲染,这就涉及到了如何将系统中的数据传递给弹窗html的问题。
现在我简单的模拟一下,我创建了两个html文件,其中index.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>
.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:
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图表。
需要传递的数据如下:
JavaScript
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)实现
数据传递:
JavaScript
//传递数据d的方法
function transmitData(data) {
sessionStorage.setItem('chartData', JSON.stringify(data))
}
数据接收:
JavaScript
let chartData
// 接收数据
function receiveData() {
chartData = JSON.parse(sessionStorage.getItem('chartData'))
}
(3) 注意点
在使用客户端存储方案的时候有两个点需要注意:
第一,客户端存储的特点是存储库都是与页面源绑定的,也就是说只有来自同一个域(子域不可以)、在相同的端口上使用相同的协议的页面才可以访问同一个存储空间。
也正是这个原因在之前我在工作中没有选择客户端存储的方案。
第二,如果使用的是Web Storage(sessionStorage和localStorage),它只能存储字符串数据,非字符串数据会被转换为字符串。
JavaScript
sessionStorage.setItem('value', 110)
console.log(typeof sessionStorage.getItem('value'))//String
从上面这个例子中就可以看到,我存入了一个数字,取出来了之后就变成了字符串。
因此如果存储的数据是一个对象的话我们在读取时就很难将其还原。解决方法是将数据序列化,转化为JSON格式进行存储。
JavaScript
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) 实现
数据传递:
JavaScript
// 打开弹窗
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()
}
数据接收:
JavaScript
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对象的方法有三种:
- iframe节点对象 的
contentWindow
属性 - 执行
window.open
所返回的窗口对象 - 利用
iframe[name]
或者索引从父页面的window.frames
中获取
(2)实现
可以通过window.frames
获取到主页面中的所有子页面,然后通过name
属性找到弹窗对应的子页面。
JavaScript
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对象。
JavaScript
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()
方法接收三个参数:
- message,需要传递的消息,这个参数最初被设计为只能传递字符串,后被改为可以传任何类型的数据,但是并非所有浏览器都支持这一改动,所以建议还是最好只传字符串。
- targetOrigin,目标页面的URL , 可以是
*
表示无限制,也可以是一个URL - transfer,可选的可传输对象的数组(只与工作线程相关)
当接收到postMessage
发送的信息后,window对象上会触发message
事件。message
事件的·event包含以下的内容:
- data,
postMessage
第一个参数传递的数据 - origin,发送信息的文档源
- source,发送信息的文档中window对象的代理,主要是为了使用其中的
postMessage
方法回复消息。如果是相同源的情况下source就是window对象,如果是不同源的情况source中可能只有postMessage
可用。
(2) 实现
JavaScript
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
实现将数据传给天猫呢?
于是进行尝试
JavaScript
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
执行失败,并且提示我源不同。这让我很费解,我只能理解为像天猫这样的成熟的网址是做了相应的保护措施的。
参考资料