JS 回顾 14
约 6582 字大约 22 分钟
2026-02-15
Navigator 对象
window.navigator属性指向一个包含浏览器和系统信息的 Navigator 对象。脚本通过这个属性了解用户的环境信息。
属性
Navigator.userAgent
navigator.userAgent属性返回浏览器的 User Agent 字符串,表示用户设备信息,包含了浏览器的厂商、版本、操作系统等信息。
下面是 Chrome 浏览器的userAgent。
navigator.userAgent
// "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ch通过userAgent属性识别浏览器,不是一个好办法。因为必须考虑所有的情况(不同的浏览器,不同的版本),非常麻烦,而且用户可以改变这个字符串。这个字符串的格式并无统一规定,也无法保证未来的适用性,各种上网设备层出不穷,难以穷尽。所以,现在一般不再通过它识别浏览器了,而是使用“功能识别”方法,即逐一测试当前浏览器是否支持要用到的 JavaScript 功能。
不过,通过userAgent可以大致准确地识别手机浏览器,方法就是测试是否包含mobi字符串。
var ua = navigator.userAgent.toLowerCase();
if (/mobi/.test(ua)) {
// 手机浏览器
} else {
// 非手机浏览器
}如果想要识别所有移动设备的浏览器,可以测试更多的特征字符串。
/mobi|android|touch|mini/.test(ua)Navigator.plugins
Navigator.plugins属性返回一个类似数组的对象,成员是 Plugin 实例对象,表示浏览器安装的插件,比如 Flash、ActiveX 等。
var pluginsLength = navigator.plugins.length;
for (var i = 0; i < pluginsLength; i++) {
console.log(navigator.plugins[i].name);
console.log(navigator.plugins[i].filename);
console.log(navigator.plugins[i].description);
console.log(navigator.plugins[i].version);
}Navigator.platform
Navigator.platform属性返回用户的操作系统信息,比如MacIntel、Win32、Linux x86_64等 。
navigator.platform
// "Linux x86_64"Navigator.onLine
navigator.onLine属性返回一个布尔值,表示用户当前在线还是离线(浏览器断线)。
navigator.onLine // true有时,浏览器可以连接局域网,但是局域网不能连通外网。这时,有的浏览器的onLine属性会返回true,所以不能假定只要是true,用户就一定能访问互联网。不过,如果是false,可以断定用户一定离线。
用户变成在线会触发online事件,变成离线会触发offline事件,可以通过window.ononline和window.onoffline指定这两个事件的回调函数。
window.addEventListener('offline', function(e) { console.log('offline'); });
window.addEventListener('online', function(e) { console.log('online'); });Navigator.language
Navigator.languages
Navigator.language属性返回一个字符串,表示浏览器的首选语言。该属性只读。
navigator.language // "en"Navigator.languages属性返回一个数组,表示用户可以接受的语言。Navigator.language总是这个数组的第一个成员。HTTP 请求头信息的Accept-Language字段,就来自这个数组。
navigator.languages // ["en-US", "en", "zh-CN", "zh", "zh-TW"]如果这个属性发生变化,就会在window对象上触发languagechange事件。
Navigator.geolocation
Navigator.geolocation属性返回一个 Geolocation 对象,包含用户地理位置的信息。注意,该 API 只有在 HTTPS 协议下可用,否则调用下面方法时会报错。
Geolocation 对象提供下面三个方法。
- Geolocation.getCurrentPosition():得到用户的当前位置
- Geolocation.watchPosition():监听用户位置变化
- Geolocation.clearWatch():取消
watchPosition()方法指定的监听函数
注意,调用这三个方法时,浏览器会跳出一个对话框,要求用户给予授权。
Navigator.cookieEnabled
navigator.cookieEnabled属性返回一个布尔值,表示浏览器的 Cookie 功能是否打开。
navigator.cookieEnabled // true注意,这个属性反映的是浏览器总的特性,与是否储存某个具体的网站的 Cookie 无关。用户可以设置某个网站不得储存 Cookie,这时cookieEnabled返回的还是true。
方法
Navigator.javaEnabled()
navigator.javaEnabled()方法返回一个布尔值,表示浏览器是否能运行 Java Applet 小程序。
navigator.javaEnabled() // falseNavigator.sendBeacon()
Navigator.sendBeacon()方法用于向服务器异步发送数据。
navigator.sendBeacon() 现在还挺有用的,它不是用来“替代 fetch/xhr 做普通请求”的,而是专门解决一个老大难问题:页面要关闭/跳转时,如何尽量可靠地把一小段数据发出去(比如埋点、统计、日志)。
主要用于
- 异步发送少量数据到服务器
- 不阻塞页面卸载(用户关页/刷新/跳转时也尽量能送达)
- 浏览器会在后台“尽力而为”把请求发出去
典型场景:
- 统计“页面停留时长”
- 记录“用户点击了某按钮后立刻离开页面”
- 崩溃/错误上报(在 unload/visibilitychange 之类的时机)
它返回一个 boolean:是否已成功把数据交给浏览器排队发送
true:浏览器接受并排队(不代表服务器一定收到)false:浏览器没接单(比如数据太大/队列满/URL 不合法等)
页面卸载时更可靠,fetch/xhr 在 unload/跳转时经常被取消,尤其移动端更明显,sendBeacon 就是为这事设计的。
你几乎控制不了它,不能自定义 method/header、不能读响应、不能做复杂配置,它就是“把这包数据送出去”。
适合小数据,适合几 KB~几十 KB 级别的埋点数据;太大可能直接 false。
发送时通常是 POST,body 可以是:Blob / FormData / URLSearchParams / ArrayBuffer / TypedArray / 字符串等,如果你传 Blob,还能指定 type(比如 application/json)
最常见用法(埋点)
window.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
const data = JSON.stringify({
event: "page_hide",
ts: Date.now(),
path: location.pathname,
});
const ok = navigator.sendBeacon(
"/analytics",
new Blob([data], { type: "application/json" })
);
// ok 只是“是否成功排队”
}
});实验性属性
Navigator 对象有一些实验性属性,在部分浏览器可用。
Navigator.deviceMemory
navigator.deviceMemory属性返回当前计算机的内存数量(单位为 GB)。该属性只读,只在 HTTPS 环境下可用。
它的返回值是一个近似值,四舍五入到最接近的2的幂,通常是 0.25、0.5、1、2、4、8。实际内存超过 8GB,也返回8。
if (navigator.deviceMemory > 1) {
await import('./costly-module.js');
}上面示例中,只有当前内存大于 1GB,才加载大型的脚本。
Navigator.hardwareConcurrency
navigator.hardwareConcurrency属性返回用户计算机上可用的逻辑处理器的数量。该属性只读。
现代计算机的 CPU 有多个物理核心,每个物理核心有时支持一次运行多个线程。因此,四核 CPU 可以提供八个逻辑处理器核心。
if (navigator.hardwareConcurrency > 4) {
await import('./costly-module.js');
}上面示例中,可用的逻辑处理器大于4,才会加载大型脚本。
该属性通过用于创建 Web Worker,每个可用的逻辑处理器都创建一个 Worker。
let workerList = [];
for (let i = 0; i < window.navigator.hardwareConcurrency; i++) {
let newWorker = {
worker: new Worker('cpuworker.js'),
inUse: false
};
workerList.push(newWorker);
}上面示例中,有多少个可用的逻辑处理器,就创建多少个 Web Worker。
Navigator.connection
navigator.connection属性返回一个对象,包含当前网络连接的相关信息。
- downlink:有效带宽估计值(单位:兆比特/秒,Mbps),四舍五入到每秒 25KB 的最接近倍数。
- downlinkMax:当前连接的最大下行链路速度(单位:兆比特每秒,Mbps)。
- effectiveType:返回连接的等效类型,可能的值为
slow-2g、2g、3g、4g。 - rtt:当前连接的估计有效往返时间,四舍五入到最接近的25毫秒的倍数。
- saveData:用户是否设置了浏览器的减少数据使用量选项(比如不加载图片),返回
true或者false。 - type:当前连接的介质类型,可能的值为
bluetooth、cellular、ethernet、none、wifi、wimax、other、unknown。
if (navigator.connection.effectiveType === '4g') {
await import('./costly-module.js');
}上面示例中,如果网络连接是 4G,则加载大型脚本。
Screen 对象
Screen 对象表示当前窗口所在的屏幕,提供显示设备的信息。window.screen属性指向这个对象。
该对象有下面的属性。
Screen.height:浏览器窗口所在的屏幕的高度(单位像素)。除非调整显示器的分辨率,否则这个值可以看作常量,不会发生变化。显示器的分辨率与浏览器设置无关,缩放网页并不会改变分辨率。Screen.width:浏览器窗口所在的屏幕的宽度(单位像素)。Screen.availHeight:浏览器窗口可用的屏幕高度(单位像素)。因为部分空间可能不可用,比如系统的任务栏或者 Mac 系统屏幕底部的 Dock 区,这个属性等于height减去那些被系统组件的高度。Screen.availWidth:浏览器窗口可用的屏幕宽度(单位像素)。Screen.pixelDepth:整数,表示屏幕的色彩位数,比如24表示屏幕提供24位色彩。Screen.colorDepth:Screen.pixelDepth的别名。严格地说,colorDepth 表示应用程序的颜色深度,pixelDepth 表示屏幕的颜色深度,绝大多数情况下,它们都是同一件事。Screen.orientation:返回一个对象,表示屏幕的方向。该对象的type属性是一个字符串,表示屏幕的具体方向,landscape-primary表示横放,landscape-secondary表示颠倒的横放,portrait-primary表示竖放,portrait-secondary表示颠倒的竖放。
下面是Screen.orientation的例子。
window.screen.orientation
// { angle: 0, type: "landscape-primary", onchange: null }下面的例子保证屏幕分辨率大于 1024 x 768。
if (window.screen.width >= 1024 && window.screen.height >= 768) {
// 分辨率不低于 1024x768
}下面是根据屏幕的宽度,将用户导向不同网页的代码。
if ((screen.width <= 800) && (screen.height <= 600)) {
window.location.replace('small.html');
} else {
window.location.replace('wide.html');
}Cookie
Cookie 是服务器保存在浏览器的一小段文本信息,一般大小不能超过4KB。浏览器每次向服务器发出请求,就会自动附上这段信息。
HTTP 协议不带有状态,有些请求需要区分状态,就通过 Cookie 附带字符串,让服务器返回不一样的回应。举例来说,用户登录以后,服务器往往会在网站上留下一个 Cookie,记录用户编号(比如id=1234),以后每次浏览器向服务器请求数据,就会带上这个字符串,服务器从而知道是谁在请求,应该回应什么内容。
Cookie 的目的就是区分用户,以及放置状态信息,它的使用场景主要如下。
- 对话(session)管理:保存登录状态、购物车等需要记录的信息。
- 个性化信息:保存用户的偏好,比如网页的字体大小、背景色等等。
- 追踪用户:记录和分析用户行为。
Cookie 不是一种理想的客户端存储机制。它的容量很小(4KB),缺乏数据操作接口,而且会影响性能。客户端存储建议使用 Web storage API 和 IndexedDB。只有那些每次请求都需要让服务器知道的信息,才应该放在 Cookie 里面。
每个 Cookie 都有以下几方面的元数据。
- Cookie 的名字
- Cookie 的值(真正的数据写在这里面)
- 到期时间(超过这个时间会失效)
- 所属域名(默认为当前域名)
- 生效的路径(默认为当前网址)
举例来说,用户访问网址www.example.com,服务器在浏览器写入一个 Cookie。这个 Cookie 的所属域名为www.example.com,生效路径为根路径/。
如果 Cookie 的生效路径设为/forums,那么这个 Cookie 只有在访问www.example.com/forums及其子路径时才有效。以后,浏览器访问某个路径之前,就会找出对该域名和路径有效,并且还没有到期的 Cookie,一起发送给服务器。
用户可以设置浏览器不接受 Cookie,也可以设置不向服务器发送 Cookie。window.navigator.cookieEnabled属性返回一个布尔值,表示浏览器是否打开 Cookie 功能。
window.navigator.cookieEnabled // truedocument.cookie属性返回当前网页的 Cookie。
document.cookie // "id=foo;key=bar"不同浏览器对 Cookie 数量和大小的限制,是不一样的。一般来说,单个域名设置的 Cookie 不应超过30个,每个 Cookie 的大小不能超过 4KB。超过限制以后,Cookie 将被忽略,不会被设置。
Cookie 是按照域名区分的,foo.com只能读取自己放置的 Cookie,无法读取其他网站(比如bar.com)放置的 Cookie。一般情况下,一级域名也不能读取二级域名留下的 Cookie,比如mydomain.com不能读取subdomain.mydomain.com设置的 Cookie。但是有一个例外,设置 Cookie 的时候(不管是一级域名设置的,还是二级域名设置的),明确将domain属性设为一级域名,则这个域名下面的各级域名可以共享这个 Cookie。
Set-Cookie: name=value; domain=mydomain.com上面示例中,设置 Cookie 时,domain属性设为mydomain.com,那么各级的子域名和一级域名都可以读取这个 Cookie。
注意,区分 Cookie 时不考虑协议和端口。也就是说,http://example.com设置的 Cookie,可以被https://example.com或http://example.com:8080读取。
Cookie 与 HTTP 协议
Cookie 由 HTTP 协议生成,也主要是供 HTTP 协议使用。
HTTP 回应:Cookie 的生成
服务器如果希望在浏览器保存 Cookie,就要在 HTTP 回应的头信息里面,放置一个Set-Cookie字段。
Set-Cookie:foo=bar上面代码会在浏览器保存一个名为foo的 Cookie,它的值为bar。
HTTP 回应可以包含多个Set-Cookie字段,即在浏览器生成多个 Cookie。下面是一个例子。
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[page content]除了 Cookie 的值,Set-Cookie字段还可以附加 Cookie 的属性。
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly上面的几个属性的含义,将在后文解释。
一个Set-Cookie字段里面,可以同时包括多个属性,没有次序的要求。
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly下面是一个例子。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly如果服务器想改变一个早先设置的 Cookie,必须同时满足四个条件:Cookie 的key、domain、path和secure都匹配。举例来说,如果原始的 Cookie 是用如下的Set-Cookie设置的。
Set-Cookie: key1=value1; domain=example.com; path=/blog改变上面这个 Cookie 的值,就必须使用同样的Set-Cookie。
Set-Cookie: key1=value2; domain=example.com; path=/blog只要有一个属性不同,就会生成一个全新的 Cookie,而不是替换掉原来那个 Cookie。
Set-Cookie: key1=value2; domain=example.com; path=/上面的命令设置了一个全新的同名 Cookie,但是path属性不一样。下一次访问example.com/blog的时候,浏览器将向服务器发送两个同名的 Cookie。
Cookie: key1=value1; key1=value2上面代码的两个 Cookie 是同名的,匹配越精确的 Cookie 排在越前面。
HTTP 请求:Cookie 的发送
浏览器向服务器发送 HTTP 请求时,每个请求都会带上相应的 Cookie。也就是说,把服务器早前保存在浏览器的这段信息,再发回服务器。这时要使用 HTTP 头信息的Cookie字段。
Cookie: foo=bar上面代码会向服务器发送名为foo的 Cookie,值为bar。
Cookie字段可以包含多个 Cookie,使用分号(;)分隔。
Cookie: name=value; name2=value2; name3=value3下面是一个例子。
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry服务器收到浏览器发来的 Cookie 时,有两点是无法知道的。
- Cookie 的各种属性,比如何时过期。
- 哪个域名设置的 Cookie,到底是一级域名设的,还是某一个二级域名设的。
Cookie 的属性
Expires
Max-Age
Expires 和 Max-Age 都是用来控制 Cookie 过期时间的属性。
Expires 用来指定 一个具体的过期时间点。
当浏览器时间超过这个时间,Cookie 就会被删除。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;- 时间必须是 UTC 格式
- 可以通过
Date.prototype.toUTCString()生成 - 浏览器根据 本地时间 判断是否过期
注意:
- 如果用户本地时间不准确,Cookie 的实际过期时间可能会出现偏差。
- 如果不设置
Expires,该 Cookie 默认是 会话级 Cookie(Session Cookie)。
Max-Age 用来指定 从当前时间开始,Cookie 存活的秒数。
例如:
Set-Cookie: id=a3fWa; Max-Age=31536000;31536000 秒 ≈ 1 年
含义是:从现在开始 1 年后过期。
特点:
- 是一个“相对时间”
- 通常比
Expires更精确 - 更推荐使用
如果同时设置:
Set-Cookie: id=a3fWa; Expires=...; Max-Age=3600;Max-Age 优先生效
如果既没有设置 Expires,也没有设置 Max-Age,那么这个 Cookie 是 Session Cookie:只在当前浏览器会话中有效,浏览器关闭后自动删除,不会持久保存到磁盘。
Domain
Path
Domain 和 Path 决定了 浏览器在什么情况下会把某个 Cookie 发送给服务器,换句话说,它们控制的是 Cookie 的作用范围。
Cookie 会不会跟着请求一起发出去,主要看两件事:
- Domain:发给哪个域名(以及它的子域名)
- Path:发给该域名下哪些路径(前缀匹配)
Domain
- 不写
Domain:Cookie 默认只属于当前响应的那个域名(host-only),一般不会共享给其它子域名。 - 写了
Domain=example.com:Cookie 会发给example.com以及*.example.com。 - 浏览器会做校验:你不能把
Domain设成无关域名,也不能设成公共后缀(如github.io),否则直接拒收。
Path
Path=/:整个站点路径都能带。Path=/api:只有请求路径以/api开头才会带(例如/api/user会带,/docs不带)。
最终规则:Domain 匹配 AND Path 匹配,浏览器才会发送该 Cookie。
document.cookie 确实能写 Cookie,但只能在浏览器允许的范围里写:
- 只能给当前页面所在域名(或它的父域,且必须同一“站点可控范围”)设置
- 不能写到完全无关的域(你说的
Domain=api.github.com这种会直接被浏览器拒绝)
另外:JS 也不能设置/读取 HttpOnly Cookie(这是故意的安全设计)
正确流程 的是 请求打到某个后端域名 → 后端用响应头 Set-Cookie 下发 → 浏览器按规则存储 → 之后符合规则的请求再自动携带。
注意:即使是后端 Set-Cookie,也一样受浏览器规则限制;后端也不能凭空给别的顶级域名种 Cookie。
想让 Cookie “独享当前域名(host-only),子域也不行”,就不设置 Domain ⇒ host-only cookie,host-only 只会在“设置它的那个精确 host”上发送,也就是:如果是 api.yumgjs.com 设置的,那么只会发给 api.yumgjs.com,不会发给 mp.yumgjs.com、www.yumgjs.com 等。
跨站 + 跨域请求,想“带 Cookie”通常要同时满足几件事:
- 前端请求要开:
credentials: 'include' - API 响应要开:
Access-Control-Allow-Credentials: true Access-Control-Allow-Origin必须是具体的https://yumg.top(不能是*)- Cookie 本身在跨站场景通常要:
SameSite=None; Secure
SameSite=None 等于“允许跨站携带 Cookie”,CSRF 风险就回来了,解决方案:
CSRF 令牌(推荐)
- 后端给页面一个 token(放在 HTML 里或通过一个接口拿到)
- 前端每次对“会改状态”的请求(POST/PUT/DELETE)带上 token(例如
X-CSRF-Token) - 后端校验 token 与 session 绑定
校验 Origin / Referer(强烈建议加)
- 对所有写操作:只允许
Origin属于你的站点(比如yumg.top) - 这对挡住大多数 CSRF 很有效(攻击者网页的 Origin 不对)
要求“自定义请求头”(例如 X-Requested-With 或你的 X-CSRF-Token)
- 跨站表单提交带不了自定义头
- 攻击面更小(当然别把它当唯一防线)
把敏感操作改成 Authorization Bearer(不走 Cookie)
- 本质是避开“浏览器自动携带 Cookie”带来的 CSRF 面
结论:SameSite=None 不是“不能用”,是“用了就必须把 CSRF 防线补齐”。
mp.yumgjs.com + api.yumgjs.com 通常比 yumg.top + api.yumgjs.com 省心很多,它们属于同一个“站点”(同一 registrable domain:yumgjs.com)。很多浏览器策略在 same-site 场景更友好:
- 你往往可以用更保守的
SameSite=Lax(甚至不必 None),体验更稳定 - 第三方 Cookie 限制对你影响小很多
- 整体安全模型更清晰
但注意:同站点 ≠ 同源。mp.yumgjs.com 调 api.yumgjs.com 仍然是跨域(origin 不同),所以 CORS / credentials 这套可能还是要配,只是 SameSite/第三方 Cookie 的坑少很多。
“少走弯路派”的正确打开方式:
方案 A:同站点新开子域(推荐)
- 前端:
mp.yumgjs.com - API:
api.yumgjs.com - Nginx 分流到不同 upstream
好处:结构清晰、日志/限流/权限更好做。
注意:这仍然是“跨域”(origin 不同),但已经是 same-site,Cookie/SameSite 的麻烦会少很多,CORS 也更可控。
方案 B:直接同域同源,用路径反代(最省事)
- 网站:
https://yumgjs.com - API:
https://yumgjs.com/api/*→ Nginx 反代到后端端口
这个是最爽的:同源
于是:
- CORS 基本不用配了
- 前端不用
credentials: include那堆细节(正常同源 cookie 会自动带) - Cookie 默认策略更顺,不容易被浏览器第三方限制搞死
- 你可以用更保守的
SameSite=Lax,CSRF 压力也小一大截
如果你目标是“少麻烦、稳定上线”,方案 B 往往是性价比之王。
Secure
Secure 的意思很简单:这个 Cookie 只能在 HTTPS 请求里发送。
- 只要请求是
https://...,浏览器才会把带Secure的 Cookie 附上去 - 如果你用
http://...访问,同名 Cookie 就算存在也不会被带上 - 它是个开关,不需要写值:
Secure写了就表示开启
补一句很关键的工程现实:现在很多场景(尤其 SameSite=None)不配 Secure 直接无效,所以基本默认都该配上。
HttpOnly
HttpOnly 的作用是:不让 JavaScript 读取这个 Cookie。
也就是:
document.cookie读不到它- 你自己写的前端代码也拿不到它
但浏览器在发请求时仍然会带上(只要域名、路径、SameSite 等条件满足)。
所以它防的主要是:XSS 把 Cookie 读走并外传。
注意一个边界:HttpOnly 只能防“偷 Cookie 的值”,防不了 XSS 直接用你的登录态发请求(因为请求时浏览器会自动携带 Cookie)。
SameSite
SameSite 是用来控制:跨站请求时要不要带 Cookie,它主要解决两件事:
- CSRF:攻击者网站诱导用户发请求,浏览器自动带上受害者在目标站点的 Cookie
- 跨站追踪:第三方资源(比如隐藏图片/iframe)借 Cookie 识别你是谁
它有三个值:Strict / Lax / None。
SameSite=Strict(最严格)
- 只要是跨站,就绝对不带 Cookie
- 只有在“当前站点”和“请求目标站点”一致时才会带
特点:安全最强,但体验可能很差(有时点外链跳转也会像没登录)。
SameSite=Lax(默认推荐)
Lax 的核心规则:
- 一般跨站不带 Cookie
- 但“顶层导航(top-level navigation)到目标站点的 GET”会带 Cookie
直觉理解:用户主动点链接跳过去(GET),可以带;但第三方页面暗搓搓帮你发请求(POST、iframe、img、ajax),不带。
所以它能挡住经典 CSRF(尤其是 POST 表单那种),同时又不至于把正常跳转体验搞崩。
SameSite=None(允许跨站携带)
None 表示:允许跨站请求也带 Cookie
这通常用于:
- 前后端跨站点部署(比如站点和 API 不同站点)
- 第三方嵌入(iframe 里的登录态)
- 第三方 SSO 某些场景
但有个硬性要求:SameSite=None 必须同时设置 Secure,否则浏览器会当作无效处理(不按 None 执行)。
document.cookie
document.cookie属性用于读写当前网页的 Cookie。
读取的时候,它会返回当前网页的所有 Cookie,前提是该 Cookie 不能有HTTPOnly属性。
document.cookie // "foo=bar;baz=bar"上面代码从document.cookie一次性读出两个 Cookie,它们之间使用分号分隔。必须手动还原,才能取出每一个 Cookie 的值。
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
console.log(cookies[i]);
}
// foo=bar
// baz=bardocument.cookie属性是可写的,可以通过它为当前网站添加 Cookie。
document.cookie = 'fontSize=14';写入的时候,Cookie 的值必须写成key=value的形式。注意,等号两边不能有空格。另外,写入 Cookie 的时候,必须对分号、逗号和空格进行转义(它们都不允许作为 Cookie 的值),这可以用encodeURIComponent方法达到。
但是,document.cookie一次只能写入一个 Cookie,而且写入并不是覆盖,而是添加。
document.cookie = 'test1=hello';
document.cookie = 'test2=world';
document.cookie
// test1=hello;test2=worlddocument.cookie读写行为的差异(一次可以读出全部 Cookie,但是只能写入一个 Cookie),与 HTTP 协议的 Cookie 通信格式有关。浏览器向服务器发送 Cookie 的时候,Cookie字段是使用一行将所有 Cookie 全部发送;服务器向浏览器设置 Cookie 的时候,Set-Cookie字段是一行设置一个 Cookie。
写入 Cookie 的时候,可以一起写入 Cookie 的属性。
document.cookie = "foo=bar; expires=Fri, 31 Dec 2020 23:59:59 GMT";上面代码中,写入 Cookie 的时候,同时设置了expires属性。属性值的等号两边,也是不能有空格的。
各个属性的写入注意点如下。
path属性必须为绝对路径,默认为当前路径。domain属性值必须是当前发送 Cookie 的域名的一部分。比如,当前域名是example.com,就不能将其设为foo.com。该属性默认为当前的一级域名(不含二级域名)。如果显式设置该属性,则该域名的任意子域名也可以读取 Cookie。max-age属性的值为秒数。expires属性的值为 UTC 格式,可以使用Date.prototype.toUTCString()进行日期格式转换。
document.cookie写入 Cookie 的例子如下。
document.cookie = 'fontSize=14; '
+ 'expires=' + someDate.toGMTString() + '; '
+ 'path=/subdirectory; '
+ 'domain=example.com';注意,上面的domain属性,以前的写法是.example.com,表示子域名也可以读取该 Cookie,新的写法可以省略前面的点。
Cookie 的属性一旦设置完成,就没有办法读取这些属性的值。
删除一个现存 Cookie 的唯一方法,是设置它的expires属性为一个过去的日期。
document.cookie = 'fontSize=;expires=Thu, 01-Jan-1970 00:00:01 GMT';上面代码中,名为fontSize的 Cookie 的值为空,过期时间设为1970年1月1月零点,就等同于删除了这个 Cookie。