EventSource API

EventSource 接口是 web 内容与服务器发送事件通信的接口。

一个 EventSource 实例会对 HTTP 服务器开启一个持久化的连接,以 text/event-stream 格式发送事件,此连接会一直保持开启直到通过调用 EventSource.close() 关闭。一旦连接开启,来自服务端传入的消息会以事件的形式分发至客户端中,如果接收消息中有一个 event 字段,触发的事件与 event 字段的值相同。如果不存在 event 字段,则将触发通用的 message 事件。

与 WebSocket 不同的是,服务器发送事件是单向的。数据消息只能从服务端发送到客户端(如用户的浏览器)。这使其成为不需要从客户端往服务器发送消息的情况下的最佳选择。

构造函数

EventSource()

创建一个新的 EventSource,用于从指定的 URL 接收服务器发送事件,可以选择开启凭据模式。

用法: cosnt pc = new EventSource(url, configuration);

url 表示远程资源的位置,configuration 为配置新连接提供选项,目前只有一个可选项 withCredentials,默认为 false,指示 CORS 是否应包含凭据。

实例属性

此接口从其父接口 EventTarget 继承属性。

  • EventSource.readyState 只读
    一个代表连接状态的数字。可能值是 CONNECTING(0)、OPEN(1)或 CLOSED(2)。

  • EventSource.url 只读
    一个表示事件源的 URL 字符串。

  • EventSource.withCredentials 只读
    一个布尔值,表示 EventSource 对象是否使用跨域资源共享(CORS)凭据来实例化(true),或者不使用(false,即默认值)。

实例方法

此接口从其父接口 EventTarget 继承方法。

  • EventSource.close()
    关闭连接(如果有),并将 readyState 属性设置为 CLOSED。如果连接已经关闭,则该方法不执行任何操作。

事件

  • error
    在事件源连接未能打开时触发。

  • message
    在从事件源接收到数据时触发。

  • open
    在与事件源的连接打开时触发。

此外,事件源本身可以发送具有 event 字段的消息,这将创建一个以该值为键的特定事件。

示例

在这个基本的例子中,创建了一个 EventSource 来从服务器接收未命名的事件;一个名为 sse.php 的页面负责生成这些事件。

1
2
3
4
5
6
7
8
9
10
const evtSource = new EventSource("sse.php?message=hello");
const eventList = document.querySelector("ul");

evtSource.onmessage = (e) => {
const newElement = document.createElement("li");

newElement.textContent = `message: ${e.data}`;
eventList.appendChild(newElement);
evtSource.close();
};

每个接收到的事件都会导致我们的 EventSource 对象的 onmessage 事件处理程序运行。它会创建一个新的 <li> 元素,并将消息的数据写入其中,然后将新元素附加到文档中已有的列表元素中。

要监听具名事件,你需要为每种类型的事件添加一个监听器。

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
const sse = new EventSource("/api/v1/sse");

/*
* 这将仅监听类似下面的事件
*
* event: notice
* data: useful data
* id: someid
*/
sse.addEventListener("notice", (e) => {
console.log(e.data);
});

/*
* 同理,以下代码将监听具有字段 `event: update` 的事件
*/
sse.addEventListener("update", (e) => {
console.log(e.data);
});

/*
* “message”事件是一个特例,因为它可以捕获没有 event 字段的事件,
* 以及具有特定类型 `event:message` 的事件。
* 它不会触发任何其他类型的事件。
*/
sse.addEventListener("message", (e) => {
console.log(e.data);
});

