# 页面通信

问题一:从页面 A 打开一个新的页面 B,B 页面关闭(包括意外崩溃),如何通知页面 A?

题目真实要求是:新打开的页面 B 页面关闭(包含意外崩溃)如何回传给页面 A。

拆分问题:

  1. B 页面正常关闭,B 页面如何通知 A 页面(涉及参数回传、参数监听)
  2. B 页面意外崩溃,比如线程直接被杀死,如何通知 A 页面(涉及监听页面崩溃)

我们应该分别作答。

# B 页面正常关闭

  1. 页面关闭时会触发的事件?

    页面关闭时会先执行 window.onbeforeunload ,然后执行 window.onunload,我们可以在这两个事件中设置回调。

  2. 如何传参?

    如何跳转到一个已经打开的页面 (opens new window)

    window.open 跳转到一个已经打开的页面,url 上面带上需要用到的参数。

  3. 成功传参后如何监听 url 的变化?

    如果路由是采用 hash 模式,则使用 onhashchange 的方式可以监听到路由 # 之后的变化,

    如果是 history 模式,则监听 popstate 的变化

同时,如果两个页面如果是同源的情况下,也可以通过监听 localStorage 的方式达到两个页面之间的通信。

# B 页面意外崩溃

B 页面意外崩溃,这时候 JS 线程都已经崩溃了,如何传递通知呢?

Logging Information on Browser Crashes (opens new window)

简单来说:在网页 onload 事件设置一个 pending 状态,beforeunload 事件下改变这个状态为 exit,如果二次访问这个页面,查看这个状态,如果这个状态是 pending,则说明上一次关闭的时候是意外崩溃。这个状态可以存在 localStorage。

如果是想实时通知 A 页面,我们可以使用 Service Worker 来实现网页崩溃的监控。

  1. Service Worker 有自己独立的线程,与网页区分开,网页崩溃的情况下,一般 Service Worker 并不会崩溃。
  2. Service Worker 的生命周期一般比网页要长,所以可以用来监控网页的状态。
  3. 网页可以通过 navigator.serviceWorker.controller.postMessage API 向掌管自己的 Service Worker 发送消息。

具体是现实采用 心跳检测

  1. 网页 B 每 5s 给自己的 Service Worker 发送一次心跳,记录一个状态并更新时间戳,如果正常关闭的时候,通知 Service Worker 清除这个状态;
  2. 如果网页崩溃了,状态和时间戳都不会变更,Service Worker 定时轮询检查状态,一旦发现状态不对且时间戳已经很长没有更新了,则说明网页已经崩溃。
  3. Service Worker 通过 postMessage 与页面进行通信(Service Worker (opens new window)

# DOM 监听

自己如何实现 Vue 的数据监听,能够检测到 DOM 新增加绑定的属性吗?

知道 Vue2 原理的小伙伴应该都知道,数据双向绑定主要依赖于 Object.defineProperty 对数据的劫持,它有 get 和 set 方法,可以监听到对象属性的读取和设置。

# Object.defineProperty 可以监听 DOM 属性吗?

Object.defineProperty 监听的目标是对象,DOM 元素的属性集合 [dom.attributes] 也是对象,当然可以被监听。

其中要注意的是,例如:style 属性也是一个属性集合,它的子属性并不能被监听。

原因是因为 Object.defineProperty 的不足:

  1. 无法监听数组的变化:数组这些方法是无法触发 set:push、pop、shift、unshift、splice、sort、reverse。Vue 中之所以能监听是因为对这些方法进行了重写(hack)。
  2. 只能监听属性,监听的不是对象的本身,需要对对象的每个属性进行遍历。对原本不在对象中的属性难以监听。Vue 中 使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。

# 如何监听一个新创建的属性呢?

手动对新创建的属性进行监听。

Vue.set 的原理:

当一个数据为响应式时,vue 会给该数据添加一个 __ob__ 属性,因此可以通过判断 target 对象是否存在 __ob__ 属性来判断 target 是否是响应式数据。当 target 是非响应式数据的时候,我们就按照普通对象添加属性的方式来处理;当 target 对象是响应式数据时,我们将 target 的属性 key 也设置为响应式并手动触发通知其他属性值的更新。

defineReactive(ob.value, key, val)
ob.dep.notify()

# 除了监听滚轮,还有什么懒加载的方法?

交叉观察者

利用 IntersectionObserver 接口提供的一种异步观察目标元素与其祖先元素或顶级文档视口交叉状态的方法。