# Notification 接口

> Notification() 构造函数及其对象：实例属性、click、close 与 error 事件、静态成员，以及移动端浏览器为何在构造函数上抛出异常。

`Notification` 是代表一条已显示通知的对象。`new Notification(title, options)` 从页面或专用 worker 创建并立即显示一条非持久通知；同一个接口在持久通知的场景下以 `event.notification` 交给 Service Worker。它的实例属性映射创建时传入的选项，事件则报告显示、激活、关闭和失败。

## 语法

```js
new Notification(title)
new Notification(title, options)
notification.close()
Notification.permission
Notification.maxActions
```

构造函数暴露在 `Window` 以及专用和共享 worker 上，在 `ServiceWorkerGlobalScope` 内抛出，那里的替代是 `self.registration.showNotification()`。桌面端支持为 Chrome 20、Firefox 22、Safari 7、Edge 14（BCD `api.Notification.Notification`）。移动端构造函数虽然存在，但在 Chrome for Android、Samsung Internet 和 iOS 上的 Safari 中无法使用（细节见「异常」），所以 PWA 把它当作仅限桌面的便利。

## 成员

实例成员均为只读，映射构造函数的 `options`。

| 成员 | 类型 | 含义 |
|---|---|---|
| `title`、`body`、`icon`、`image`、`badge` | string | 创建时传入的文本与图片 URL。缺省的选项读作 `""`。 |
| `tag` | string | 替换键。同一源上后来带相同 `tag` 的通知会替换这一条。 |
| `data` | any | 作为 `options.data` 传入的可结构化克隆载荷；没有时为 `null`。 |
| `dir`、`lang` | string | 文字方向（`"auto"`、`"ltr"`、`"rtl"`）与 BCP 47 语言标签。 |
| `requireInteraction`、`renotify`、`silent` | boolean（`silent` 可为 `null`） | 行为标志；各引擎支持情况见 [Notifications API](/zh/reference/notifications/notifications-api/) 的参数表。 |
| `timestamp` | number | 通知所关联的时间，自纪元起的毫秒数；默认为创建时间。 |
| `vibrate` | 数字数组 | 振动模式，Android 上的 Chromium。 |
| `actions` | array | 非持久通知上恒为空数组；Service Worker 内的 `event.notification` 上有内容。 |
| `navigate` | string | 激活时打开的 URL，Safari 18.4。 |

事件有 `show`（已显示；iOS 上的 Safari 不触发）、`click`、`close` 与 `error`（显示失败，例如权限在检查与调用之间被撤销）。静态的 `Notification.permission` 见 [Notification.requestPermission()](/zh/reference/notifications/permissions/)；`Notification.maxActions` 返回引擎会显示的操作按钮数量，在 Chromium 中为 `2`（[notification.mojom](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/public/mojom/notifications/notification.mojom) 中的 `kMaximumActions`，chromium.googlesource.com），在 Safari 中为 `undefined`，因为该静态成员不存在。

## 异常

| 异常 | 触发条件 |
|---|---|
| `TypeError` | 在 Service Worker 内调用构造函数。Chromium 的报错文本为 `Illegal constructor. Use ServiceWorkerRegistration.showNotification() instead.`（[notification.cc](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/notifications/notification.cc)，chromium.googlesource.com）。 |
| `TypeError` | 在 Chrome for Android 或 Samsung Internet 中从任何上下文调用构造函数；BCD 记录该构造函数在那里总是抛出（`api.Notification.Notification`，`chrome_android`）。报错文本就是同一句 `Illegal constructor`。 |
| `TypeError` | `options.actions` 非空。Chromium 的报错文本为 `Actions are only supported for persistent notifications shown using ServiceWorkerRegistration.showNotification().`。 |
| `TypeError` | `renotify` 为 `true` 但 `tag` 为空，或 `silent` 为 `true` 同时设置了 `vibrate`。 |
| `ReferenceError` | iOS 与 iPadOS 上 Safari 的浏览器标签页：除非页面是 manifest 带非默认 `display` 的主屏幕 Web App，否则 `Notification` 标识符不存在。 |

权限被拒或未决定不是异常。构造函数返回一个 `Notification`，其 `error` 事件触发而什么都不显示，这就是调用前要检查 `Notification.permission` 的原因。

