表单事件

在实际开发中,表单是最常见的交互区域,一个表单中包含多种输入框和按钮。登录、注册、搜索、留言、提交订单……几乎所有业务都离不开表单。常见表单事件如下:

事件名 说明
submit 表单提交
input 输入时触发
change 失去焦点后触发
focus 获得焦点
blur 失去焦点
reset 表单重置

首先是针对于表单中的输入框,有一个input事件,当输入框内容发生改变时立即触发(实时触发)

html 复制代码
<form id="loginForm">
  <label>
    <input type="text" id="username" placeholder="请输入用户名">
  </label>
  <label>
    <input type="password" id="password" placeholder="请输入密码">
  </label>
  <button>登录</button>
</form>
js 复制代码
const input = document.getElementById('username')

//监听input事件
input.addEventListener('input', () => {
    console.log('当前输入内容:', input.value)   //使用value属性来获取输入框中输入的内容
})

每输入一个字符都会触发input事件,包括删除、粘贴等,像实时校验用户输入内容是否合法就可以采用这种方式,比如判断用户输入的用户名是否是邮箱格式:

js 复制代码
input.addEventListener('input', () => {
  	//使用正则表达式进行判断
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    const isEmail = emailRegex.test(input.value)
    console.log('是否是邮箱:', isEmail)
})

这就是非常典型的一种表单校验案例。当然,还有一个change事件和 input 很像,但有一个关键区别,就是只有在内容变化且失去焦点后才触发。

针对于使用了中文输入法的情况,有时候非常恶心:

image-20260225160152582

由于中文输入法也是先输入每个字符再完成拼写,所以就会出现这种输入法过程中的文本输入也被监听到的情况,因此我们还可以更细致地进行事件监听:

  1. compositionstart - 输入法开始
  2. compositionupdate - 输入法正在打字
  3. compositionend - 输入法结束
js 复制代码
//只监听输入法结束时的事件
input.addEventListener('compositionend', () => {
    console.log(input.value)   //得到的就是输入法完成的结果
})

利用这种机制,我们就可以进行更加精确判断了:

js 复制代码
let composition = false

const showText = () => {
    if(!composition) {
        console.log(input.value)
    }
}

input.addEventListener('compositionstart', () => composition = true)
input.addEventListener('compositionend', () => {
    composition = false
    showText()   //因为compositionend不会一起触发input事件,所以完成输入法之后也得手动查看一次
})
input.addEventListener('input', showText)

除了普通的文本输入框之外,选择框也可以在选择之后触发input事件:

html 复制代码
<label>
  <select id="gender">
    <option>男</option>
    <option>女</option>
  </select>
</label>
js 复制代码
const select = document.getElementById('gender')
select.addEventListener('input', evt => {
    console.log(select.value)
})

接着是焦点获取相关的事件,在之前我们提到,部分元素是可以获得焦点的,键盘事件只有在取得焦点时才会正确响应,当元素聚焦或失焦时,也可以通过对应的事件来进行处理,focus 和 blur 就是聚焦和失焦时的事件:

js 复制代码
input.addEventListener('focus', function () {
  console.log('获得焦点')
})

input.addEventListener('blur', function () {
  console.log('失去焦点')
})

同样的,利用这种机制我们也可以在用户输入完成之后进行表单校验。

接着是非常重要的表单提交事件,在HTML阶段我们学习过,当点击表单内的按钮时,会自动提交表单内容,发起一个表单请求,默认为GET请求方式,对应的事件就是表单提交事件:

js 复制代码
const form = document.getElementById('loginForm')

form.addEventListener('submit', function (e) {
  console.log('表单提交了')
})

在默认情况下,表单提交会刷新页面并发送请求,我们也可以阻止其默认行为来防止页面刷新,并通过自行发起请求的方式来进行登录,有关发起XHR请求相关内容我们会在前端路线后续课程中介绍。

编辑事件

编辑事件主要用于可编辑内容区域,比如现在输入框中有一段文本:

