从页面 A 打开一个新页面 B,B 页面正常关闭或意外崩溃,A页面怎么更新B页面提交的参数并进行更新

过程拆分

  • A页面中打开B页面,A,B页面通信方式?
  • B页面正常关闭,如何通知A页面?
  • B页面意外崩溃,如何通知A页面?

A、B页面的通信方式

  • url传参
  • localStorage本地存储
  • postmessage
  • WebSocket协议
  • SharedWorker
  • Service Worker

url传参

A.vue

<template>
  <div>
    <div>{{ count }}</div>
    <button @click="handleNewDialog">B弹窗</button>
  </div>
</template>

<script setup>
  import { ref, onMounted } from "vue";
  import { useRouter } from 'vue-router' //引入useRouter
  const count = ref(1);
  const router = useRouter();

  const {href} = router.resolve({ //使用resolve
    name:'B',    //这里是跳转页面的name
    path: '/B',
    query: {
      count: count.value,
    }
  })
  window.name = 'A'
  function handleNewDialog() {
    window.open(href, '_blank', centerStyle(400, 400)+',toolbar=no,menubar=no,resizeable=no,location=no,status=no,scrollbars=yes')
  }
  // 子方法
  var centerStyle = function (height, width) {
    var iTop = (window.screen.height - 30 - height) / 2;       //获得窗口的垂直位置; 
    // var iLeft = (window.screen.width - 10 - width) / 2;        //获得窗口的水平位置; 不生效
    let iLeft = (window.screenX || window.screenLeft || 0) + (window.screen.width - width) / 2;
    return 'height=' + height + ',width=' + width + ',top=' + iTop + ',left=' + iLeft
  };
  window.addEventListener('hashchange', function () {// 监听 hash
    let hash = window.location.hash;
    let index = hash.indexOf('?');
    let searchData = hash.substring(index + 1);
    let arr = searchData.split('=');
    count.value = arr[1];
  }, false);

</script>
<style>
</style>

B.vue

<template>
  <div>
    <div>{{ newCount }}</div>
    <button @click="handelAdd">增加count</button>
    <button type="button" @click="sendA()">发送A页面消息,关闭B弹窗,newCount变更后并显示在A页面上</button>
  </div>
</template>

<script setup>
  import { ref } from "vue";
  import { useRoute, useRouter } from 'vue-router'
  window.name = 'B'
  let newCount = ref(0);
  const route = useRoute()
  newCount.value = route.query.count
  function handelAdd() {
    newCount.value ++
  }
  // 窗口崩溃
  window.onbeforeunload = function (e) {
  	sendA()
    return '确定离开此页吗?';
  }
  function sendA() {
    let href = window.location.origin + '/#/?count=' + newCount.value
    window.open(href, 'A')
  }
</script>
<style>
</style>

localStorage本地存储

// A页面存储count, 监听获取数据,更新页面
localStorage.setItem('count', count.value);
// A页面 监听获取数据  storage事件只有在值发生变化时才会触发。
window.addEventListener('storage', function (e) {
})
// B页面获取count B修改之后更新localStorage存储的count
let testB = localStorage.getItem('count');

注:localStorage仅允许方位同源,存储的数据将保存在浏览器会话中,如果A打开的B页面和A是不同源的,则无法方位同一Storage

postMessage

postMessage 是 html5 引入的API,postMessage()方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档、多窗口、跨域消息传递,多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案。
A.vue

<template>
  <div>
    <div>{{ count }}</div>
    <button @click="handleNewDialog">new弹窗</button>
  </div>
</template>