## 平台可用性

该接口在桌面端普遍存在，在移动端要么缺失、要么没有可用的构造函数：Chrome for Android 42 与 Samsung Internet 4 只为 Service Worker 用途暴露它，iOS 上的 Safari 16.4 只在已安装的 Web App 内暴露它，Android WebView 没有（BCD `api.Notification`）。Chrome 自 49 起不在无痕模式显示通知。接口存在的地方实例成员和事件都受支持，但 iOS 上没有 `show`，而 `actions`、`badge`、`image`、`renotify`、`timestamp` 与 `vibrate` 仅限 Chromium。

## 示例

第一个示例天然只适用于桌面；第二和第三个是在 Android 和 iOS 上同样可用的形态。

### 在桌面端显示非持久通知

权限已授予时，构造函数立即显示。`click` 处理函数聚焦窗口并关闭通知；不调用 `close()` 的话，操作系统会保留它直到自己的超时。

```js
function showOrderUpdate(orderId) {
  const notification = new Notification('订单已发货', {
    body: `订单 #${orderId} 正在路上。`,
    icon: '/icons/parcel.png',
    tag: `order-${orderId}`,
    data: { orderId },
  });
  notification.addEventListener('click', () => {
    window.focus();
    location.hash = `#order-${notification.data.orderId}`;
    notification.close();
  });
  notification.addEventListener('error', () => console.warn('通知未显示'));
}
```

浏览器在显示几秒后自行关闭非持久通知，并把它排除在通知中心之外，所以这种形态适合「已保存」之类的确认，而不适合用户会回头查看的消息。

### 在 Service Worker 中读取持久通知

在 `notificationclick` 处理函数里，同一个接口以 `event.notification` 到达，此时 `actions` 有内容且 `close()` 可用。

```js
self.addEventListener('notificationclick', (event) => {
  const { tag, data, actions } = event.notification;
  event.notification.close();
  if (event.action === 'archive') {
    event.waitUntil(fetch(`/api/threads/${data.threadId}/archive`, { method: 'POST' }));
    return;
  }
  event.waitUntil(self.clients.openWindow(`/inbox#${tag}`));
});
```

这里的 `actions.length` 至多为 `Notification.maxActions`；超出该上限的按钮在显示通知时已被静默丢弃。

### 检测构造函数是否可用

可靠的探测是在确认接口与权限之后，在 `try` 里构造。任何失败都转到持久路径，而它在 Android 和 iOS 上也是唯一的路径。

```js
async function show(title, options) {
  if (!('Notification' in window) || Notification.permission !== 'granted') {
    return showInPage(title, options.body); // 没有接口，或没有权限
  }
  try {
    return new Notification(title, options);
  } catch (err) {
    if (err.name !== 'TypeError') throw err;
    const registration = await navigator.serviceWorker.getRegistration();
    if (!registration?.active) return showInPage(title, options.body); // 没有可退回的对象
    return registration.showNotification(title, options);
  }
}
```

生产代码通常完全跳过构造函数，在桌面和移动端都调用 `showNotification()`；这个探测在没有注册 Service Worker 的场合才有用。

:::observed
在 Chrome for Android 的 Console 里执行 `new Notification('x')` 会抛出 `Uncaught TypeError: Failed to construct 'Notification': Illegal constructor. Use ServiceWorkerRegistration.showNotification() instead.`，即 Chromium [notification.cc](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/notifications/notification.cc)（chromium.googlesource.com）开头的那个字符串；同一源文件在桌面端对非空 `options.actions` 抛出 `Actions are only supported for persistent notifications shown using ServiceWorkerRegistration.showNotification().`。`Notification.maxActions` 在各平台的 Chrome 中都求值为 `2`，即 `notification.mojom` 中 `kMaximumActions` 的值。
:::

## 另请参阅

- [Notifications API Standard: Notification interface](https://notifications.spec.whatwg.org/#notification)（whatwg.org）
- [Notification: Notification() constructor](https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification)（developer.mozilla.org）
- [Notifications API](/zh/reference/notifications/notifications-api/)
- [Notification.requestPermission()](/zh/reference/notifications/permissions/)
- [通知的 actions 与 badge 选项](/zh/reference/notifications/notification-actions-badge/)