注意

  1. 浏览器同域名下的HTTP请求数量是有限制的(例如Chrome浏览器的限制为6个),所以如果打开多个选项卡,这种长连接可能会因为占满限制名额而导致无法发送新的HTTP请求。

  2. EventSource API 发送的HTTP请求为GET类型的,如果想要发送POST类型的HTTP请求,或者修改HTTP请求的headers,可以使用 @microsoft/fetch-event-source 库,该库对这种情况进行了polyfill,使用方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import { fetchEventSource } from '@microsoft/fetch-event-source';
    fetchEventSource(url, {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    Accept: '*/*',
    Connection: 'keep-alive',
    },
    body: JSON.stringify({
    messages: [{
    content: 'Hello.',
    role: 'user',
    }],
    }),
    onmessage(event) {
    console.log(`EventSource onmessage ${Date.now()}: ${event.data}`);
    },
    onerror() {
    console.log('EventSource onerror');
    },
    });

    类似的库还有 event-source-polyfill 库。

  3. 针对问答式的请求,服务器端返回的文本数据为流式时,EventSource API 相比 WebSocket 更加简单便捷,chatGPT 类问答服务多采用 EventSource API 来实现。

DOM中的高度、宽度与距离

HTMLElement/Element 属性

offset

HTMLElement.offsetWidth 属性表示元素的布局宽度。该属性包括元素的内容宽度(content-width)、竖直方向滚动条宽度(scrollbar-width)(如果存在的话)、水平方向的内边距(padding-left padding-right)以及水平方向的边框(border-left-width border-right-width)。

HTMLElement.offsetHeight 属性表示元素的布局高度。该属性包括元素的内容高度(content-height)、水平方向滚动条高度(scrollbar-height)(如果存在的话)、竖直方向的内边距(padding-top padding-bottom)以及竖直方向的边框(border-top-width border-bottom-width)。

offsetWidth = 水平方向content-width + 左右padding + 左右border + 竖直方向滚动条scrollbar-width(如果存在的话)
offsetHeight = 竖直方向content-height + 上下padding + 上下border + 水平方向滚动条scrollbar-height(如果存在的话)

offset_inner

offsetWidth 和 offsetHeight 可以理解为一个 边框盒(border box) 的 width 和 height。

HTMLElement.offsetTop 属性表示元素的左上角相对于其 offsetParent 元素的顶部边框的距离。
HTMLElement.offsetLeft 属性表示元素左上角相对于其 offsetParent 元素的左边框的距离。

HTMLElement.offsetParent 返回一个指向最近的(指包含层级上的最近)包含该元素的定位元素或者最近的 table,td,th,body 元素。当元素的 style.display 设置为 “none” 时,或者该元素的 style.position 被设为 “fixed” 时,offsetParent 返回 null

client

Element.clientWidth 属性表示元素的内部宽度。该属性包括元素的内容宽度(content-width)和内边距(padding),但不包括竖直方向滚动条宽度(scrollbar-width)(如果存在的话),边框(border)和外边距(margin)。

Element.clientHeight 属性表示元素的内部高度。该属性包括元素的内容宽度(content-width)和内边距(padding),但不包括水平方向滚动条高度(scrollbar-height)(如果存在的话),边框(border)和外边距(margin)。

clientWidth = 水平方向content-width + 左右padding
clientHeight = 竖直方向content-height + 上下padding

client_inner

offsetWidth 和 offsetHeight 可以理解为一个 内边距盒(padding box) 的 width 和 height。
padding-box 并不是一个标准的盒模型选项,这里只是为了方便理解

Element.clientTop 属性表示元素顶部边框的宽度。该属性仅包括元素的顶部边框宽度
(border-top-width)。

Element.clientLeft 属性表示元素的左边框宽度。该属性包括元素的左边框 (border-left-width),如果元素的文本方向是从右向左(RTL, right-to-left),并且由于内容溢出导致左边出现了一个垂直滚动条,则该属性包括滚动条的宽度。

scroll

Element.scrollWidth 属性表示元素在不使用滚动条的情况下适合视口中的所有内容所需的最小宽度。scrollWidth 的测量方式与 clientWidth 相同:包含元素的内边距(padding),但不包括边框(border),外边距(margin)或垂直滚动条(如果存在)。scrollWidth 也包括伪元素的宽度,例如 ::before 或 ::after 。如果元素的内容可以正常展示而不需要水平滚动条,则其 scrollWidth 等于 clientWidth 。