html 复制代码
<textarea style="width: 300px;height: 200px" id="text"></textarea>
js 复制代码
const input = document.getElementById('text')
input.value = `1、祝有爱者有爱,无爱者自由。
2、不要靠近我,熟悉我,关心我,然后离开我。
3、人在刚认识的时候最好,虚伪且礼貌。
4、有空多爱自己,别人很忙。
5、世界上最危险的东西就是希望。
6、或许换个时间,有些人真的很合适。
7、我渴望你是救赎,也恐惧你是深渊。
8、想离开的人从来都不缺理由。
9、我对你的热情,在无数个夜晚里消耗殆尽。
10、也许最稳定的关系,就是没有关系。`
input.addEventListener('select', evt => {
    console.log(evt)
})

当我们选中其中一段文本之后,就会触发select事件:

image-20260225172405128

我们通过文本框的selectionStart和selectionEnd属性即可获得选择的起始和结束位置(就是选择的文本内容子串起始和结束下标)

js 复制代码
input.addEventListener('select', evt => {
    console.log("选择起始位置", input.selectionStart)
    console.log("选择结束位置", input.selectionEnd)
    console.log("选择内容:", input.value.substring(input.selectionStart, input.selectionEnd))
})

只不过,这个事件只能在一些可以编辑的元素中触发,如果是不可编辑的元素,是无法触发的:

html 复制代码
<div id="text">
  苹果正式官宣:2026年3月4日北京时间22:00
  举办「Apple Experience」特别活动
  纽约、伦敦、上海三地同步直播
  全场重点:iPhone 17e、全新平价Mac、M5全家桶
</div>

不过,还有一个事件selectstart可以在任意情况下触发,即使不是输入框,只要用户在页面上开始选择都会触发这个事件:

js 复制代码
div.addEventListener('selectstart', evt => {
    console.log(evt)
})

利用这个事件,我们可以实现阻止用户选择页面文本的效果:

js 复制代码
div.addEventListener('selectstart', evt => {
    console.log("用户尝试选择文本")
    evt.preventDefault()   //阻止选择开始
})

在前面我们为大家介绍了如何利用键盘事件来防止用户使用Ctrl+C复制页面内容,不过,虽然这种方式简单粗暴,但是如果用户有其他复制方式,比如右键菜单复制,依然可以实现内容拷贝,要彻底防止用户复制内容,我们可以使用copy事件来处理:

js 复制代码
div.addEventListener('copy', evt => {
    console.log("用户尝试复制文本")
    evt.preventDefault()
})

页面事件

前面我们讲了鼠标、键盘、表单等事件,它们大多是“用户行为触发”的。而页面级事件用于控制整个页面生命周期,通常是:

  • 页面加载完成
  • 页面关闭
  • 页面尺寸变化
  • 页面滚动
  • 网络状态变化
  • 页面可见性变化

这些事件往往和整个页面生命周期有关,页面事件主要绑定在:window 对象和document 对象上。

当 DOM 树构建完成 时会触发DOMContentLoaded事件,这个阶段仅仅只是HTML文档树解析完成,不等待图片加载,不等待 CSS 加载:

js 复制代码
document.addEventListener('DOMContentLoaded', () => {
    const div = document.getElementById('text')
    console.log('DOM结构已经构建完成')
    console.log(div)   //此时已经能够拿到DOM元素了
})

由于不等待CSS加载,有可能这个时候元素的最终渲染尺寸还没有确定,诸如getBoundingClientRect这类方法获取的结果并不一定是准确的。

接着就是load事件,它会在 整个页面加载完成 才会触发,包括DOM、图片、CSS以及一些其他的外部资源在内:

js 复制代码
window.addEventListener('load', () => {
    console.log('页面完全加载完成')
})

所以,我们前面提到的JS执行时DOM还未构建完成,导致无法获取到元素的问题,也可以通过事件监听的形式来完成,我们只需要在load事件中进行处理即可保证所有元素一定加载完成,这也算一种处理方式。

除了页面加载之外,页面在卸载时也会触发相关事件,当用户即将离开页面时,会触发beforeunload,比如关闭标签页,页面跳转,刷新页面等情况。unload就是页面完全卸载之后触发了。

