主题
2-16location、navigator、history对象和本地存储
本页对应 第二章WebAPIs,共 6 段视频,约 49 分钟。
学习目标
- 能说出
location对象的href、search、hash三个属性分别返回地址中的哪部分内容,并通过给location.href赋值实现 JS 跳转、调用location.reload()刷新当前页面。 - 能独立编写“5 秒后自动返回首页”案例:用
setInterval让计数变量每秒递减,并把最新值渲染到页面元素;计数归零时用clearInterval清除定时器,并用location.href自动跳转。 - 能使用
navigator.userAgent判断当前是移动端还是 PC 端访问,并据此跳转到对应版本的页面。 - 能调用
history.forward()、history.back()、history.go(1)、history.go(-1)实现页面的前进与后退。 - 能用
localStorage.setItem、getItem、removeItem完成数据的存储、读取和删除,并能说明数据在刷新或关闭浏览器后仍保留,以及在开发者工具 Application 面板中查看和清除这些数据。 - 能说明 localStorage 只能存储字符串,并使用
JSON.stringify把对象或数组转成 JSON 字符串后存储,再用JSON.parse取出并转回原对象,从而访问其属性。
知识点
本地存储为什么需要
为什么需要: 页面刷新或关闭后,普通变量里的数据会丢失;本地存储可以把数据保存在浏览器本地,实现数据持久化。典型场景是 TodoMVC 这类待办清单:刷新页面后,已添加的事项仍然存在。转写稿中演示了在待办网站输入内容后刷新、重新打开页面,数据仍不丢失。
怎么想: 本地存储本质是把数据存到浏览器里,常见有 localStorage 和 sessionStorage。二者容量约 5M 左右,开发中重点用 localStorage。
怎么做: 先记住 localStorage 的完整写法是 window.localStorage,平时省略 window。它用键值对存储,并且存进去的是字符串。
易错点: 不要以为存了数字 18,取出来就还是数字;取出来是字符串。也不要把它当成普通对象直接塞复杂数据。
localStorage 的基本存取删
为什么需要: 数据存进去后要能读取、删除,才能完成“持久化—使用—清理”的完整流程。
怎么想: 存用 setItem(key, value),取用 getItem(key),删用 removeItem(key)。键要写成字符串形式。
怎么做: 笔记代码如下:

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>本地存储-localstorage</title>
</head>
<body>
<script>
// 本地存储 - localstorage 存储的是字符串
// 1. 存储
localStorage.setItem('age', 18)
// 2. 获取
console.log(typeof localStorage.getItem('age'))
// 3. 删除
localStorage.removeItem('age')
</script>
</body>
</html>调试时打开开发者工具,在“应用 / Application”里找到“本地存储”,可以看到 age 的值;刷新页面或关闭再打开,数据仍在。控制台里 typeof localStorage.getItem('age') 输出 string,说明取回的是字符串。
易错点: setItem 的键要加引号;删除时 removeItem('age') 只删对应键值;不要写成 localStorage.age 这种不规范的混用方式。
sessionStorage 了解即可
为什么需要: 有些数据只希望在当前会话中使用,浏览器关闭就清除,这时可用 sessionStorage。
怎么想: 它的用法和 localStorage 基本相同,区别是关闭浏览器页面后数据会被清除。
怎么做: 存储 sessionStorage.setItem(key, value),获取 sessionStorage.getItem(key),删除 sessionStorage.removeItem(key)。
易错点: 不要把“关闭页面”和“刷新页面”混为一谈;重点掌握 localStorage,sessionStorage 了解区别即可。
存储复杂数据类型:先转 JSON 字符串
为什么需要: 对象、数组这类复杂数据类型很常用,但本地存储只能存字符串。直接存对象会出问题。
怎么想: 转写稿中直接 localStorage.setItem('goods', goods) 后,在应用面板看到的是 [object Object],取出来也无法直接使用。解决思路是:先把对象转成 JSON 字符串,再存进去。
怎么做: 用 JSON.stringify(复杂数据类型) 转换。代码如下:
html
<body>
<script>
// 本地存储复杂数据类型
const goods = {
name: '小米',
price: 1999
}
// localStorage.setItem('goods', goods)
// console.log(localStorage.getItem('goods'))
// 1. 把对象转换为JSON字符串 JSON.stringify
localStorage.setItem('goods', JSON.stringify(goods))
// console.log(typeof localStorage.getItem('goods'))
</script>
</body>JSON 字符串首先是一个字符串,属性名必须用双引号,字符串类型的属性值也必须用双引号。调试时在应用面板能看到类似 {"name":"小米","price":1999} 的内容。
易错点: JSON 里不能写单引号;JSON.stringify 只是转成字符串,取出来仍然是字符串,不能马上当对象用。
取回复杂数据类型:再转回对象
为什么需要: localStorage.getItem('goods') 取回的是 JSON 字符串,不是对象,不能直接访问 goods.name。
怎么想: 存的时候“对象 → JSON 字符串”,取的时候反过来“JSON 字符串 → 对象”。
怎么做: 用 JSON.parse(JSON字符串) 转换。代码如下:
html
<body>
<script>
// 本地存储复杂数据类型
const goods = {
name: '小米',
price: 1999
}
// localStorage.setItem('goods', goods)
// console.log(localStorage.getItem('goods'))
// 1. 把对象转换为JSON字符串 JSON.stringify
localStorage.setItem('goods', JSON.stringify(goods))
// console.log(typeof localStorage.getItem('goods'))
// 2. 把JSON字符串转换为对象 JSON.parse
console.log(JSON.parse(localStorage.getItem('goods')))
</script>
</body>调试时先看 typeof localStorage.getItem('goods'),结果是 string;再用 JSON.parse 包一层,控制台输出对象形式,就可以继续使用里面的属性。
易错点: 不要对普通字符串乱用 JSON.parse,它要求内容是合法 JSON;存复杂数据记住两步:JSON.stringify 存,JSON.parse 取。
先说明本节范围
本节笔记的主题是“综合案例”中的数组 map 与 join 方法。你提供的转写稿讲的是 BOM 中的 location、navigator、history、localStorage,与本节笔记的 map/join 不是同一知识点。为避免编造,下面不把 BOM 内容硬塞进本节,只依据笔记中的说明和代码扩写;本节笔记未附图片,因此没有图片 alt 需要替换。
数组 map 方法:为什么需要
当你要“遍历数组、处理每个元素,并且把处理结果收集成一个新数组”时,就需要 map。笔记里写得很明确:map 可以遍历数组处理数据,并且返回新的数组。它和普通遍历的区别在于:map 的重点是“有返回值”,而笔记也特别对比了 forEach——forEach 没有返回值,结果是 undefined。所以,如果后续还要拿着处理后的结果继续用,优先想到 map。
数组 map 方法:怎么想
先把需求拆成两个问题:第一,我是不是要对数组里的每个元素做同一种加工?第二,加工完的结果要不要形成一个新数组?如果两个答案都是“是”,就用 map。笔记中把 map 称为“映射”,并解释为两个元素的集之间元素相互“对应”的关系。放到代码里,就是原数组的 red 对应新数组的 red颜色,blue 对应 blue颜色,pink 对应 pink颜色。回调函数接收 ele 和 index,笔记注释说明:ele 是数组元素,index 是索引号。
数组 map 方法:怎么做
笔记代码如下,保留原样:
javascript
<body>
<script>
const arr = ['red', 'blue', 'pink']
// 1. 数组 map方法 处理数据并且 返回一个数组
const newArr = arr.map(function (ele, index) {
// console.log(ele) // 数组元素
// console.log(index) // 索引号
return ele + '颜色'
})
console.log(newArr)
</script>
</body>调试时,先按笔记注释把 ele、index 打印出来,确认回调参数拿到了什么;再取消注释或直接打印 newArr,检查是否得到处理后的新数组。关键动作是 return ele + '颜色':没有 return,map 收集不到加工结果。
map 易错点
第一,忘记写 return,回调虽然执行了,但新数组元素会变成 undefined。第二,把 map 当成 forEach 用,只想遍历却忽略了它会返回新数组。第三,误以为 map 的重点是“改原数组”;笔记强调的是“返回新的数组”,所以要用变量接住返回值,例如 const newArr = ...。第四,回调参数名可以变,但位置含义要清楚:第一个是元素,第二个是索引。
数组 join 方法:为什么需要
当你已经有一个数组,但页面或后续逻辑需要的是“字符串”时,就需要 join。笔记写明:join() 方法用于把数组中的所有元素转换一个字符串。它不是继续加工数组元素,而是解决“数组怎么拼成一个字符串”的问题。
数组 join 方法:怎么想与怎么做
先想分隔符:不传参数时,笔记说“小括号为空则逗号分割”;传空字符串时,“元素之间没有分隔符”;传 '|' 时,就用竖线分隔。笔记代码如下,保留原样:
html
<body>
<script>
const arr = ['red', 'blue', 'pink']
// 1. 数组 map方法 处理数据并且 返回一个数组
const newArr = arr.map(function (ele, index) {
// console.log(ele) // 数组元素
// console.log(index) // 索引号
return ele + '颜色'
})
console.log(newArr)
// 2. 数组join方法 把数组转换为字符串
// 小括号为空则逗号分割
console.log(newArr.join()) // red颜色,blue颜色,pink颜色
// 小括号是空字符串,则元素之间没有分隔符
console.log(newArr.join('')) //red颜色blue颜色pink颜色
console.log(newArr.join('|')) //red颜色|blue颜色|pink颜色
</script>
</body>调试过程就是连续打印三次 join 结果,对照笔记注释看分隔符是否符合预期:空括号是逗号,空字符串是无分隔,'|' 是竖线。
join 易错点
第一,把 join() 和 join('') 混为一谈:前者默认逗号,后者完全没有分隔符。第二,忘记 join 返回的是字符串,不是数组。第三,数组元素本身是对象或复杂数据时,直接 join 得到的内容可能不是你想要的结构;本节笔记只演示字符串数组,不要额外推断。第四,join 是对数组操作,调用前要确认变量确实是数组。
综合案例小结
本节两个方法可以串起来用:先用 map 把原数组加工成新数组,再用 join 把新数组拼成字符串。判断标准很简单:要“加工并得到新数组”想 map;要“数组转字符串并控制分隔符”想 join。同时记住笔记中的关键对比:map 有返回值,forEach 没有返回值。
课件图示