Element.scrollHeight 属性表示元素在不使用滚动条的情况下为了适应视口中所用内容所需的最小高度。scrollHeight 的测量方式与 clientHeight 相同:包括元素的内边距(padding),但不包括元素的边框(border)和外边距(margin)。scrollHeight 也包括 ::before 和 ::after 这样的伪元素的高度。 如果元素的内容不需要垂直滚动条就可以正常展示,则其 scrollHeight 等于 clientHeight。

scroll-height_inner

Element.scrollTop 属性可以获取或设置(和前面的属性不同,scrollTop 是一个可读写的属性)元素的顶部到可视内容顶部的距离。如果一个元素的内容没有产生垂直方向的滚动条,那么它的 scrollTop 值为 0 。

Element.scrollLeft 属性可以获取或设置元素的左边到可视内容左边的距离。如果一个元素的内容没有产生水平方向的滚动条,那么它的 scrollLeft 值为 0 。

style 属性

通过 HTMLElement.style 可以获取或设置元素的样式属性。但大部分时候,样式并不是直接存在于元素上而是通过继承等原因体现到的。我们可以通过 window.getComputedStyle(element, [pseudoElt]) 获取到已经解析的 CSS 样式,该方法返回一个 CSSStyleDeclaration 对象,当元素的样式更改时,它会自动更新。

HTMLElement.style.{top|left|right|bottom} 属性定义了定位元素的外边距边界与其包含块边界或正常位置之间的距离,非定位元素设置此属性无效。

以 top 为例,top 的效果取决于元素的 position 属性:

  • 当 position 设置为 absolute 或 fixed 时,top 属性指定了定位元素上外边距边界与其包含块上边界之间的偏移。
  • 当 position 设置为 relative 时,top 属性指定了元素的上边界离开其正常位置的偏移。
  • 当 position 设置为 sticky 时,如果元素在 viewport 里面,top 属性的效果和 position 为 relative 等同;如果元素在 viewport 外面,top 属性的效果和 position 为 fixed 等同。
  • 当 position 设置为 static 时,top 属性无效。

HTMLElement.style.margin{Left|Top|Right|Bottom} 属性定义了元素的外边距
HTMLElement.style.padding{Left|Top|Right|Bottom} 属性定义了元素的内边距
HTMLElement.style.border{Left|Top|Right|Bottom}Width 属性定义了元素边框的宽度

getBoundingClientRect 方法

Element.getBoundingClientRect() 方法返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口的位置。值得注意的是,以上像 offsetWidth 等只读属性返回的都是四舍五入(round)之后的整数数值,getBoundingClientRect 可以返回精确的小数数值。

该方法返回的 DOMRect 对象中的 widthheight 属性是包含了 paddingborder-width 的,而不仅仅是内容部分的宽度和高度。在 content-box 模型中,这两个属性值分别与元素的 width/height + padding + border-width 相等。而如果是 border-box,两个属性则直接与元素的 width 或 height 相等。

element-box_inner

getClientRects 方法

Element.getClientRects() 方法返回一个 DOMRect 集合,其包括元素内所有
边框盒(border box)的边界矩形(bounding rectangles)。大多数元素只有一个边框盒(border box),但是诸如一个多行的内联元素(例如多行的 span 元素)每行都有一个边框盒(border box)。

window 属性

window.innerWidth 属性表示以像素为单位的窗口的视口(viewport)宽度。如果垂直滚动条存在,则这个属性将包括它的宽度。

window.innerHeight 属性表示以像素为单位的窗口的视口(viewport)高度。如果水平滚动条存在,则这个属性将包括它的高度。

window.outerWidth 属性表示整个浏览器外部窗口的宽度。包括侧边栏(如果存在)、窗口镶边和调整窗口大小的边框。

window.outerHeight 属性表示整个浏览器外部窗口的高度。包括工具栏(如果存在)、窗口镶边和调整窗口大小的边框。

inner-outer_inner