接着是页面尺寸变化,当浏览器窗口大小改变时会触发resize事件:

js 复制代码
window.addEventListener('resize', evt => {
    console.log(evt)
})

这在响应式布局控制中非常常用。

还有页面滚动时,会触发scroll事件:

js 复制代码
window.addEventListener('scroll', evt => {
    console.log(evt)
})

在后续练习中,我们会利用滚动事件来实现真正的视差滚动效果。

此外,页面可见性变化时也能监听,比如我们想看当前页面是否被切到后台了,使用visibilitychange事件来监听:

js 复制代码
window.addEventListener('visibilitychange', evt => {
    console.log(document.visibilityState)   //通过visibilityState属性查看是否可见
})

当切换标签页或是最小化窗口时,事件就会触发。

我们甚至还可以监听页面是否发生错误,比如有JS发生了报错:

js 复制代码
window.addEventListener('error', function (e) {
    console.log('发生错误:', e.message)
})

console.log(a)
image-20260225182451688

当然不仅仅是JS加载错误,比如图片加载错误也是可以监听到的:

html 复制代码
<img id="img" src="js.jpg">
js 复制代码
const img = document.querySelector('img')  //针对图片元素进行错误监听

img.addEventListener('error', function () {
    console.log('图片加载失败')
})

常用WebAPI

这一部分我们接着来介绍常用的WebAPI,在前端开发中,浏览器提供了一些强大的 API 供 JavaScript 与浏览器进行交互,我们将依次介绍一些常用的API以便大家使用。

窗口对象

window 是 JavaScript 中的一个全局对象,它是所有对象的顶层,它表示浏览器窗口(或框架),是浏览器环境的核心。它不仅是浏览器窗口的表示,还提供了访问浏览器相关功能的接口。同时,window 也是所有浏览器环境中全局对象的引用,我们之前没有使用new执行的this其实就是指向的window对象。

  • window 是全局作用域的顶级对象,在浏览器中定义了一个“全局环境”。
  • 当你在浏览器中执行代码时,你访问的全局变量(比如 document、console)都属于 window 对象的一部分。

在window 对象中有很多重要的属性:

  • window.document:返回当前文档对象。通常用于操作 HTML 文档结构。
  • window.location:返回当前页面的 URL 信息,并允许修改 URL 来重定向页面。
  • window.navigator:返回浏览器的信息,如版本、平台、语言等。
  • window.console:提供与开发者工具相关的功能,如 console.log(),console.error() 等。
  • window.history:允许访问浏览器的历史记录,并可以执行前进、后退操作。
  • window.innerWidth / window.innerHeight:返回浏览器视口的宽度和高度。
  • window.localStorage / window.sessionStorage:提供本地存储(localStorage)和会话存储(sessionStorage)的接口,允许在浏览器中存储数据。

需要注意的是,在浏览器中,所有的全局变量和函数都是 window 对象的属性,实际上var声明(let和const不受影响)的全局变量 x 等同于 window.x,function声明的全局函数 foo() 等同于 window.foo():

js 复制代码
function test() {
    console.log("牛逼")
}
image-20260226162140134

实际上我们之前使用的很多全局函数都是window提供的。

首先我们来介绍一下窗口尺寸相关的属性,在 JS 中,窗口尺寸主要分为两类:包含滚动条的整体窗口尺寸 和 可视区域尺寸。可视区域尺寸就是指浏览器窗口中实际能看到内容的区域大小(不包含浏览器的地址栏、工具栏、开发者工具页面,也不包含滚动条)

  • window.innerWidth:获取浏览器可视区域的宽度(单位:px)
  • window.innerHeight:获取浏览器可视区域的高度(单位:px)

示例代码:

js 复制代码
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
console.log(`可视区域宽度:${viewportWidth}px,高度:${viewportHeight}px`);

我们也可以使用整体窗口尺寸,它会完整包含整个浏览器窗口大小:

  • window.outerWidth:整个浏览器窗口的宽度(包含浏览器边框、滚动条、工具栏等)
  • window.outerHeight:整个浏览器窗口的高度(包含浏览器标题栏、状态栏等)