<script setup>
  import { ref, onMounted } from "vue";
  import { useRouter } from 'vue-router' //引入useRouter
  const count = ref(1);
  const router = useRouter();

  const {href} = router.resolve({ //使用resolve
    name:'B',    //这里是跳转页面的name
    path: '/B',
    query: {
      count: count.value,
    }
  })
  window.name = 'A'
  function handleNewDialog() {
    window.open(href, '_blank', centerStyle(400, 400)+',toolbar=no,menubar=no,resizeable=no,location=no,status=no,scrollbars=yes')
  }
  // 子方法
  var centerStyle = function (height, width) {
    var iTop = (window.screen.height - 30 - height) / 2;       //获得窗口的垂直位置; 
    // var iLeft = (window.screen.width - 10 - width) / 2;        //获得窗口的水平位置; 不生效
    let iLeft = (window.screenX || window.screenLeft || 0) + (window.screen.width - width) / 2;
    return 'height=' + height + ',width=' + width + ',top=' + iTop + ',left=' + iLeft
  };
  window.addEventListener("message", receiveCount, false);
</script>
<style>
</style>

B.vue

<template>
  <div>
    <div>{{ newCount }}</div>
    <button @click="handelAdd">增加count</button>
    <button type="button" @click="sendA()">发送A页面消息</button>
  </div>
</template>
<script setup>
  import { ref } from "vue";
  import { useRoute, useRouter } from 'vue-router'
  window.name = 'B'
  let newCount = ref(0);
  const route = useRoute()
  newCount.value = route.query.count
  function handelAdd() {
    newCount.value ++
  }
  function sendA() {
  	// window.opener----是window.open打开的子页面对象调用父页面对象
  	// 通常在使用window.opener的时候要去判断父窗口的状态,如果父窗口被关闭或者更新,就会出错,解决办法是加上如下的验证if(window.opener && !window.opener.closed)
    let targetWindow = window.opener
    targetWindow.postMessage(newCount.value, window.location.href);
  }
</script>
<style>
</style>

代码效果
API介绍

  • 发送数据
    otherWindow.postMessage(message, targetOrigin, [transfer]);
    

    otherWindow:窗口的引用,例如:比如执行window.open返回的窗口对象 iframe的contentWindow属性或者是命名过的或数值索引的window.frames.
    message:要发送给其他窗口的数据
    targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,指定后只有对应origin下的窗口才可以接收到消息,设置为通配符"*“表示可以发送到任何窗口,但通常处于安全性考虑不建议这么做.如果想要发送到与当前窗口同源的窗口,可设置为”/"
    transfer (可选属性):是一串和message同时传递的Transferable对象,这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权.

  • 接收数据:监听message事件的发生
    window.addEventListener("message", receiveMessage, false) ;
    function receiveMessage(event) {
         var origin= event.data;
         console.log(event);
    }
    

上述代码中console的结果

  • data : 指的是从其他窗口发送过来的消息对象;
  • type: 指的是发送消息的类型;
  • source: 指的是发送消息的窗口对象;
  • origin: 指的是发送消息的窗口的源

WebSocket协议

  1. 什么是 WebSocket
  • WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
  • 它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
  • Websocket是一个持久化的协议
  1. websocket的原理
  • websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信
  • 在websocket出现之前,web交互一般是基于http协议的短连接或者长连接
  • websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"
  1. websocket的使用场景: 社交聊天、弹幕、多玩家游戏、协同编辑、股票基金实时报价、体育实况更新、视频会议/聊天、基于位置的应用、在线教育、智能家居等 需要高实时的场景。

SharedWorker

SharedWorker 接口代表一种特定类型的 worker,可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker。它们实现一个不同于普通 worker 的接口,具有不同的全局作用域,SharedWorkerGlobalScope 。
page.vue

<template>
  <div>{{ count }}</div>
  <div @click="sendMessage">点击1</div>
</template>

<script setup>
  import sharedWorkerHook from './sharedWorkerHook.js'
  import { ref, onMounted } from "vue";
  let count = ref(1);
  onMounted(() => {
    sharedWorkerHook.port.start()
    // 接收SharedWorker返回的结果
    sharedWorkerHook.port.onmessage = event => {
      count.value = event.data;
      console.log(event.data, '11111111111111')
    }
  })
  function sendMessage() {
    count.value ++
    sharedWorkerHook.port.postMessage({ value: count.value })
  }
