你不知道的4个JavaScript API
在本文中,将介绍一些鲜为人知但却非常有用的 API,如:
Page Visibility API
Web Share API
Broadcast Channel API
Internationalization API
我们将一起看看它们是什么,我们应该在哪里使用它们,以及如何使用它们。
Page Visibility API
这是一个鲜为人知的 web API,在JS 现状调查[1]中,它的认知度排名倒数第四。它可以让你知道用户何时离开了页面。准确地说,只要页面的可见性状态发生变化,无论是用户最小化、最大化窗口还是切换标签页,该 API 都会触发一个事件。
在过去,你不得不使用一些噱头来了解用户是否切换了标签页或最小化了窗口。最流行的方式是使用blur
和focus
浏览器事件。使用这些事件会导致类似下面情况的发生:
window.addEventListener("focus", function () {
// User is back on the page
// Do Something
});
window.addEventListener("blur", function () {
// User left the page
// Do Something
});
前面的代码可以工作,但是不符合预期。因为blur
事件是在页面失去焦点时触发的,所以当用户点击搜索栏、alert
对话框、控制台或窗口边框时,它就会被触发。所以,blur
和focus
只告诉我们页面是否被激活,但不告诉我们页面的内容是否被隐藏或显示。
什么时候使用
一般来说,我们想要使用Page Visibility API
,是希望用来停止不必要的程序。比如说当用户没有看到页面时,或者执行后台操作时。具体的场景可以是:
- 当用户离开页面时暂停视频、图像旋转或动画;
- 如果页面显示来自 API 的实时数据,在用户离开时暂时停止实时显示的行为;
- 发送用户分析报告。
如何使用
Page Visibility API
带来了两个属性和一个事件,用于访问页面可见性状态:
document.hidden
:该属性是全局可见并且只读。尽量避免使用该属性,因为现在已经被废弃了。当访问该属性时,如果页面是隐藏状态则返回true
,如果页面是可见状态则返回false
。document.visibilityState
:该属性是document.hidden
更新后的版本。当访问该属性时,会根据页面的可见性状态返回四个可能的值:visible
:该页面是可见的,或者准确地说,它没有被最小化,也不在另一个标签页。hidden
:该页面不可见,它是最小化的,或者在另一个标签页。prerender
:这是一个可见页面在预渲染时的初始状态。一个页面的可见性状态可以从prerender
开始,然后改变到另一个状态,但它不能从另一个状态改变到prerender
。unloaded
:该页面正在从内存中卸载。
visibilitychange
:这是一个由document
对象提供的事件,当页面的visibilityState
发生变化时被触发。
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
// page is visible
} else {
// page is hidden
}
});
为了了解如何使用Page Visibility API
,让我们用该特性来实现当用户离开页面时,暂停视频以及停止从 API 获取资源。首先,我将使用vite.js
,它是一个快速启动新项目的神奇工具:
npm create vite@latest unknown-web-apis
当被要求选择一个框架时,选择vanilla
来创建一个vanilla
javascript 项目。完成之后,前往新文件夹,安装必要的npm
包并启动开发服务器:
cd unknown-web-apis
npm install
npm run dev
打开localhost:3000/[2],你将看到你的 Vite 项目启动和运行!
vite-new-project.png
首先,我们直接跳转到/main.js
文件并删除所有样板代码。其次,打开/index.html
,在id
为#app
的div
标签内部添加一个video
元素,上面可以添加你想添加的任意视频文件。这里我使用了一只正在跳舞的耀西。
<div id="app">
<video controls id="video">
<source src="./yoshi.mp4" />
</video>
</div>
dancing-Yoshi.png
回到/main.js
,我们将向document
对象添加一个事件监听器,用来监听visibilitychange
事件。然后当页面显示或隐藏时,我们可以访问document.visibilityState
属性的值。
document.addEventListener("visibilitychange", () => {
console.log(document.visibilityState);
});
你可以前往页面的控制台,当最小化窗口或者切换到另一个标签页时,查看页面可见性状态。现在,在事件监听器内部,我们可以检查document.visibilityState
属性,当属性值为hidden
时暂停视频,当属性值为visible
时播放视频。当然,我们首先要使用document.querySelector()
选择video
元素。
const video = document.querySelector("#video");
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
video.play();
} else {
video.pause();
}
});
现在,只要用户离开页面,视频就会停止。另一个使用Page Visibility API
的场景是,当用户没有查看页面时,停止获取不必要的资源。为了看效果,我们将编写一个函数不间断地从quotable.io[3] API 获取随机引用,并当页面隐藏时暂停该行为。首先,我们将在/index.html
创建一个新的div
标签来存储引用。
<div id="app">
<video controls id="video">
<source src="./yoshi.mp4" />
</video>
<div id="quote"></div>
</div>
回到/main.js
,我们使用Fetch API[4]发起对quotable.io
端点**https://api.quotable.io/random**[5] 的调用,然后将结果插入到quote
div 中。
const quote = document.querySelector("#quote");
const getQuote = async () => {
try {
const response = await fetch("<https://api.quotable.io/random>");
const { content, author, dateAdded } = await response.json();
const parsedQuote = ` <q>${content}</q> <br> <p>- ${author}</p><br> <p>Added on ${dateAdded}</p>`;
quote.innerHTML = parsedQuote;
} catch (error) {
console.error(error);
}
};
让我们简单地解释一下此处发生了什么。首先我们从 DOM 中选中了quote
元素。然后声明getQuote
函数,该函数是一个异步函数,允许我们使用await
关键字进行等待,直到从 API 中获取到数据。获取的数据是 JSON 格式的,因此我们再次使用await
关键字来等待,直到数据被解析为 JavaScript 对象。
quotable.io
的 API 为我们提供了content
、author
和dateAdded
等属性,我们把这些属性注入并显示在quote
div 中。这样做是没问题的,但是引用只会获取一次,因此我们可以使用setInterval()
每 10 秒来调用一次函数。
const quote = document.querySelector("#quote");
const getQuote = async () => {
try {
const response = await fetch("<https://api.quotable.io/random>");
const { content, author, dateAdded } = await response.json();
const parsedQuote = ` <q>${content}</q> <br> <p>- ${author}</p><br> <p>Added on ${dateAdded}</p>`;
quote.innerHTML = parsedQuote;
} catch (error) {
console.error(error);
}
};
getQuote();
setInterval(getQuote, 10000);
如果用户最小化窗口或者切换标签页,该页面仍然会获取引用,创建没有必要的网络加载。为了解决这个问题,在获取引用之前我们可以检查当前页面是否可见。
const getQuote = async () => {
if (document.visibilityState === "visible") {
try {
const response = await fetch("<https://api.quotable.io/random>");
const { content, author, dateAdded } = await response.json();
const parsedQuote = `
<q>${content}</q> <br>
<p>- ${author}</p><br>
<p>Added on ${dateAdded}</p>`;
quote.innerHTML = parsedQuote;
} catch (error) {
console.error(error);
}
}
};
getQuote();
setInterval(getQuote, 10000);
现在,我们只会在页面对用户可见的情况下获取引用。
兼容性
广泛支持[6]
Web Share API
这是什么
Web Share API
也是最不为人所知的 API 之一,但却非常有用。它可以让你访问操作系统的原生分享机制,这对移动端用户特别有用。有了这个 API,你可以分享文本、链接和文件,而不需要创建你自己的分享机制或使用第三方的分享机制。
什么时候使用
用途已经不言自明。你可以用它将你的页面内容分享到社交媒体上,或将其复制到用户的剪贴板上。
如何使用
Web Share API
赋予我们两个接口来访问用户的分享系统:
navigator.canShare()
:接受你想分享的数据作为参数,并根据其是否可分享,来返回一个布尔参数。navigator.share()
:返回一个promise
,如果分享成功的话,该promise
将会resolve
。该接口会调用原生分享机制,并接收你想分享的数据作为参数。注意,它只能在用户按下链接或按钮时调用。也就是说,它需要transient activation[7](瞬时激活)。分享数据是一个可以具有以下属性的对象:url
:要分享的链接text
:要分享的文本title
:要分享的标题files
:表示要分享的File
对象数组
为了了解如何使用该 API,我们将回收先前的用例,做一个选项使用Web Sharing API
来分享我们的引用。首先,我们必须在/index.html
新增一个分享按钮:
<div id="app">
<video controls id="video">
<source src="./yoshi.mp4" />
</video>
<div id="quote"></div>
<button type="button" id="share-button">Share Quote</button>
</div>
前往/main.js
从 DOM 中选择分享按钮。然后,创建async
函数来分享想要分享的数据。
const shareButton = document.querySelector("#share-button");
const shareQuote = async (shareData) => {
try {
await navigator.share(shareData);
} catch (error) {
console.error(error);
}
};
现在,我们可以为shareButton
元素添加click
事件监听器,以此来调用shareQuote
函数。shareData.text
的值会是quote.textContent
属性,shareData.url
的值会是页面的 URL,也就是location.href
属性。
const shareButton = document.querySelector("#share-button");
const shareQuote = async (shareData) => {
try {
await navigator.share(shareData);
} catch (error) {
console.error(error);
}
};
shareButton.addEventListener("click", () => {
let shareData = {
title: "A Beautiful Quote",
text: quote.textContent,
url: location.href,
};
shareQuote(shareData);
});
现在你可以通过你的原生操作系统与任何人分享你的引用。然而,需要注意的是,Web Share API
只有在上下文安全的情况下才会起作用,也就是说,页面是通过https://
或wss://
URLs 提供的。
兼容性
基本不支持[8]
Broadcast Channel API
这是什么
我想谈论的另一个 API 是Broadcast Channel API
。它允许浏览器上下文互相发送和接收基本数据。浏览器上下文是指标签页、窗口、iframe
等元素,或任何可以显示页面的地方。出于安全考量,浏览器上下文之间的通信是不被允许的,除非它们是同源的并使用Broadcast Channel API
。对于两个同源的浏览器上下文,它们的 URL 必须有相同的协议(如http/https
)、域(如example.com
)和端口(如:8080
)。
什么时候使用
Broadcast Channel API
通常用于在不同的标签页和窗口之间保持页面状态同步,以提高用户体验或出于安全原因考虑。它也可以用来知道一个服务在另一个标签页或窗口中何时完成。使用场景有:
- 在所有标签页上登录或注销用户。
- 检测资源何时上传,并在所有页面中展示它。
- 指示
service worker
做一些幕后工作。
如何使用
Broadcast Channel API
涉及一个BroadcastChannel
对象,该对象可用于向其他上下文发送信息。构造函数只有一个参数:作为标识符的字符串,该标识符从其他上下文连接到频道。
const broadcast = new BroadcastChannel("new_channel");
一旦我们在两个上下文中创建了具有相同标识符的BroadcastChannel
对象,这个新的BroadcastChannel
对象将有两个可用的方法来开始进行通信:
BroadcastChannel.postMessage()
:在所有连接的上下文中发送消息。它接受任意类型的对象作为其唯一的参数,因此你可以发送各种各样的数据。broadcast.postMessage("Example message");
BroadcastChannel.close()
:关闭通道,向浏览器表明它不会再收到任何信息,这样它就可以把这些信息收集到垃圾回收中。
为了接受信息,BroadcastChannel
有一个message
事件,我们可以使用addEventListener
或其onmessage
属性来监听。message
事件有一个data
属性,包含发送的数据和其他属性,以识别发送消息的上下文,如origin
、lastEventId
、source
和ports
。
broadcast.onmessage = ({ data, origin }) => {
console.log(`${origin} says ${data}`);
};
让我们看看如何通过使用先前的例子来使用Broadcast Channel API
。我们的目标是制作另一个具有同源的浏览器上下文,并在两个上下文中展示相同的引用。为了做到这一点,我们将创建一个名为new-origin
的新文件夹,里面有一个新的/index.html
和/main.js
文件。
/new-origin/index.html
将是一个新的 HTML 模板,里面有一个#quote
div:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="../favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="quote"></div>
<script type="module" src="./main.js"></script>
</body>
</html>
在/new-origin/main.js
文件中,我们将创建一个新的broadcast channel
,并从 DOM 中选择#quote
元素:
const broadcast = new BroadcastChannel("quote_channel");
const quote = document.querySelector("#quote");
在先前的/main.js
文件中,我们将创建新的BroadcastChannel
对象,并连接到"quote_channel"
。我们还将修改getQuote
函数,将引用作为消息发送到其他上下文。
const broadcast = new BroadcastChannel("quote_channel");
//...
const getQuote = async () => {
try {
const response = await fetch("<https://api.quotable.io/random>");
const { content, author, dateAdded } = await response.json();
const parsedQuote = ` <q>${content}</q> <br> <p>- ${author}</p><br> <p>Added on ${dateAdded}</p>`;
quote.innerHTML = parsedQuote;
broadcast.postMessage(parsedQuote);
} catch (error) {
console.error(error);
}
};
回到/new-origin/main.js
文件,我们将监听message
事件并在每次发送新的引用时改变quote.innerHTML
。
const broadcast = new BroadcastChannel("quote_channel");
const quote = document.querySelector("#quote");
broadcast.onmessage = ({ data }) => {
quote.innerHTML = data;
};
现在你可以看到http://localhost:3000/new-origin/
中的引用是如何变化为http://localhost:3000
中的引用的。你也可以注意到,当http://localhost:3000
标签被隐藏时,引用并没有改变,因为它只在其页面可见性状态为可见时才会去获取引用。
兼容性
广泛支持[9]
Internationalization API
这是什么
在开发一个网页或应用程序时,需要将其内容翻译成其他语言以覆盖更广泛的受众是非常常见的。然而,仅仅将你的网页文本翻译成你所需要的任何语言,并不足以使你的内容对讲该语言的人可用,因为像日期、数字、单位等东西在不同国家是不同的,可能会给你的用户带来困惑。
我们假设你想在你的网页上展示日期"2022 年 11 月 8 日",就像"11/8/22"。根据读者所在的国家,这些数据可以用三种不同的方式来阅读:
- "11/8/2022"或美国用户的 MM/DD/YY。
- "8/11/2022"或欧洲和拉美用户的 DD/MM/YY。
- "2011/8/22"或日本、中国和加拿大用户的 YY/MM/DD。
这就是Internationalization API
(或I18n API
)来解决不同语言和地区的格式问题的地方。I18n API
是一个了不起的工具,有多种用途,但我们不会深入研究,以免使本文过于复杂。
如何使用
I18n API
使用locale
标识符来起作用。locale
标识符是一个字符串,用来表示用户的语言、城市、地区、方言以及其他偏好。准确的说,locale
标识符是一个字符串,由连字符分隔的子标签组成。子标签代表了用户偏好,比如语言、国家、地区或文字,并以以下方式格式化:
"zh"
:中文(语言);"zh-Hant"
:用繁体字(文字)书写的中文(语言);"zh-Hant-TW"
:在台湾(地区)使用的繁体字(文字)书写的中文(语言)。
还有更多的子标签来解决更多用户的偏好(如果你想了解更多,你可以查看RFC[10]对语言标签的定义),但简而言之,I18n API
使用这些locale
标识符来知道如何格式化所有语言敏感的数据。
更确切地说,I18n API
提供了一个Intl
对象,它带来了一堆专门的构造函数来处理对语言敏感的数据。在我看来,一些对国际化最有用的Intl
构造函数是:
Intl.DateTimeFormat()
:用于格式化日期和时间。Intl.DisplayNames()
:用于格式化语言、地区和文字显示名字。Intl.Locale()
:用于构建和操作locale
标识符标签。Intl.NumberFormat()
:用于格式化数字。Intl.RelativeTimeFormat()
:用于格式化相对时间描述。
在我们的例子中,我们将重点关注Intl.DateTimeFormat()
构造函数,以根据用户的区域设置来格式化引用的dateAdded
属性。Intl.DateTimeFormat()
构造函数接收两个参数:定义日期格式约定的locale
字符串和用于自定义日期格式的options
对象。
创建的Intl.DateTimeFormat()
对象有一个format()
方法,它接收两个参数:我们要格式化的Date
对象和用于自定义如何显示格式化日期的options
对象。
const logDate = (locale) => {
const newDate = new Date("2022-10-24"); // YY/MM/DD
const dateTime = new Intl.DateTimeFormat(locale, { timeZone: "UTC" });
const formatedDate = dateTime.format(newDate);
console.log(formatedDate);
};
logDate("en-US"); // 10/24/2022
logDate("de-DE"); // 24.10.2022
logDate("zh-TW"); // 2022/10/24
注意:在Intl.DateTimeFormat
构造函数的options
参数中,我们将timeZone
属性设置为"UTC"
,这样日期就不会被格式化为用户的当地时间。在我的例子中,没有timeZone
的选项,日期被解析为 "10/23/2022"。
正如你所看到的,dateTime.format()
根据locale
的日期格式约定改变日期。我们可以使用navigator.language
全局属性在引用的日期上实现这一行为,该全局属性具有用户的首选locale
设置。为此,我们将创建一个新的函数,接收一个日期字符串(YYYY-MM-DD 格式),并根据用户的locale
返回格式化的日期。
const formatDate = (dateString) => {
const date = new Date(dateString);
const locale = navigator.language;
const dateTimeFormat = new Intl.DateTimeFormat(locale, { timeZone: "UTC" });
return dateTimeFormat.format(date);
};
我们可以在getQuote()
函数中添加这个函数来解析dateAdded
日期。
const getQuote = async () => {
if (document.visibilityState === "visible") {
try {
const response = await fetch("<https://api.quotable.io/random>");
const { content, author, dateAdded } = await response.json();
const parsedQuote = `
<q>${content}</q> <br>
<p>- ${author}</p><br>
<p>Added on ${formatDate(dateAdded)}</p>`;
quote.innerHTML = parsedQuote;
broadcast.postMessage(parsedQuote);
} catch (error) {
console.error(error);
}
}
};
有了这个,我们的引用就被本地化为用户的首选语言了!在我的例子中,我的navigator.language
值是"en"
,所以我的日期被格式化为 MM/DD/YY。
兼容性
广泛支持[11]
总结
读完这篇文章后,你现在可以灵活地了解这些 API 的存在以及如何使用它们。尽管它们在 JS 现状调查中的认知度排名最后,但它们非常有用,知道如何使用它们肯定会提高你的开发经验。这些强大的 API 并不为人所知,这意味着还有一些你我都不知道的有用的 API,所以现在是探索并找到那个可以简化你的代码,并为你节省大量开发时间的 API 的最佳时机。
参考资料
[1]JS 现状调查:https://2021.stateofjs.com/en-US/features/
[2]localhost:3000/:http://localhost:3000/
[3]quotable.io:https://github.com/lukePeavey/quotable
[6]广泛支持:https://caniuse.com/pagevisibility
[7]transient activation:https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation
[8]基本不支持:https://caniuse.com/web-share
[9]广泛支持:https://caniuse.com/mdn-api_broadcastchannel_name