示例代码:

js 复制代码
const windowWidth = window.outerWidth;
const windowHeight = window.outerHeight;
console.log(`浏览器窗口整体宽度:${windowWidth}px,高度:${windowHeight}px`);

除了获取窗口尺寸之外,我们也可以获取滚动状态,当页面出现滚动条时,我们可以通过:

js 复制代码
window.scrollY  //纵向滚动位置
window.scrollX  //横向滚动位置

来获取整个页面的滚动位置,不过需要注意的是这两个值不能通过直接修改来改变页面的滚动位置,需要和之前使用元素的内部滚动一样,调用scroll相关方法:

js 复制代码
// 滚动到指定位置
window.scroll({ top: 0 })

接着是window对象的一些方法,可以用来控制浏览器窗口或执行其他操作:

  • window.alert():弹出一个警告框,显示指定的消息。
  • window.confirm():弹出一个确认框,返回用户的选择(true 或 false)。
  • window.prompt():弹出一个输入框,允许用户输入内容。
  • window.open():打开一个新的浏览器窗口或标签页。
  • window.close():关闭当前窗口。
js 复制代码
//因为是全局对象,调用其中的函数可以直接写成alert
window.alert("This is an alert!");  // 弹出警告框
let userConfirmed = window.confirm("Do you confirm this action?");

有关window对象中提供的其他属性,我们会在本节后续内容中继续介绍。

浏览导航和历史

location 是 window 对象的一个属性,它指向一个包含当前文档 URL 信息的对象,你可以通过它读取或修改浏览器地址栏的 URL,从而实现页面跳转、刷新等操作。

首先是href属性,它代表浏览器当前访问的完整 URL:

js 复制代码
console.log(location.href)
//当前浏览器地址栏的地址 http://localhost:63342/HelloHtml/index.html

当然完整地址可以拆分成几个部分单独获取,假设 URL 是 https://www.example.com:8080/path/page.html?name=test&age=18#section1:

属性名 作用 示例
protocol 协议(含冒号) https:
host 主机名 + 端口号 www.example.com:8080
hostname 主机名 www.example.com
port 端口号 8080
pathname 路径(含斜杠) /path/page.html
search 查询参数(含问号) ?name=test&age=18
hash 锚点(含井号) #section1
origin 协议 + 主机名 + 端口号(只读) https://www.example.com:8080

针对于href属性,我们可以对其进行修改来实现页面跳转:

js 复制代码
location.href = 'https://www.baidu.com'

注意修改href必须携带前面的协议,如果不携带会被认为是在当前站点进行跳转,会以路径的形式进行拼接,注意如果是跳转某个子页面,一定要区分最前面加/和不加的区别,前者会从根路径开始,后者会以当前路径继续深入。

通过这种方式跳转页面都会导致页面强制刷新,即使跳转到相同路径的页面也会刷新。

除了使用这种方式跳转页面之外,我们也可以使用assign方法,同样可以实现跳转到指定 URL 的效果:

js 复制代码
location.assign('https://www.baidu.com')

此外,还有一个与assign效果相同的replace方法,但是它们的区别在于历史记录,我们会在后面接着介绍。

除了页面跳转之外,我们也可以手动刷新页面:

js 复制代码
location.reload()   //简单刷新页面
location.reload(true)   //强制刷新页面(忽略缓存,重新加载)

我们接着来看历史记录,几乎所有现代化浏览器都有历史记录功能:

image-20260226182015508

历史记录中保存了所有你访问过的网站,你可以直接跳转到自己曾经访问过的站点,我们也可以在页面的顶部点击左右箭头符号来实现向前或向后跳转:

image-20260226183408716

当然,如果只是在某一个标签页内,前进后退的范围仅限于当前这个标签所访问过的站点或地址。在浏览器中也是使用的一个类似于栈结构的容器进行存放:

image-20260226225444538

history 也是 window 对象的属性,它管理浏览器的会话历史记录(即你访问过的页面列表),可以实现 “前进”“后退”“新增 / 替换历史记录” 等操作,是前端路由(如 HistoryRouter)的核心:

