postmessage

第一种考虑的是postMessage ,方法可以安全地实现跨源通信

预备属性值

window.opener返回打开当前窗口的源窗口的引用,如果当前窗口不是由其他窗口打开的,则返回null

1
window.opener

window.parent返回当前窗口的父窗口对象,一般用在iframe对嵌入它的父窗口引用上,若无父窗口返回自身引用

1
window.parent

window.top当前窗口的最顶层窗口

1
window.top

window.frameElement返回嵌入当前窗口的iframe对象

1
window.frameElement

window.frameElement 返回嵌入窗口的元素,如果未嵌入窗口,则返回null

1
window.frameElement

window.frames 返回当前窗口中所有子窗体的数组

1
window.frames

通信方法

  1. 发送窗口引用目标窗口使用postmessage,目的是用来触发目标窗口message的监听事件

    1
    originWindow.postMessage(message, targetOrigin, [transfer]);
  2. 窗口通信接受方使用onMessage

    1
    2
    3
    originWindow.onmessage = (event)=>{
    console.log(event)
    }

messageEvent的值打印如下:

获取目标窗口常用的方法

  1. 引用父窗口window.parent
  2. 引用打开源窗口window.opener
  3. 引用打开源窗口window.top
  4. 监听message事件,回调参数的source字段
  5. 直接open新窗口的值
  6. window.frames获取子框架集合
1
2
3
4
5
6
7
8
9
10
11
// http://origin.com
var targetWindow = window.open('http://target.com')
targetWindow.postMessage('origin data','http://target.com')
// http://target.com
window.addEventListener('message',(event)=>{
if(event.origin !== 'http://origin.com') return
// 接受到的信息
console.log(event.data) // origin data
// 把event.source作为回信对象,并且把event.origin作为targetOrigin
event.source.postMessage('target data',event.origin)
})

使用注意:
使用postMessage将数据发送到其他窗口时,始终指定精确的目标origin,而不是*;接收消息时候要验证当前消息的source来源。否则可能会导致引起跨站点脚本攻击。

localstorage

第二种考虑的是localstorage,之所以用这个是因为无意中发现storage也是可以监听的,这样的话就可以通过监听当前同源的storage实现同源不同标签之间的通信刷新。
调用localStorage.setItem()触发事件,但是在当前事件监听页面用此方法是不能触发事件的,必须在不同的同源标签页面,且两次更新的值不变也不会触发事件
监听事件

1
2
3
window.onstorage = (e) => {console.log(e)}
// or
window.addEventListener('storage', (e) => console.log(e))

触发事件

1
localStorage.setItem('key','value')

还有能想到的前端实现通信的方法是使用cookie,本质上和storage是一回事,但是没有监听方法,唯一的可以实现通信的方式就是定时器轮询cookie是否更新,而且cookie也同样受限制于同源种cookie,每次发送请求携带多余的cookie增加通信负担,是一种比较低效的不推荐的方式

websocket

涉及到服务层,以前做过websocket的相关聊天工具,这个就不止是标签之间的通信,而是任意一个浏览器和浏览器之间的标签页面通信。我们可以用node启动一个websocket服务。以下简单模拟下聊天室的websocket搭建,使得连接ws服务的页面之间可以通信。

server端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 聊天ws数据接口{msg,type} msg[string]消息 type[string]消息类型
const WebSocket = require('ws')
const wsSever = new WebSocket.Server({ port: 8088 })
// 消息广播
wsSever.broadcast = function (data) {
wsSever.clients.forEach(function each (client) {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data))
}
})
}
wsSever.on('connection', function connection (ws) {
// ws.nickName = `游客${i++}`
// console.log('%s 进入聊天室', ws.nickName)
wsSever.broadcast({
system: `${ws.nickName}号进入聊天室`,
type: 'system'
})
const exsitGroup = []
// 聊天室里所有连接的信息的昵称呈现给新加入的连接人
// wsSever.clients.forEach(function each (client) {
// if (client.readyState === WebSocket.OPEN && client.nickName) {
// exsitGroup.push(client.nickName)
// }
// })
if (exsitGroup.length > 0) {
ws.send(JSON.stringify({
type: 'exsit',
data: exsitGroup
}))
}
// 监听消息事件
ws.on('message', function (message) {
console.log(`Server Received Message:${message}`)
const { type, data } = JSON.parse(message)
console.log(type, data)
if (type === 'join') {
wsSever.broadcast({
type,
data
})
ws.nickName = data
} else if (type === 'chat') {
wsSever.broadcast({
type,
data: ws.nickName + '说: ' + data
})
}
})
// 结束事件
ws.on('close', function (code, reason) {
console.log(`客户端关闭连接:code(${code}),reason(${reason})`)
wsSever.broadcast({
type: 'quit',
data: ws.nickName
})
})
})