一个显著的区分 inner 和 outer 的例子就是打开浏览器的 开发者工具,outerHeight 将会计算上开发者工具的高度,而 innerHeight 不会。

screen 属性

window.screen.width 属性表示屏幕的宽度。

window.screen.height 属性表示屏幕的高度。

window.screen.availWidth 属性表示浏览器窗口在屏幕上可占用的最大宽度。

window.screen.availHeight 属性表示浏览器窗口在屏幕上可占用的最大高度。小工具(Widgets),如任务栏或其他特殊的程序窗口,可能会减少浏览器窗口和其他应用程序能够利用的空间。

document 属性

document.documentElement 返回的是整个 html 文档
document.body 返回的是 body 元素

html 文档 与 body 元素都是有效的 HTMLElement , 所以,他们的属性也符合对应属性的定义:

document.documentElement.clientWidth 浏览器窗口可视区宽度(不包括浏览器控制台、菜单栏、工具栏、滚动条)
document.documentElement.clientHeight 浏览器窗口可视区高度(不包括浏览器控制台、菜单栏、工具栏、滚动条)

document.documentElement.offsetHeight 获取整个文档的高度(包含 body 的 margin )
document.documentElement.offsetWidth 获取整个文档的宽度(包含 body 的 margin )

document.documentElement.scrollTop 返回文档的滚动 top 方向的距离(当窗口发生滚动时值改变)
document.documentElement.scrollLeft 返回文档的滚动 left 方向的距离(当窗口发生滚动时值改变)

document.body.offsetHeight 获取整个 body 的高度(不包含 body 的 margin )
document.body.offsetWidth 获取整个 body 的宽度(不包含 body 的 margin )

distance_inner

Fetch API小抄

Fetch API 提供了一个 window.fetch() 接口来获取资源(包括网络资源)。Fetch API 可以用来替代 XMLHttpRequest ,可以在 Web Workers 中使用( WorkerGlobalScope.fetch() )。

阅读更多

Web Components 小抄

Web Components 是 W3C 在2014年提出的网页组件式开发的技术规范,旨在给 Web 开发者提供浏览器原生级别的组件能力。通过 Web Components 规范开发者可以自定义可重用的 Web 组件并引入到任意项目中而不必借助 Web 组件化框架,并且不必考虑组件内样式和变量污染的问题。

阅读更多

浏览器会话历史 history API

History API

History 接口允许操作浏览器的会话历史记录。

History API 的方法和属性

属性 是否只读 类型 作用
history.length 只读 整数 history 堆栈中会话的数量
history.state 只读 object 返回一个表示历史堆栈顶部的状态的值
history.scrollRestoration auto|manual (string) 允许Web应用程序在历史导航上显式地设置默认滚动恢复行为
方法 作用
history.back() 在浏览器历史记录里前往上一页, 用户可点击浏览器左上角的返回按钮模拟此方法. 等价于 history.go(-1)
history.forward() 在浏览器历史记录里前往下一页,用户可点击浏览器左上角的前进按钮模拟此方法. 等价于 history.go(1)
history.go() 入参为整数,通过当前页面的相对位置从浏览器历史记录加载页面。
history.pushState(state, title[, url]) 按指定的名称和URL(如果提供该参数)将数据 push 进会话历史栈,数据被 DOM 进行不透明处理;可以指定任何可以被序列化的 javascript 对象。
history.replaceState(state, title[, url]) 按指定的数据,名称和URL(如果提供该参数),更新历史栈上最新的入口。这个数据被 DOM 进行了不透明处理。你可以指定任何可以被序列化的 javascript 对象。

History API 用法