js 复制代码
console.log(history.length)  //获取栈长度

长度就是当前标签页访问过的所有可以回退的页面数量,也就是历史记录的数量。除此之外,还有scrollRestoration属性,它保存的是滚动恢复策略:

js 复制代码
console.log(history.scrollRestoration)

它的作用是控制浏览器在历史记录之间导航时(后退 / 前进)是否自动恢复页面的滚动位置,默认情况下值为auto,浏览器自动恢复到上一次的滚动位置,我们也可以设置为manual来让浏览器不自动恢复滚动位置,需手动控制。

我们接着来看看历史记录的核心方法:

方法名 作用 示例
back() 回退到上一条历史记录(等价于点击浏览器后退按钮) history.back()
forward() 前进到下一条历史记录(等价于点击浏览器前进按钮) history.forward()
go(n) 跳转到指定偏移量的历史记录:- n=1:前进 1 步- n=-1:后退 1 步- n=0:刷新当前页 history.go(-2) // 后退 2 步
js 复制代码
history.back() // 后退一步
history.forward() // 前进一步
history.go(-2) // 后退两步
history.go(1) // 前进一步
history.go(0) // 刷新当前页

注意历史记录的跳转(无论是JS还是浏览器直接点击返回前进按钮)可能会导致页面刷新,当两个历史记录的页面路径或地址不同时,都会导致页面刷新。

我们在前面提到的assign和replace方法,最大的区别就在于是否存在历史记录,如果是replace相当于直接将当前页面替换成了一个新的地址,也就是说会把当前页面的历史记录信息给覆盖,而assign相当于是进行一次新的调跳转,跳转之后之前的页面会作为历史记录存放。

除了进行页面跳转会增加历史记录,我们也可以手动插入一个新的历史记录:

js 复制代码
history.pushState(null, '', 'index.html')

这里需要传入3个参数:

  • state (对象):一个与新历史记录条目相关的状态对象。每当用户导航到该状态时,popstate 事件就会被触发,你可以从事件中或是history.state读取这个对象。如果不需要,传 null 即可。
  • title (字符串):目前大多数浏览器都会忽略这个参数,通常传空字符串 ""。
  • url (字符串):新的历史记录条目地址。注意: 为了安全,这个 URL 必须与当前页面同源(同一个域名)

执行后,和之前一样,上一个页面的路径会作为历史,我们依然可以点击返回变回之前的页面,接着页面链接路径会替换为pushState传入的url参数,但不会刷新页面。这对于一些SPA(单页面应用)的实现,比如 VueRouter 就非常关键,如果我们通过location.href地址栏的 URL 会导致页面刷新。 而使用 pushState,你可以改变 URL,让用户觉得“跳转了页面”,但实际上网页内容是通过 JavaScript 动态更新的,整个过程非常平滑,没有白屏。

与其类似的还有一个replaceState,不过和之前的replace方法一样,它会直接替换当前的历史记录,而不是保存之前并插入一个新的,所以这种方式是无法回退之前路径的。

浏览器信息

navigator 用于获取当前浏览器和设备环境信息,简单来说就是浏览器的“自我介绍”。你可以通过它知道:浏览器类型、版本、操作系统、是否联网、用户使用的语言、摄像头/麦克风权限、地理位置、剪贴板等等。

很多常用的WebAPI属性都可以通过navigator来获取,我们先从浏览器信息开始介绍。其中最主要的就是userAgent属性,可以获取浏览器的 用户代理字符串:

js 复制代码
console.log(navigator.userAgent)
image-20260226232916113

可以看到这个结果里面包含了很多信息,包括当前使用的浏览器名称、操作系统版本等,只不过这种方式只能获取一个大概的信息,但实际版本和名称并不准确,并且部分浏览器是可以随意修改UA信息的,所以不推荐用于精确判断。要简单判断下是否移动端还是没问题的:

js 复制代码
if (/Mobile/.test(navigator.userAgent)) {
  console.log("移动端")
} else {
  console.log("桌面端")
}