</script>

page2.vue

<template>
  {{ count }}
  <div @click="sendMessage">点击2</div>
</template>

<script setup>
  import sharedWorkerHook from './sharedWorkerHook'
  import { ref, onMounted } from "vue";
  let count = ref(100);
  onMounted(() => {
    sharedWorkerHook.port.start()
    // 接收SharedWorker返回的结果
    sharedWorkerHook.port.onmessage = event => {
      count.value = event.data
      console.log(event.data, '22222222222')
    }
  })
  
  function sendMessage() {
    count.value ++
    sharedWorkerHook.port.postMessage({ value: count.value })
  }
</script>

worker.js

/**
 * @description 所有连接这个worker的集合
 */
const portsList = []
/**
 * @description 连接成功回调
 */
self.onconnect = (event) => {
    // 当前触发连接的端口
    const port = event.ports[0]
    // 添加进去
    portsList.push(port)
    // 接收到消息的回调
    port.onmessage = (event) => {
        // 获取传递的消息
        const { type, message, value } = event.data
        // 计算
        let result = 0
        result = value
        portsList.forEach((port) => port.postMessage(`${result}`))
    }
}

sharedWorkerHook.js

const sharedWorker = new SharedWorker(new URL('./worker.js', import.meta.url), 'test')

export default sharedWorker

文件目录结构
在这里插入图片描述
上述代码的效果图ShareWorker的Web API 接口

Service Worker

Service Worker 是一个可以长期运行在后台的 Worker,能够实现与页面的双向通信。多页面共享间的 Service Worker 可以共享,将 Service Worker 作为消息的处理中心(中央站)即可实现广播效果。

// 注册 Service Worker 
navigator.serviceWorker.register('./sw.js').then(function () {
    console.log('Service Worker 注册成功');
})
// 其中./sw.js是对应的Service Worker脚本。Service Worker本身并不具备“广播通信”的功能, 需要我们将其改造成消息中转站:
self.addEventListener('message', function (e) {
    console.log(e.data);
    e.waitUntil(
        self.clients.matchAll().then(function (clients) {
            if (!clients || clients.length === 0) {
                return;
            }
            clients.forEach(function (client) {
                client.postMessage(e.data);
            });
        })
    );
});
// A 在需要获取的页面监听Service Worker发送来的消息:
navigator.serviceWorker.addEventListener('message', function (e) {
    console.log(e.data)
});

// B 发送消息,可以调用Service Worker的postMessage方法:
navigator.serviceWorker.controller.postMessage('Hello A');

Service Worker - 《阮一峰 Web API 教程》

如何监控网页崩溃?

  • B 页面正常关闭,如何通知 A 页面
    页面正常关闭时,会先执行 window.onbeforeunload ,然后执行 window.onunload ,我们可以在这两个方法里向 A 页面通信

  • B 页面意外崩溃,又该如何通知 A 页面
    页面正常关闭,我们有相关的 API,崩溃就不一样了,页面看不见了,JS 都不运行了,那还有什么办法可以获取B页面的崩溃?

    1. window 对象的 load 和 beforeunload 事件,通过心跳监控来获取 B 页面的崩溃
    • 在页面加载时(load事件)在sessionStorage记录goodexit状态为pending。
    • 如果用户正常退出(beforeunload事件)状态改为true。
    • 如果crash了,状态依然为pending。
    • 在用户第2次访问网页的时候(第2个load事件),查看goodexit的状态,如果仍然是pending就是可以断定上次访问网页崩溃了。
    window.addEventListener('load', function () {
      sessionStorage.setItem('good_exit', 'pending');
      setInterval(function () {
         sessionStorage.setItem('time_before_crash', new Date().toString());
      }, 1000);
    });
    
    window.addEventListener('beforeunload', function () {
      sessionStorage.setItem('good_exit', 'true');
    });
    
    if(sessionStorage.getItem('good_exit') &&
      sessionStorage.getItem('good_exit') !== 'true') {
      /*
         insert crash logging code here
     */
      alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));
    }
    