client端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
.chat-wrap {
width: 500px;
height: 500px;
overflow: hidden;
border: 1px solid #eee;
}
#chat{
height: 450px;
overflow-y: hidden;
overflow-x: hidden;
}
#chat p {
white-space: nowrap;
word-break: break-all;
}
#chat .msg-normal{ font-size: 16px; color: #333 }
#chat .msg-system{ font-size: 12px; color: rgb(255, 122, 14) }
.user-area{ display: flex; height: 50px;}
.user-area .msg{flex:4;font-size: 16px;}
.user-area .send{flex:1;background: rgba(66, 130, 240, 0.933);color: #fff;text-align: center;line-height: 50px;cursor: pointer;}
.pre-info{margin-bottom: 20px;}
</style>
<title>ws-client-chat</title>
</head>
<body>
<div class="pre-info">
<input type="text" id="tyname" class="tyname" placeholder="请先输入昵称">
<button id="enter" class="enter" disabled>进入聊天</button>
</div>
<div class="chat-wrap">
<div id="chat"></div>
<div class="user-area">
<input type="text" id="msg" class="msg"><div id="send" class="send">发送</button>
</div>
</div>
<script>
document.querySelector('#tyname').addEventListener('input',(e)=>{
if(e.target.value.length >0){
document.querySelector('#enter').disabled = false
}else{
document.querySelector('#enter').disabled = true
}
})
document.querySelector('#enter').addEventListener('click',(e)=>{
e.target.disabled = true
// 连接一个ws服务
var ws = new WebSocket("ws:localhost:8088");
ws.onopen = function(evt) {
console.log("Connection open ...")
// 向ws发送消息 输送昵称
ws.send(JSON.stringify({
data: document.querySelector('#tyname').value,
type: 'join'
}))
}
// 监听服务关闭
ws.onclose = function(evt) {
console.log("Connection close ...")
}
// 监听服务发来的消息
ws.onmessage = function(evt) {
console.log( "Client Received Message: " + evt.data);
const {type, data} = JSON.parse(evt.data)
const chatItem = document.createElement('p')
console.log(type, data)
if(type === 'chat'){
chatItem.className = 'msg-normal'
chatItem.innerHTML = data
}else if(type === 'system') {
chatItem.className = 'msg-system'
chatItem.innerHTML = data
}else if(type === 'join') {
chatItem.className = 'msg-system'
chatItem.innerHTML = `${data}进入聊天室`
}else if(type === 'quit') {
chatItem.className = 'msg-system'
chatItem.innerHTML = `${data}退出聊天室`
}else if(type === 'exsit') {
chatItem.className = 'msg-system'
chatItem.innerHTML = `和 ${data.join(' ')} 他们打声招呼吧`
}
document.querySelector('#chat').appendChild(chatItem)
};

document.querySelector('#send').addEventListener('click',()=>{
// 向ws发送消息
ws.send(JSON.stringify({
data: document.querySelector('#msg').value,
type: 'chat'
}))
document.querySelector('#msg').value = ''
})
// 当前标签离开事件
window.onbeforeunload = function(){
return '确认离开?' //输入什么无所谓,规避欺骗风险这个自定义文字弹窗已经不被厂商支持
}
window.onunload = function(){
ws.close('1000','Leave or refresh page')
}
})
</script>
</body>
</html>

原创内容,欢迎交流转载请注明出处