我们接着来看下一个属性,language用于获取浏览器语言,比如要判断是否中文:

js 复制代码
if (navigator.language.startsWith("zh")) {
  console.log("中文环境")
}

onLine可以判断是否联网:

js 复制代码
console.log(navigator.onLine)

大家可以试试关闭电脑WIFI或是拔掉主机的网线,观察网络状态变化,还可以结合事件监听:

js 复制代码
window.addEventListener("online", () => {
  console.log("网络已恢复")
})

window.addEventListener("offline", () => {
  console.log("网络已断开")
})

我们还可以通过geolocation获取用户位置,不过此操作需要用户授权,所以我们只能以回调形式传入获取之后的处理逻辑:

js 复制代码
navigator.geolocation.getCurrentPosition(
  position => {   //用户允许执行,则进一步执行回调
    console.log(position.coords.latitude)  //通过coords获取经纬度
    console.log(position.coords.longitude)
    console.log(position.coords.accuracy)   //坐标精确度
  },
  error => {
    console.log("获取失败")   //如果用户不允许,则执行错误回调
  }
)
image-20260226234407496

这里我们可以点击访问该网站时允许(就和手机App授权是一样的)来让浏览器可以访问当前的地理信息。

接着是剪贴板,浏览器可以实现在用户不主动执行Ctrl+V的情况下,直接读取用户的剪贴板内容,不过和上面一样,由于剪贴板里面可能涉及到用户隐私信息,所以需要用户手动授权才能使用:

js 复制代码
navigator.clipboard.readText().then(text => {
    console.log(text)
})
image-20260226235048260

这里readText()会返回一个Promise对象,当用户允许剪贴板之后,Promise才会正常resolve,如果用户拒绝了读取剪贴板的请求,会直接失败:

image-20260226235225136

除了读取剪贴板的内容之外,我们也可以对剪贴板内容进行修改:

js 复制代码
navigator.clipboard.writeText("Hello")

最后是摄像头和麦克风,我们可以通过navigator.mediaDevices来获取媒体设备:

js 复制代码
navigator.mediaDevices.getUserMedia({ 
    video: true,   //如果需要获取摄像头,就开启
    audio: true    //如果需要获取麦克风,就开启
}).then(stream => {
    console.log("获取成功")
}).catch(err => {
    console.log("获取失败")  //用户不授权或是根本没有对应的媒体设备就失败
})

由于也会涉及到用户隐私,所以这里依然需要用户手动授权,否则会导致获取失败:

image-20260227000454298

如果成功获取到,这里会得到一个stream对象,它代表数据的输入流,大家可以理解为视频数据像水流一样不断涌入,我们可以把这个流绑定到一个video元素上:

html 复制代码
<video id="video" width="640" height="480" autoplay playsinline></video>
js 复制代码
navigator.mediaDevices.getUserMedia({
    video: { width: 640, height: 480 },  //视频可以进一步设置摄像头录制的宽度和高度以及其他属性
    audio: true
}).then(stream => {
    const video = document.getElementById('video')
    video.srcObject = stream  //将video元素的srcObject属性设置为这里的流即可
})

现在很多的一些视频会议都可以靠mediaDevices来实现。除了摄像头录制之外,我们也可以对屏幕进行录制,这里我们直接使用另一个方法getDisplayMedia来实现,注意屏幕录制也需要单独的权限:

image-20260227001743410
html 复制代码
<div style="position: relative;width: fit-content">
  <video style="position: absolute; bottom: 20px; right: 20px;border: black 2px solid;border-radius: 10px"
         id="user" width="320" height="240" autoplay playsinline></video>
  <video id="screen" width="1280" height="720" autoplay playsinline></video>
</div>
js 复制代码
navigator.mediaDevices.getDisplayMedia({
    video: {
        cursor: 'always', // 显示鼠标光标
        displaySurface: 'monitor' // 可选:monitor/window/browser
    },
    audio: false  // 屏幕音频就不获取了
}).then(stream => {
    const video = document.getElementById('screen')
    video.srcObject = stream
})