代码示例
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>本地存储-localStorage</title>
</head>
<body>
<script>
// 本地存储 - localStorage 存储的是字符串
// 1. 存储
localStorage.setItem('age', 18)
// 2. 获取
console.log(typeof localStorage.getItem('age')) // string
// 3. 删除
localStorage.removeItem('age')
// sessionStorage 用法相同,区别:浏览器关闭后数据被清除
sessionStorage.setItem('name', 'pink')
console.log(sessionStorage.getItem('name'))
sessionStorage.removeItem('name')
</script>
</body>
</html>要点解释
localStorage.setItem(key, value)以键值对形式存储,key 必须加引号(字符串),省略了window,完整写法是window.localStorage.setItem。localStorage.getItem(key)取出的是字符串,所以typeof打印出来是string,即使你存的是数字18。localStorage.removeItem(key)按键删除,值会跟着一起被删掉;在浏览器「应用 / Application → 本地存储」面板里可以查看和手动清除。- 易错点:
localStorage中的S是大写(localStorage,不是localstorage);setItem中的I也是大写。 - 易错点:
sessionStorage关闭浏览器就清空,localStorage不会自动清除,除非手动删除——两者用法完全一样,只换对象名。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>本地存储复杂数据类型</title>
</head>
<body>
<script>
// 本地存储复杂数据类型
const goods = {
name: '小米',
price: 1999
}
// 直接存对象:本地存储里会变成 [object Object],取出来无法使用
// localStorage.setItem('goods', goods)
// console.log(localStorage.getItem('goods'))
// 1. 把对象转换为 JSON 字符串 JSON.stringify
localStorage.setItem('goods', JSON.stringify(goods))
// console.log(typeof localStorage.getItem('goods')) // string
// 2. 把 JSON 字符串转换为对象 JSON.parse
console.log(JSON.parse(localStorage.getItem('goods')))
</script>
</body>
</html>要点解释
- 本地存储只能存字符串,直接
setItem('goods', goods)存进去会变成[object Object],数据等于丢了。 - 存储前必须
JSON.stringify(对象)转成 JSON 字符串;取出来时必须JSON.parse(字符串)转回对象才能用.name、.price。 - 易错点:取出来不解析就直接用,得到的是字符串,
typeof是string而不是object,无法访问属性。 - 易错点:
JSON两个字母都要大写;stringify与parse别写反——存用 stringify,取用 parse。 - JSON 字符串的特点:本身是字符串;属性名必须用双引号,不能用单引号;字符串类型的属性值也必须双引号。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数组 map 与 join</title>
</head>
<body>
<script>
const arr = ['red', 'blue', 'pink']
// 1. 数组 map 方法 处理数据并且返回一个数组
const newArr = arr.map(function (ele, index) {
// console.log(ele) // 数组元素
// console.log(index) // 索引号
return ele + '颜色'
})
console.log(newArr) // ['red颜色', 'blue颜色', 'pink颜色']
// 2. 数组 join 方法 把数组转换为字符串
// 小括号为空则逗号分割
console.log(newArr.join()) // red颜色,blue颜色,pink颜色
// 小括号是空字符串,则元素之间没有分隔符
console.log(newArr.join('')) // red颜色blue颜色pink颜色
console.log(newArr.join('|')) // red颜色|blue颜色|pink颜色
</script>
</body>
</html>要点解释
map遍历数组并返回一个新数组,return的结果就是新数组里的元素;forEach没有返回值(undefined),这是两者最大区别。- 易错点:
map里忘记return,新数组会全是undefined。 join()把数组转成字符串:括号为空默认用逗号分隔,括号里给空字符串''则元素紧挨着没有分隔符。- 组合用法:
arr.map(...).join('')常用来把数据数组拼接成 HTML 字符串,再交给本地存储或渲染到页面——这样存进localStorage的天然就是字符串,省去JSON.stringify。
常见错误
1. 直接把对象塞进本地存储
- 现象:取出来是
"[object Object]",或者打印null。 - 原因:
setItem只接收字符串,对象会被隐式toString(),信息已经丢了,不是"存进去再取回来"的问题。 - 修法:写入前
JSON.stringify(obj),读取后JSON.parse(str)。养成"存前序列化、取后反序列化"的固定动作。
2. 取到 JSON 字符串就直接当对象用
- 现象:
JSON.parse忘了写,data.name打印undefined,但console.log(data)看起来"有内容"。 - 原因:控制台把字符串美化显示,肉眼像对象;实际
typeof是string。 - 修法:取完先
console.log(typeof data)自检;或统一封装getData(key){ return JSON.parse(localStorage.getItem(key) || 'null') }。
3. 键名不一致 / 大小写不统一
- 现象:明明存过,
getItem返回null,Application 面板里却能看到数据。 - 原因:
setItem('age')与getItem('Age')、'userInfo'与'userinfo'不匹配;键名区分大小写。 - 修法:键名抽成常量或统一前缀(如
'todo_list'),项目内只走一处封装函数,不手写字符串。
4. 存的是数字,取出来是字符串却按数字比较
- 现象:
getItem('age') === 18为false;累加变成'181'字符串拼接。 - 原因:本地存储只有字符串,
'18'与18全等不成立。 - 修法:取出后显式
Number(...)/JSON.parse(...)转换;比较一律用==之外的显式转换,不靠隐式转换。
5. 定时器里用 location.href 跳转却不清定时器
- 现象:倒计时到 0 后页面反复跳转、控制台疯狂输出;或
reload写在setInterval里导致页面刷不停。 - 原因:
setInterval没有clearInterval(timerId),条件满足后仍在执行;跳转/刷新被当成普通语句重复触发。 - 修法:定时器 ID 用变量接住;在跳转前先
clearInterval(timerId),再location.href = url;reload只在一次性事件(如点击)里调用。
6. 用 sessionStorage 存需要长期保留的数据
- 现象:本地调试正常,关掉浏览器重开数据全没了,误判为"代码写错了"。
- 原因:
sessionStorage生命周期绑定标签页/浏览器会话,关闭即清空;与localStorage混用。 - 修法:需要持久化的一律
localStorage;只有"一次性流程状态"(如表单分步、临时筛选条件)才用sessionStorage。
练习
练习 1|对象存取闭环 定义 user = { name:'张三', age:18, tags:['a','b'] },存入 localStorage 键 user_info。刷新页面后读取,要求:
- Application 面板里看到的是标准 JSON(属性名双引号);
- 页面打印
张三和tags的第二个元素; - 控制台输出
typeof结果为object。
练习 2|安全读取 + 默认值 封装 getData(key, defaultValue):键不存在或内容被手动改坏(在 Application 面板里改成 abc)时,返回 defaultValue 而不是抛错。用 try/catch 或判空实现,并写两个测试用例验证。
练习 3|列表渲染(map + join) 数组 ['学习本地存储','完成练习','复习location'],用 map 生成 <li> 字符串、join('') 拼接后插入 ul。要求:
- 故意先漏写
return,观察页面出现undefined的效果,再补上; - 把该数组
JSON.stringify存到本地,刷新后从本地读取并重新渲染,数据不丢。
练习 4|5 秒倒计时跳转(改造版) 实现"5 秒后自动跳转",并额外满足:
- 倒计时到 0 时先清定时器再跳转,控制台不出现重复打印;
- 页面上提供一个"停止跳转"按钮,点击后倒计时停在当前数字且不再跳转。
练习 5|参数读取 在页面地址后手动加上 ?keyword=笔记本#music,用 location.search 和 location.hash 分别取出并打印;再写一个输入框 + 按钮,点击后把输入内容拼到地址的问号参数上并跳转(location.href),验证刷新后参数仍在。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | location对象 | 10:24 |
| 2 | 5秒钟之后返回首页案例 | 07:02 |
| 3 | navigator对象 | 04:29 |
| 4 | histroy对象 | 05:40 |
| 5 | 本地存储数据持久化localstorage | 10:07 |
| 6 | 本地存储复杂数据类型 | 11:56 |
小结
- 本地存储的本质是"字符串容器",所有复杂类型的坑都源于这一点;把
stringify/parse当成一对固定动作,比记 API 更重要。 - 存取代码不要散落在业务逻辑里,统一封装
setData/getData并内置序列化与容错,能一次性消灭键名不一致、忘记 parse、脏数据崩溃三类问题。 - 判断"数据到底存没存进去",第一动作是打开 Application 面板看原始值,而不是反复改代码——看得见的值才是事实。
location.href是页面跳转的主力,reload是"重来一次"的暴力手段;凡是放在定时器里的跳转/刷新,必须先清定时器再执行。search和hash是"把状态写进地址"的两种方式:前者用于带参数的查询,后者用于不刷新页面的局部切换,后者在框架路由里会被大量复用。navigator.userAgent那段判断代码属于"抄来即用"的工具代码,理解意图(判断终端 → 跳对应站点)即可,不必逐字记忆。history的前进后退用得少,go(±1)比forward/back更好记;真正需要它时再查,不要为它提前投入学习成本。- 数据持久化要区分"该留多久":长期配置用
localStorage,会话内临时状态用sessionStorage,敏感信息两个都不要放。