Skip to content

2-16location、navigator、history对象和本地存储 ​

本页对应 第二章WebAPIs,共 6 段视频,约 49 分钟。

学习目标 ​

  1. 能说出 location 对象的 href、search、hash 三个属性分别返回地址中的哪部分内容,并通过给 location.href 赋值实现 JS 跳转、调用 location.reload() 刷新当前页面。
  2. 能独立编写“5 秒后自动返回首页”案例:用 setInterval 让计数变量每秒递减,并把最新值渲染到页面元素;计数归零时用 clearInterval 清除定时器,并用 location.href 自动跳转。
  3. 能使用 navigator.userAgent 判断当前是移动端还是 PC 端访问,并据此跳转到对应版本的页面。
  4. 能调用 history.forward()、history.back()、history.go(1)、history.go(-1) 实现页面的前进与后退。
  5. 能用 localStorage.setItem、getItem、removeItem 完成数据的存储、读取和删除,并能说明数据在刷新或关闭浏览器后仍保留,以及在开发者工具 Application 面板中查看和清除这些数据。
  6. 能说明 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 没有返回值。

课件图示 ​

课件第20页:1.6 本地存储(今日重点) 本地存储:将数据存储在本地浏览器中 常见的使用场景

课件第21页:1.6 本地存储分类- localStorage(重点) l 作用: 数据可以长

课件第24页:1.6 本地存储分类- localStorage l 浏览器查看本地数据:

课件第27页:1.6 本地存储分类- localStorage l 解决:需要将复杂数据类型转

课件第28页:1.6 本地存储分类- localStorage l 问题:因为本地存储里面取出

课件第29页:1.6 本地存储分类- localStorage l 解决:把取出来的字符串转换

课件第30页:1.6 本地存储分类- localStorage l 解决方案: 对象 JSON

课件第35页:步骤 渲染业务 新增业务 删除业务 学生就业统计表 本地存储数据 修改数据 修改

代码示例 ​

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),验证刷新后参数仍在。

原视频对照 ​

#视频时长
1location对象10:24
25秒钟之后返回首页案例07:02
3navigator对象04:29
4histroy对象05:40
5本地存储数据持久化localstorage10:07
6本地存储复杂数据类型11:56

小结 ​

  1. 本地存储的本质是"字符串容器",所有复杂类型的坑都源于这一点;把 stringify / parse 当成一对固定动作,比记 API 更重要。
  2. 存取代码不要散落在业务逻辑里,统一封装 setData / getData 并内置序列化与容错,能一次性消灭键名不一致、忘记 parse、脏数据崩溃三类问题。
  3. 判断"数据到底存没存进去",第一动作是打开 Application 面板看原始值,而不是反复改代码——看得见的值才是事实。
  4. location.href 是页面跳转的主力,reload 是"重来一次"的暴力手段;凡是放在定时器里的跳转/刷新,必须先清定时器再执行。
  5. search 和 hash 是"把状态写进地址"的两种方式:前者用于带参数的查询,后者用于不刷新页面的局部切换,后者在框架路由里会被大量复用。
  6. navigator.userAgent 那段判断代码属于"抄来即用"的工具代码,理解意图(判断终端 → 跳对应站点)即可,不必逐字记忆。
  7. history 的前进后退用得少,go(±1) 比 forward / back 更好记;真正需要它时再查,不要为它提前投入学习成本。
  8. 数据持久化要区分"该留多久":长期配置用 localStorage,会话内临时状态用 sessionStorage,敏感信息两个都不要放。