这样就可以实现会议室分享屏幕加在线聊天效果了。

文档对象

我们在前面的学习中认识了document对象,它代表我们整个页面文档,通过它,你可以用代码访问、修改甚至删除网页上的任何内容。这一部分,我们来深入了解一下它的更多内容。

首先就是它包含了页面的诸多元数据,如 title, URL, characterSet等:

js 复制代码
console.log(document.title)  //网页标题
console.log(document.characterSet)   //编码格式 

我们可以尝试修改网页的编码格式,此时document的对应属性也会跟着发生变化:

image-20260227003430220

如果我们想要获取页面的地址,也可以通过URL属性来获得:

js 复制代码
console.log(document.URL)
image-20260227003716723

这个属性和location.href类似,但是这里的属性是只读无法修改的。

前面我们介绍了如何向页面添加元素,除此之外,我们还可以直接向页面打印文本内容,会以文本节点的形式进行插入:

js 复制代码
document.writeln("Hello World!")
image-20260227004324417

前面我们学习了如何获取元素,我们可以选择通过元素的id或class属性来快速拿到对应的元素,除此之外,我们还可以一次性获取所有指定类型的元素:

js 复制代码
console.log(document.links)  //获取所有的链接
console.log(document.images)  //获取所有的图片
console.log(document.forms)  //获取所有的表单  

数据存储

浏览器本身是“无状态”的,当页面一刷新,我们创建的所有变量、函数、数据都会烟消云散,为了保证用户每次访问网站时能够保留之前使用的一些数据,我们需要一种机制,把数据“保存下来”。

浏览器常见的数据存储方式有以下方式:

存储方式 大小 生命周期 是否发给服务器 适用场景
Cookie 小(4KB) 可设置 ✅ 会自动发送 登录状态
localStorage 5~10MB 永久 ❌ 不会 本地缓存
sessionStorage 5MB 会话级,标签页关闭就消失 ❌ 不会 临时状态
IndexedDB 很大 永久 ❌ 不会 大型数据
Cache Storage 很大 永久 ❌ 不会 PWA缓存
Memory 小 页面刷新就消失 ❌ 不会 临时变量

Memory就是我们一直以来使用的数据存储方式,所有的数据都存储在内存中,页面关闭之后自动释放,不留下任何痕迹。这一节我们来介绍其他五种数据存储方式。

首先是最简单的LocalStorage,它是 Web Storage API 的一部分,用来在浏览器中持久化存储数据。所有存储在其中的数据都不会随着页面的刷新而消失,关闭浏览器后仍然存在。

在LocalStorage中,数据是以键值对形式存储的,类似于Map,我们可以直接使用:

js 复制代码
//注意键值对需要以字符串形式存放
localStorage.setItem("AAA", "建材王总")

由于键值对只能以字符串形式存放,所以这里建议将对象转换为JSON格式进行存储,直接存储对象会导致类型转换,变成"[object Object]"。

添加键值对后,我们可以直接在开发者工具中查看存放了哪些数据:

image-20260227112034591

存储在LocalStorage的数据是永久保存的,即使我们刷新网页,下次依然可以访问到。我们可以使用getItem来获取之前保存的数据:

js 复制代码
console.log(localStorage.getItem("AAA"))

其他方法如下:

js 复制代码
localStorage.key(0)   //按下标位置获取Key
localStorage.removeItem("AAA")   //移除键值对
localStorage.length   //键值对数量
localStorage.clear()   //清空所有数据

不过需要注意的是,LocalStorage采用的是同源策略,如果我们跳转到其他站点上,在之前站点存储的数据是不通用的,每个站点的数据独立存放:

image-20260227113451949

所以各位小伙伴无需担心其他网站污染了我们自己存储的数据,因为都是独立的。接着我们要介绍一个和LocalStorage非常类似的存储方案:SessionStorage,它的用法和前者非常类似,也是键值对:

js 复制代码
sessionStorage.setItem("AAA", "建材王总")

与LocalStorage的区别是,它是存储在会话存储空间中的:

image-20260227113959061