这个方案巧妙的利用了页面崩溃无法触发 beforeunload 事件来实现的。
需要注意的是,使用sessionStorage存储状态可能会因为用户强制关闭网页或者重新打开浏览器而丢失,而将状态存储在localStorage或Cookie中可能会导致每有一次网页打开,就会有一个crash上报。因此,需要根据实际情况选择合适的存储方式。!

  1. 基于Service Worker的崩溃统计方案:
    • 在页面的脚本中创建Service Worker工作线程。
    • 定时向该线程发送消息,即使网页奔溃了,线程还能存活。
    • 在线程中接收消息并比对时间,当间隔时间大于15秒时,就认为超时没有心跳了,页面处于奔溃阶段,向监控系统上报相关信息。
      优点
      • Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker 一般情况下不会崩溃;
      • Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态;
      • 网页可以通过 navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 发送消息
        完整的设计流程
      • B 页面加载后,通过 postMessage API 每 5s 给 sw 发送一个心跳,表示自己的在线,sw 将在线的网页登记下来,更新登记时间;
      • B 页面在 beforeunload 时,通过 postMessage API 告知自己已经正常关闭,sw 将登记的网页清除;
      • 如果 B页面在运行的过程中 crash 了,sw 中的 running 状态将不会被清除,更新时间停留在奔溃前的最后一次心跳;
      • A 页面 Service Worker 每 10s 查看一遍登记中的网页,发现登记时间已经超出了一定时间(比如 15s)即可判定该网页 crash 了。
    // B
    if (navigator.serviceWorker.controller !== null) {
      let HEARTBEAT_INTERVAL = 5 * 1000 // 每五秒发一次心跳
      let sessionId = uuid() // B页面会话的唯一 id
      let heartbeat = function () {
        navigator.serviceWorker.controller.postMessage({
          type: 'heartbeat',
          id: sessionId,
          data: {} // 附加信息,如果页面 crash,上报的附加数据
        })
      }
      window.addEventListener("beforeunload", function() {
        navigator.serviceWorker.controller.postMessage({
          type: 'unload',
          id: sessionId
        })
      })
      setInterval(heartbeat, HEARTBEAT_INTERVAL);
      heartbeat();
    }
    
    // 每 10s 检查一次,超过15s没有心跳则认为已经 crash
    const CHECK_CRASH_INTERVAL = 10 * 1000 
    const CRASH_THRESHOLD = 15 * 1000
    const pages = {}
    let timer
    function checkCrash() {
      const now = Date.now()
      for (var id in pages) {
        let page = pages[id]
        if ((now - page.t) > CRASH_THRESHOLD) {
          // 上报 crash
          delete pages[id]
        }
      }
      if (Object.keys(pages).length == 0) {
        clearInterval(timer)
        timer = null
      }
    }
    
    worker.addEventListener('message', (e) => {
      const data = e.data;
      if (data.type === 'heartbeat') {
        pages[data.id] = {
          t: Date.now()
        }
        if (!timer) {
          timer = setInterval(function () {
            checkCrash()
          }, CHECK_CRASH_INTERVAL)
        }
      } else if (data.type === 'unload') {
        delete pages[data.id]
      }
    })
    

小结

对于同源页面
- 广播模式:Broadcast Channe / Service Worker / LocalStorage + StorageEvent
- 共享存储模式:Shared Worker / IndexedDB / cookie
- 口口相传模式:url传参(window.open + window.opener)
- 基于服务端:Websocket / Comet / SSE 等

对于非同源页面:

  • 使用 iframe作为桥发送和监听消息
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值