ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

JavaScript Cookie操作全解析:从原生API到js-cookie实战

JavaScript Cookie操作全解析:从原生API到js-cookie实战 1. 项目概述为什么我们还在聊Cookie操作在Web开发的世界里Cookie就像是一个老派的、但依然不可或缺的“记事本”。尽管现代前端有了LocalStorage、SessionStorage甚至IndexedDB这些更强大的本地存储方案但Cookie凭借其与生俱来的、由浏览器自动管理的HTTP请求头携带能力在处理用户会话Session、身份认证令牌以及一些简单的用户偏好设置时依然占据着不可替代的位置。尤其是在处理需要与服务器端紧密交互的场景比如保持登录状态、实现跨页面数据传递Cookie的“自动提交”特性是其他存储API无法比拟的。我见过不少新手开发者一上来就直奔Vue、React这些框架却对基础的Cookie操作一知半解。等到真正需要处理登录逻辑、或者对接一个老旧的第三方服务时面对document.cookie那略显原始的字符串操作往往一头雾水。实际上无论是直接使用原生的document.cookie还是借助像js-cookie这样的轻量级库掌握Cookie的增删改查都是前端工程师的一项基本功。它不复杂但细节很多一个字符的错误就可能导致整个认证流程失败。今天我就结合自己多年的踩坑经验系统性地整理一下JavaScript中操作Cookie的常见API、核心原理以及那些容易被忽略的“魔鬼细节”。2. Cookie核心原理与属性全解析在动手写代码之前我们必须先理解Cookie到底是什么以及它身上那些关键的属性都控制着什么。这能帮你从根本上避免很多低级错误。2.1 Cookie的本质与数据格式Cookie本质上是一段小型文本数据由服务器通过Set-Cookie响应头发送给浏览器或者由客户端JavaScript通过document.cookie设置。浏览器会将这些数据存储起来并在后续向同一域名发送的HTTP请求中自动通过Cookie请求头将其携带回服务器。一个Cookie字符串的格式是这样的namevalue; expiresSat, 01 Jan 2025 00:00:00 GMT; path/; domain.example.com; secure; samesitelax我们来拆解每一个部分namevalue这是核心的键值对。name和value都必须是字符串。这里有一个非常重要的点value中不能包含分号;、逗号,和空格。如果必须包含请使用encodeURIComponent()进行编码读取时再用decodeURIComponent()解码。expires和max-age控制Cookie的生命周期。expires指定一个具体的GMT格式的过期时间点。如果未设置或设置为一个过去的时间Cookie会在会话结束时关闭浏览器失效这被称为“会话Cookie”。max-age指定Cookie从设置开始存活的秒数优先级高于expires。例如max-age2592000表示存活30天。path指定Cookie在哪些路径下可以被发送。默认为设置Cookie时的页面路径。例如path/admin的Cookie只有在访问/admin及其子路径如/admin/users时才会被发送。通常我们会设置为path/使其在整个站点下有效。domain指定Cookie对哪个域名有效。默认为当前域名不包含子域名。如果你希望Cookie在a.example.com和b.example.com之间共享需要显式设置为domain.example.com注意前面的点。这是一个常见的跨子域共享配置点。secure这是一个布尔标志。如果设置Cookie只会在通过HTTPS协议发送请求时才会被携带。在当今全站HTTPS的趋势下对于涉及认证的Cookie设置secure是必须的安全实践。samesite这是一个相对较新但至关重要的安全属性用于控制跨站请求时是否发送Cookie。Strict最严格完全禁止第三方跨站请求携带Cookie。用户从外部链接点击进入你的网站登录态Cookie也不会被发送。Lax默认值现代浏览器的默认行为允许在顶级导航如点击链接和GET请求中携带Cookie但禁止在跨站的POST提交、iframe加载或通过fetch/XMLHttpRequest发起的请求中携带。这平衡了安全性和用户体验。None允许跨站携带Cookie但必须同时设置secure属性即必须使用HTTPS。常用于需要跨站共享登录态的第三方服务。注意samesite属性在现代浏览器中已成为默认Lax。如果你的应用依赖跨站POST请求携带Cookie例如一个传统的、嵌入在第三方页面的表单你需要明确设置samesitenone; secure并确保你的站点是HTTPS的否则请求将丢失Cookie导致会话失效。这是我排查过最多的“诡异”登录问题之一。2.2 原生APIdocument.cookie的双重角色document.cookie是一个有趣的属性它同时扮演了“读”和“写”两个角色但行为完全不同。写入设置/修改 当你给document.cookie赋值一个字符串时你并不是在替换所有Cookie而是在新增或更新一个特定的Cookie。// 设置一个名为“username”的Cookie值为“john”路径为根目录7天后过期 const days 7; const date new Date(); date.setTime(date.getTime() (days * 24 * 60 * 60 * 1000)); document.cookie usernamejohn; expires${date.toUTCString()}; path/;写入操作是精细化的一次只影响一个键值对。读取获取 当你读取document.cookie时它会返回当前页面可访问的所有Cookie格式是一个长长的字符串所有Cookie用分号和空格;连接。console.log(document.cookie); // 输出可能为usernamejohn; themedark; session_idabc123读取操作是粗粒度的你得到的是一个需要自行解析的字符串。这种设计导致了原生API操作的不便也是封装库存在的主要价值。3. 从零封装一个健壮的Cookie工具库理解了原理我们就可以动手封装自己的工具函数了。这个过程能让你对每一个细节都了如指掌。我将按照“增、删、改、查”的逻辑来构建。3.1 基础工具函数编码与日期处理在开始核心功能前我们需要两个辅助函数。/** * 安全地编码Cookie值主要处理分号、逗号和空格。 * param {string} value - 原始值 * returns {string} 编码后的值 */ function encodeCookieValue(value) { // 使用encodeURIComponent可以安全地处理绝大多数特殊字符 // 注意它也会编码一些在Cookie值中合法的字符如‘’但这通常是安全的。 return encodeURIComponent(value); } /** * 解码Cookie值 * param {string} value - 编码后的值 * returns {string} 解码后的原始值 */ function decodeCookieValue(value) { try { return decodeURIComponent(value); } catch (e) { // 如果解码失败例如值未被编码则返回原值 return value; } } /** * 将天数转换为GMT格式的过期时间字符串 * param {number} days - 过期天数 * returns {string} GMT格式的日期字符串 */ function getExpiresDate(days) { const date new Date(); date.setTime(date.getTime() (days * 24 * 60 * 60 * 1000)); return date.toUTCString(); }3.2 核心操作一设置Cookie增/改设置Cookie需要考虑所有可选属性并确保值的编码安全。/** * 设置一个Cookie * param {string} name - Cookie名称 * param {string} value - Cookie值 * param {Object} [options] - 配置选项 * param {number} [options.expires] - 过期天数 * param {string} [options.expiresDate] - 具体的GMT过期时间字符串优先级高于expires * param {string} [options.path] - 路径默认/ * param {string} [options.domain] - 域名 * param {boolean} [options.secure] - 是否仅HTTPS * param {Strict|Lax|None} [options.sameSite] - SameSite属性 */ function setCookie(name, value, options {}) { let cookieString ${encodeCookieValue(name)}${encodeCookieValue(value)}; // 处理过期时间 if (options.expiresDate) { cookieString ; expires${options.expiresDate}; } else if (options.expires) { cookieString ; expires${getExpiresDate(options.expires)}; } // 如果都不设置则为会话Cookie // 处理路径 cookieString ; path${options.path || /}; // 处理域名 if (options.domain) { cookieString ; domain${options.domain}; } // 处理Secure if (options.secure) { cookieString ; secure; } // 处理SameSite if (options.sameSite) { const sameSiteLower options.sameSite.toLowerCase(); if ([strict, lax, none].includes(sameSiteLower)) { cookieString ; samesite${sameSiteLower}; // 重要如果设置为None必须同时设置Secure在HTTPS下 if (sameSiteLower none !options.secure) { console.warn(Cookie ${name}设置了samesiteNone但未设置secure在现代浏览器中可能无效。); } } } // 执行设置 document.cookie cookieString; } // 使用示例 setCookie(user_token, eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..., { expires: 7, // 7天后过期 path: /, secure: true, // 仅HTTPS sameSite: Lax });3.3 核心操作二读取Cookie查读取需要从document.cookie的长字符串中解析出我们需要的特定值。/** * 获取指定名称的Cookie值 * param {string} name - 要查找的Cookie名称 * returns {string|null} - 找到则返回值未找到返回null */ function getCookie(name) { // 1. 获取所有Cookie字符串 const allCookies document.cookie; if (!allCookies) { return null; } // 2. 按‘; ’分割成单个Cookie键值对 const cookiePairs allCookies.split(; ); // 3. 遍历查找 for (const pair of cookiePairs) { const [cookieName, cookieValue] pair.split(); // 解码名称进行比较因为名称也可能被编码尽管不常见 if (decodeCookieValue(cookieName) name) { // 找到后解码值并返回 return decodeCookieValue(cookieValue); } } // 4. 未找到 return null; } // 使用示例 const token getCookie(user_token); if (token) { console.log(获取到Token:, token); // 通常这里会将token放入后续API请求的Authorization头中 } else { console.log(用户未登录或Token已过期); }3.4 核心操作三删除CookieJavaScript中没有直接删除Cookie的API。删除的本质是设置一个同名的Cookie并将其过期时间设置为一个过去的时间。同时path和domain必须与要删除的Cookie创建时保持一致否则删除会失败。/** * 删除一个Cookie * param {string} name - 要删除的Cookie名称 * param {Object} [options] - 必须与设置时的path和domain一致 * param {string} [options.path] - 路径默认/ * param {string} [options.domain] - 域名 */ function deleteCookie(name, options {}) { // 关键通过设置一个过去的过期时间来“删除” // 同时必须保持path和domain一致 setCookie(name, , { ...options, // 继承传入的path和domain expiresDate: Thu, 01 Jan 1970 00:00:00 GMT // 一个标准的过去时间 }); } // 使用示例用户退出登录时 deleteCookie(user_token, { path: /, secure: true }); // 注意如果当初设置时指定了domain这里也必须传入相同的domain3.5 进阶操作获取所有Cookie与检查功能有时我们需要查看或操作所有Cookie。/** * 获取当前所有Cookie并以对象形式返回 * returns {Object} 所有Cookie的键值对对象 */ function getAllCookies() { const allCookies document.cookie; const cookieObj {}; if (!allCookies) { return cookieObj; } const cookiePairs allCookies.split(; ); for (const pair of cookiePairs) { const [name, value] pair.split(); // 解码后存入对象 cookieObj[decodeCookieValue(name)] decodeCookieValue(value); } return cookieObj; } /** * 检查某个Cookie是否存在 * param {string} name - Cookie名称 * returns {boolean} */ function hasCookie(name) { return getCookie(name) ! null; } // 使用示例 const allCookies getAllCookies(); console.log(当前所有Cookie:, allCookies); console.log(是否有theme Cookie?, hasCookie(theme));4. 第三方库js-cookie的深度使用与源码浅析虽然自己封装能学到更多但在生产环境中使用一个成熟、稳定、经过充分测试的库是更明智的选择。js-cookie是目前最流行的Cookie操作库它API简洁兼容性好且支持AMD/CommonJS模块化。4.1 安装与基础API你可以通过npm安装npm install js-cookie或者直接使用CDN。script srchttps://cdn.jsdelivr.net/npm/js-cookie3/dist/js.cookie.min.js/script它的基础API极其简单// 设置Cookie Cookies.set(name, value, { expires: 7, path: /, secure: true, sameSite: lax }); // 读取Cookie const value Cookies.get(name); // 返回 value const all Cookies.get(); // 返回所有Cookie的对象 {name: value, ...} // 删除Cookie Cookies.remove(name, { path: /, secure: true });你会发现Cookies.remove同样需要正确的path和domain。4.2 高级特性与配置js-cookie提供了一些非常实用的高级特性1. 自动JSON转换 这是我最喜欢的功能之一。你可以直接存储对象或数组库会自动帮你序列化和反序列化。const userPrefs { theme: dark, language: zh-CN, fontSize: 14 }; Cookies.set(preferences, userPrefs, { expires: 30 }); const savedPrefs Cookies.get(preferences); console.log(savedPrefs.theme); // 输出 dark自动解析回了对象其原理是在set时内部调用JSON.stringify在get时尝试JSON.parse如果失败则返回原始字符串。2. 默认属性配置 你可以通过Cookies.defaults为后续所有操作设置默认属性这在统一管理站点Cookie策略时非常有用。// 假设你的站点全站HTTPS且希望Cookie默认7天过期路径为根目录 Cookies.defaults { expires: 7, path: /, secure: true, sameSite: lax }; // 之后设置Cookie就简洁多了 Cookies.set(session_key, abc123); // 自动应用上述默认属性3. 命名空间Converter 这是一个进阶功能允许你为特定Cookie定义自定义的读写转换逻辑。例如你希望所有日期类型的值都自动转换为Date对象。// 定义一个针对lastVisit这个Cookie的转换器 Cookies.withConverter({ read: function(value, name) { if (name lastVisit) { return new Date(value); // 将存储的字符串转换为Date对象 } return value; // 其他Cookie正常返回 }, write: function(value, name) { if (value instanceof Date name lastVisit) { return value.toISOString(); // 将Date对象转换为ISO字符串存储 } return value; } }); // 使用 Cookies.set(lastVisit, new Date()); const lastVisitDate Cookies.get(lastVisit); // 这里得到的是一个Date对象 console.log(lastVisitDate.getFullYear());4.3 源码核心逻辑借鉴阅读js-cookie的源码简化版能加深理解。其set和get的核心逻辑与我们自己封装的思路一致但更加健壮处理了更多边界情况。// 简化的 set 逻辑 function set (name, value, attributes) { // 1. 处理编码它使用了一个更安全的编码函数 name encode(name); value encode(converter.write ? converter.write(value, name) : value); // 2. 构建属性字符串 var str name value; var expires attributes.expires; if (typeof expires number) { // 将天数转换为Date对象 expires new Date(); expires.setMilliseconds(expires.getMilliseconds() expires * 864e5); } if (expires) { str ; expires expires.toUTCString(); } // ... 处理path, domain, secure, sameSite // 3. 赋值 document.cookie str; } // 简化的 get 逻辑 function get (name) { var cookies document.cookie ? document.cookie.split(; ) : []; for (var i 0; i cookies.length; i) { var parts cookies[i].split(); var foundName decode(parts.shift()); // 解码名称 if (name undefined || name foundName) { var cookieValue parts.join(); // 处理值中可能包含的 var result decode(cookieValue); // 尝试JSON解析 if (result.substring(0, 2) {) { try { result JSON.parse(result); } catch (e) {} } // 应用转换器 return converter.read ? converter.read(result, foundName) : result; } } return null; }从源码可以看到它在值解码后会尝试判断字符串是否以{开头如果是则尝试JSON.parse这实现了自动JSON转换。同时它用parts.join()来处理Cookie值本身包含等号的边缘情况比简单的split()[1]更健壮。5. 实战场景与避坑指南理论说再多不如看几个真实场景。下面是我在实际项目中遇到的典型用例和对应的坑。5.1 场景一用户登录态管理这是Cookie最经典的用途。服务器在验证用户名密码后生成一个加密的Session ID或JWT Token通过Set-Cookie头种到浏览器。// 假设登录API返回了token和用户信息 async function handleLogin(username, password) { try { const response await fetch(/api/login, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ username, password }) }); const data await response.json(); if (data.success) { // 通常服务器会通过Set-Cookie头自动设置但有时前端也需要手动设置 // 例如将服务器返回的token存储到Cookie Cookies.set(auth_token, data.token, { expires: 7, // 7天有效期 path: /, secure: true, // 生产环境必须 sameSite: lax }); // 同时可以将用户基本信息如用户名也存入Cookie避免频繁请求 Cookies.set(user_info, JSON.stringify({ name: data.userName, role: data.role }), { expires: 1 }); console.log(登录成功Cookie已设置); window.location.href /dashboard; } } catch (error) { console.error(登录失败:, error); } }避坑点Token安全认证Token如JWT本身可能包含敏感信息。确保服务器对Token进行了签名如使用HMAC或RSA并且前端无法篡改。secure和httpOnly此属性只能由服务器设置JavaScript无法读取是保护Token的关键。HttpOnly Cookie对于最核心的会话标识强烈建议服务器将其设置为HttpOnly。这样即使网站存在XSS漏洞恶意脚本也无法通过document.cookie窃取该Cookie极大增强了安全性。此时前端JavaScript不需要、也无法操作这个Cookie它仅由浏览器自动在请求中携带。5.2 场景二跨子域共享登录状态公司有www.example.com主站和admin.example.com后台两个子域希望用户在一个站点登录后访问另一个站点时也保持登录状态。解决方案在设置认证Cookie时指定domain为.example.com注意前面的点。// 在 www.example.com 登录成功后 Cookies.set(shared_session, session_id_abc, { expires: 1, path: /, domain: .example.com, // 关键允许所有子域访问 secure: true, sameSite: lax });这样用户在访问admin.example.com时浏览器也会自动带上这个Cookie。避坑点域名匹配规则domain设置为.example.com可以匹配example.com本身及其所有子域如www.example.com、admin.example.com。但不能匹配父域或其他不相关域名。端口和协议无关Cookie的域匹配不关心端口号localhost:8080和localhost:3000被视为不同源但Cookie域规则不涉及端口和协议HTTP/HTTPS但secure属性会限制协议。5.3 场景三简单的用户偏好设置对于一些非敏感的用户设置如主题色、语言偏好使用Cookie存储是个简单直接的选择。// 保存主题偏好 function saveThemePreference(theme) { Cookies.set(user_theme, theme, { expires: 365 }); // 保存一年 applyTheme(theme); // 立即应用主题 } // 页面加载时读取并应用 function initPage() { const savedTheme Cookies.get(user_theme) || light; // 默认亮色主题 applyTheme(savedTheme); // 绑定主题切换按钮事件 document.getElementById(themeToggle).addEventListener(click, () { const newTheme savedTheme light ? dark : light; saveThemePreference(newTheme); }); }避坑点存储容量限制每个Cookie通常最大为4KB且每个域名下的Cookie总数和总大小也有限制通常为50个左右总大小4KB左右。对于复杂的配置考虑使用localStorage容量约5MB。性能影响由于Cookie会在每一个同域请求的HTTP头中被自动发送如果存储了大量数据会无形中增加请求的负载影响性能。因此只将真正需要服务器知晓的数据放在Cookie里。5.4 场景四与LocalStorage的抉择这是一个常见问题我该用Cookie还是LocalStorage特性CookieLocalStorage生命周期可设置过期时间或为会话级持久存储除非手动清除存储容量约4KB每个约5MB每个域名数据位置浏览器和服务器通过HTTP头仅浏览器自动发送是每次请求自动携带否可访问性可通过document.cookie非HttpOnly和HTTP头访问仅通过JavaScript API访问主要用途会话管理、身份认证、需要服务器知晓的简单数据纯前端缓存、大量用户偏好设置、离线数据选择建议选Cookie数据需要被自动发送到服务器用于维持状态如登录Session、购物车ID。或者数据很小且需要设置精确的过期时间。选LocalStorage数据纯粹供前端使用不需要发给服务器且数据量可能较大如用户编辑器的草稿、缓存的API响应数据。一个常见的混合模式是用HttpOnly的Cookie存储核心会话ID保证安全同时用LocalStorage存储用户界面相关的偏好如主题、列表排序方式两者互补。6. 常见问题排查与调试技巧即使理解了所有原理在实际开发中还是会遇到各种“诡异”的问题。下面是我总结的排查清单。6.1 问题一Cookie设置成功了但读取不到/请求中未携带这是最高频的问题。请按以下顺序排查检查路径Path你是否在/admin路径下设置的Cookie然后试图在首页/读取它Cookie的路径必须匹配。最稳妥的方式是始终设置path/。检查域名Domain你是否在www.example.com设置但试图在app.example.com读取需要设置domain.example.com来实现跨子域共享。检查Secure属性如果你的网站是HTTPS但Cookie设置了securetrue那么当你在本地开发环境HTTP测试时这个Cookie是无效的既无法设置也无法读取。本地开发时请暂时注释掉secure选项。检查SameSite属性这是现代浏览器Chrome 80 Firefox 79等的默认行为变化导致的经典问题。现象用户从第三方网站如邮件链接点击进入你的网站发现是未登录状态。原因现代浏览器默认将未指定SameSite的Cookie视为Lax。Lax模式下从外部链接发起的跨站请求即使是GET请求在某些严格实现下也可能不携带Cookie。解决方案如果你的应用依赖跨站请求携带Cookie例如单点登录SSO场景必须显式设置sameSitenone; securetrue。并且必须使用HTTPS。检查浏览器设置用户可能禁用了第三方Cookie或者使用了隐私浏览模式。可以在浏览器开发者工具的Application或Storage标签页中直接查看当前页面下所有可用的Cookie这是最直接的调试手段。6.2 问题二Cookie值包含特殊字符导致被截断如果你尝试存储一个包含分号的字符串比如user;admin它会破坏Cookie的格式。// 错误示例 document.cookie roleuser;admin; // 浏览器会理解为namerole, valueuser; 然后admin成了一个独立的无效片段 console.log(document.cookie); // 可能只输出 roleuser解决方案始终对值进行编码。// 正确示例使用我们的封装函数或js-cookie setCookie(role, user;admin, { expires: 7 }); // 或 Cookies.set(role, user;admin, { expires: 7 }); // 实际存储的是 roleuser%3Badmin读取时会自动解码回 user;admin6.3 问题三删除Cookie失败删除的本质是设置过期如果path或domain不匹配就会创建一个新的、无效的Cookie而旧的依然存在。// 假设当初这样设置 Cookies.set(myCookie, value, { path: /dashboard, secure: true }); // 这样删除会失败因为path不匹配 Cookies.remove(myCookie); // 默认path是/ // 这会在根路径/下创建一个名为myCookie的过期Cookie但原路径/dashboard下的还在 // 正确删除方式 Cookies.remove(myCookie, { path: /dashboard, secure: true });黄金法则删除Cookie时传入的path、domain、secure属性必须与设置时完全一致。养成在设置Cookie后将配置保存下来的习惯例如存到一个常量对象里删除时直接引用。6.4 开发者工具实战调试现代浏览器的开发者工具是调试Cookie的利器以Chrome为例查看所有Cookie打开开发者工具 (F12) - Application (或 Storage) - Cookies。这里会按域名列出所有Cookie并清晰展示其Name、Value、Domain、Path、Expires/Max-Age、Size、HttpOnly、Secure、SameSite属性。一目了然。实时监控Cookie变化在开发者工具 - Network标签页点击任意一个网络请求在Headers选项卡中查看Request Headers里的Cookie字段以及Response Headers里的Set-Cookie字段。这是观察Cookie在客户端和服务器间如何传递的最直接方式。手动编辑与删除在Application - Cookies面板你可以直接双击某个Cookie的值或属性进行编辑也可以右键删除。这在测试不同场景时非常方便。掌握这些调试技巧能让你在遇到Cookie相关问题时快速定位是前端设置问题、浏览器策略问题还是服务器配置问题。
返回列表