会话存储空间和本地存储空间不同,当我们关闭当前浏览器标签页时,会话会自动结束,同时也会清理会话空间存储的数据。注意,只要当前标签页不关闭,即使我们刷新网页也不会导致SessionStorage被清理,因为会话没有结束,所以一定要注意它和Memory数据的区别。

还有就是SessionStorage也遵循同源策略,但仅限于同标签页内:

  1. 即使在同一个会话下跳转到其他页面也是无法读取的
  2. 由于是会话级别的数据,所以不同标签页之间即使是同一个站点也是无法读取的

这种存储更适合一些表单临时数据、支付流程存储等场景。

接着我们来介绍一下Cookie,它是浏览器提供的一种小型文本数据存储机制,用于在客户端(浏览器)保存少量数据,并在之后的请求中自动携带给服务器。

Cookie的格式是:

复制代码
name=value; expires=时间; path=/; domain=xxx; secure; HttpOnly; SameSite

它包含自己的名称、过期时间、路径、所属的域、安全性、仅网络可用、同源策略。

要添加Cookie有两种方式:

  • 服务器通过 Set-Cookie 响应头来为客户端添加新的Cookie
  • 通过cookieStore手动添加新的Cookie

由于我们还未解除到服务端环境,所以这里我们仅演示第二种方式,使用set方法即可:

js 复制代码
cookieStore.set({
    name: 'test',   //传入需要设置的Cookie名字
    value: '我是数据'   //Cookie的数据
})
image-20260227121328343

可以看到,这里我们成功插入了一个新的Cookie,名称和值就是我们刚刚设定好的内容。需要注意的是,设置Cookie和之前的LocalStorage不同,它不是同步进行的,这里会得到一个Promise对象,当设置Cookie真正完成之后,才会结果。

我们可以尝试刷新网页重新请求页面,可以看到请求头会携带我们添加的自定义Cookie:

image-20260227121839241

像一些用于表明身份的令牌数据,就可以通过Cookie来进行传递,所以你也可以把Cookie理解为是浏览器帮服务器“记住用户身份”的小纸条。当然,如果存在多条Cookie信息,那么会以分号分隔:

image-20260227134548611

我们也可以通过docuemnt.cookie来获取成品字符串:

js 复制代码
console.log(document.cookie)

默认情况下,我们创建的Cookie是存在过期时间的,如果什么都不设置那么过期时间就是会话周期内,这和前面介绍的SessionStorage是一样的。当Cookie过期之后,就会自动被移除,我们也可以手动为Cookie设置过期时间:

js 复制代码
cookieStore.set({
    name: 'test',
    value: '我是数据',
    expires: Date.now() + 3600 * 1000  //当前时间加上1个小时
})
image-20260227132413418

这里简单介绍一下后续几个参数:

  • domain:当前Cookie可用的域名,注意只能是当前站点或子站点的域名,不能随意指定
  • path:当前Cookie生效的路径起始位置,默认是/表示所有路径都适用
  • secure:表示只有在安全环境下才能使用,比如localhost或是https站点
  • HttpOnly:表示只用作请求,不能被JS随意读取或修改
  • SameSite:控制跨站请求是否携带Cookie信息,默认是严格模式,完全禁止跨站携带(比如在某个网站流量数据,结果后台恶意发起一条银行网站的转账请求,如果携带Cookie将会导致账户盗刷)

不过,虽然前面介绍这三种存储方案都各有好处,但是都存在一个很大的问题,就是容量有限制。接下介绍一下IndexedDB,它也是浏览器提供的一种本地数据库存储方案,用于在客户端存储大量结构化数据。它比 localStorage、sessionStorage 更强大,它支持存储大量数据(MB ~ GB 级)就像后端的数据库一样,可以装下很多东西。

还有一种是专用于PWA渐进式应用程序页面缓存的CacheStorage,由于目前暂时接触不到,所以这里暂时不做详细介绍。

有关IndexedDB和CacheStorage的内容,我们会放到后续的《前端工程化》课程中进行详细介绍。

本章练习

实现真视差滚动

苹果官网完善

正在加载页面,请稍后...