History API 最常见的用法是通过 pushState() 方法添加浏览器url历史(浏览器不会加载该 url ,甚至不会检查 url 是否真的存在),这样,在回退的时候就会加载 history 堆栈中的页面。在某种意义上,调用 pushState() 与 设置 window.location = "#foo" 类似,二者都会在当前页面创建并激活新的历史记录。但 pushState() 具有如下几条特点:

  • 新的 URL 可以是与当前 URL 同源的任意 URL 。相反,只有在修改哈希时,设置 window.location 才能是同一个 document。
  • 如果你不想改 URL ,就不用改。相反,设置 window.location = "#foo"; 在当前哈希不是 #foo 时,才能创建新的历史记录项。
  • 你可以将任意数据和新的历史记录项相关联。而基于哈希的方式,要把所有相关数据编码为短字符串。
  • 如果 标题(title 随后还会被浏览器所用到,那么这个数据是可以被使用的(哈希则不是)。
  • pushState() 不会触发 hashchange 事件,即使新的 URL 与旧的 URL 仅哈希不同也是如此

每当活动的历史记录项发生变化时, popstate 事件都会被传递给 window 对象。如果当前活动的历史记录项是被 pushState() 创建的,或者是由 replaceState() 改变的,那么 popstate 事件的状态属性 state 会包含一个当前历史记录状态对象的拷贝。

参考: https://developer.mozilla.org/zh-CN/docs/Web/API/History

DOM中的事件 Event

DOM中的事件 Event

Event 接口表示在 DOM 中出现的事件。

Event(事件)可以由三种方式触发:

  • 由用户触发动作,例如鼠标或键盘事件
  • 由 API 生成,例如指示动画已经完成运行的事件,视频已被暂停等
  • 通过脚本代码触发,例如对元素调用 HTMLElement.click() 方法,或者定义一些自定义事件,再使用 EventTarget.dispatchEvent() 方法将自定义事件派发往指定的目标(target)

有许多不同类型的事件,其中一些使用基于 Event 主接口的二次接口。Event 本身包含适用于所有事件的属性和方法。

很多DOM元素可以被设计接收(或者监听) 这些事件, 并且执行代码去响应(或者处理)它们。通过 EventTarget.addEventListener() 方法可以将事件处理函数绑定到不同的HTML elements上 (比如<button>, <div>, <span>等等) 。这种绑定事件处理函数的方式基本替换了老版本中使用 HTML event handler attributes(例如 HTMLElement.onclick = ... ) 来绑定事件处理函数的方式。除此之外,通过正确使用 removeEventListener() 方法,这些事件处理函数也能被移除。

阅读更多

Web Worker小抄

Web Worker

Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法,线程可以执行任务而不干扰用户界面。

它有以下特点:

  • 异步多线程 在主线程运行的同时,Worker 线程在后台运行,两者互不干扰
  • 同源限制 分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源,Worker 也仅能被生成它的脚本所使用
  • DOM 限制 Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用 documentwindowparent 这些对象。但是,Worker 线程可以访问 navigatorlocation 对象,可以使用大量 window 对象之下的东西,包括 WebSockets , IndexedDB
  • message通信机制 Worker 线程和主线程间的数据传递通过这样的消息机制进行——双方都使用 postMessage() 方法发送各自的消息,使用 onmessage 事件处理函数来响应消息
  • 支持web IO Worker 线程可以使用 XMLHttpRequest 进行网络I/O

阅读更多

cookie、sessionStorage、localStorage区别与作用

相同点:

  1. 都是客户端临时信息对象
  2. 只能存储字符串类型对象

不同点:

  1. 生命周期不同,cookie可以设置过期时间,localStorage始终有效,sessionStorage在关闭窗口后失效
  2. cookie的主要内容包括:名字、值、过期时间、路径和域,有路径的概念,可以限制cookie只属于某个路径下
  3. 存储大小限制不同,cookie 4k localStorage/sessionStorage 5M
  4. cookie始终在http请求中携带,Web Storage仅在本地保存
  5. 作用域不同,sessionStorage在不同的浏览器窗口中不共享,即使是同一个页面,localStorage/cookie在所有同源的窗口中是共享的。
  6. Web Storage 有API setItem getItem removeItem clear, cookie需要自己解析
  7. document.cookie window.localStorage window.sessionStorage
  8. web storage是html5引入的,部分老旧浏览器不支持