认识Storage

WebStorage主要提供了一种机制,可以让浏览器提供一种比cookie更直观的key、value存储方式:

  • localStorage:本地存储,提供的是一种永久性的存储方法,在关闭掉网页重新打开时,存储的内容依然保留;
  • sessionStorage:会话存储,提供的是本次会话的存储,在关闭掉会话时,存储的内容会被清除

      // 1.token的操作
      let token = localStorage.getItem("token");
      if (!token) {
        console.log("从服务器获取token");
        token = "coderFtokenInfo";
        localStorage.setItem("token", token);
      }
      
      // 2.username/password
      let username = localStorage.getItem("username");
      let password = localStorage.getItem("password");
      if (!username || !password) {
        console.log("让用户输入账号密码");
        username = "coderF";
        password = "123456";
        
        // 将 token/username/password保存到Storage
        localStorage.setItem("username", username);
        localStorage.setItem("password", password);
      }

      // 3.后续操作
      console.log(token);
      console.log(token.length);
      console.log(token + "哈哈哈");

localStorage和sessionStorage的区别

我们会发现localStorage和sessionStorage看起来非常的相似。

那么它们有什么区别呢?

  • 验证一:关闭网页后重新打开,localStorage会保留,而sessionStorage会被删除;
  • 验证二:在页面内实现跳转,localStorage会保留,sessionStorage也会保留;
  • 验证三:在页面外实现跳转(打开新的网页),localStorage会保留,sessionStorage不会被保留;
    <a href="./static/about.html" target="_blank">关于页面</a>
    <script>
      // 验证一:关闭网页
      // 1.localStorage的存储保持
      // localStorage.setItem("name", "localStorage");
      // 2.sessionStorage的存储会消失
      // sessionStorage.setItem("name", "localStorage");
      
      // 验证二:打开新的网页
      // localStorage.setItem("info", "local");
      // sessionStorage.setItem("info", "session");

      // 验证三:打开新的网页,并且是在新的标签中打开
      localStorage.setItem("infoTab", "local");
      sessionStorage.setItem("infoTab", "session");
    </script>

Storage常见的方法和属性

Storage有如下的属性和方法:
属性:

  • Storage.length:只读属性
    • 返回一个整数,表示存储在Storage对象中的数据项数量;
      方法:
  • Storage.key(index):该方法接受一个数值n作为参数,返回存储中的第n个key名称;
  • Storage.getItem():该方法接受一个key作为参数,并且返回key对应的value;
  • Storage.setItem():该方法接受一个key和value,并且将会把key和value添加到存储中。
    • 如果key存储,则更新其对应的值;
  • Storage.removeItem():该方法接受一个key作为参数,并把该key从存储中删除;
  • Storage.clear():该方法的作用是清空存储中的所有key;
      const ACCESS_TOKEN = "token";
      console.log(localStorage.length);

      // 常见的方法
      localStorage.setItem(ACCESS_TOKEN, "rinToken");
      console.log(localStorage.getItem(ACCESS_TOKEN));

      // 其他方法
      console.log(localStorage.key(0));
      console.log(localStorage.key(1));
      console.log(localStorage.removeItem("infoTab"));
      localStorage.clear()

cache类工具封装

class Cache {
  constructor(isLocal = true) {
    this.storage = isLocal ? localStorage : sessionStorage;
  }

  setCache(key, value) {
    if (!value) {
      throw new Error("value error: value 必须有值");
    }
    if (value) {
      this.storage.setItem(key, JSON.stringify(value));
    }
  }

  getCache(key) {
    const result = this.storage.getItem(key);
    if (result) {
      return JSON.parse(result);
    }
  }
  
  removeCache(key) {
    this.storage.removeItem(key);
  }

  clear() {
    this.storage.clear();
  }
}

const localCache = new Cache();
const sessionCache = new Cache(false);
    <script src="./js/cache.js"></script>
    <script>
      localCache.setCache("sno", 111);
      
      // Storage 本身是不能直接存储对象类型的
      const userInfo = {
        name: "rin",
        nickname: "coderRin",
        level: 100,
        imgUrl: "http://github.com/coderRin.png",
      };
      
      // localStorage.setItem("userInfo", JSON.stringify(userInfo));
      // console.log(JSON.parse(localStorage.getItem("userInfo")));
      sessionCache.setCache("userInfo", userInfo);
      console.log(sessionCache.getCache("userInfo"));
    </script>

什么是正则表达式?

我们先来看一下维基百科对正则表达式的解释:

  • 正则表达式(英语:Regular Expression,常简写为regex、regexp或RE),又称正则表示式、正则表示法、规则表达式、常规表示法,是计算机科学的一个概念;
  • 正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。
  • 许多程序设计语言都支持利用正则表达式进行字符串操作。

简单概况:正则表达式是一种字符串匹配利器,可以帮助我们搜索、获取、替代字符串;

在JavaScript中,正则表达式使用RegExp类来创建,也有对应的字面量的方式:

  • 正则表达式主要由两部分组成:模式(patterns)和修饰符(flags)

    // 创建正则
    /*
      参数
        1.匹配的规则 pattern
        2.匹配的修饰符 flags
    */
    const re1 = new RegExp('abc','ig')
    const re2 = /acb/ig // 我是注释

正则表达式的使用方法

有了正则表达式我们要如何使用它呢?

  • JavaScript中的正则表达式被用于 RegExp 的 exec 和 test 方法;
  • 也包括 String 的 match、matchAll、replace、search 和 split 方法;

      const re1 = /acb/gi;
      const message = "fda323bc fa54Bc87 23d09fABC 87AaaB43c65";

      // 1.使用正则对象上的实例方法
      // 2.使用字符串的方法,传入一个正则表达式
      // 需求: 将所有的 abc(忽略大小写) 换成cba
      const newMessage = message.replaceAll(/abc/gi, "cba");
      console.log(newMessage);
      
      // 需求: 将字符串中数字全部删除
      const newMessage2 = message.replaceAll(/\d+/gi, "");
      console.log(newMessage2);
      const re1 = /abc/gi;

      const message = "fdabc123 fa54Bc87 23d09fABC 87AaaB43c65";

      // 1.使用正则对象上的实例方法
      // webpack -> loader -> test: 匹配文件名
      // 正则 -> 拿到文件 -> loader操作
      // 1.1test方法: 检测某一个字符串是否符合正则的规则
      /* if (re1.test(message)) {
        console.log("message 符合规则");
      } else {
        console.log("message 不符合规则");
      } */

      // 1.2 exec 方法:使用正则执行一个字符串
      /* const res1 = re1.exec(message);
      console.log(res1); */

      // 2.使用字符串的方法,传入一个正则表达式
      // 2.1 match 方法
      /* const result2 = message.match(re1);
      console.log(result2) */

      // 2.2 matchAll 方法
      // 注意: match 传入的正则修饰符必须 + g
      const result3 = message.matchAll(re1);
      /* console.log(result3.next());
      console.log(result3.next());
      console.log(result3.next());
      console.log(result3.next()); */
      for (const item of result3) {
        // console.log(item)
      }

      // 2.3 replace/replaceAll

      // 2.4 split
      const result4 = message.split(re1);
      console.log(result4);

      // 2.5 search
      const result5 = message.search(re1);
      console.log(result5);

      // 案例: 让用户输入的账号必须包含5个a
      /* const inputEl = document.querySelector("input");
      const tipEl = document.querySelector(".tip");

      inputEl.oninput = function () {
        const value = this.value;
        if (/^a{5,8}$/.test(value)) {
          tipEl.textContent = "输入的内容符合规则要求";
        } else {
          tipEl.textContent = "输入的内容不符合规则要求,请重新输入";
        } 
      }; */

修饰符flag的使用

常见的修饰符:

需求:

  • 获取一个字符串中所有的abc;
  • 将一个字符串中的所有abc换成大写;

规则 – 字符类(Character classes)

字符类(Character classes) 是一个特殊的符号,匹配特定集中的任何符号。

反向类(Inverse classes)

  • \D 非数字:除 \d 以外的任何字符,例如字母。
  • \S 非空格符号:除 \s 以外的任何字符,例如字母。
  • \W 非单字字符:除 \w 以外的任何字符,例如非拉丁字母或空格。

规则 – 锚点(Anchors)

符号 ^ 和符号 $ 在正则表达式中具有特殊的意义,它们被称为“锚点”。

  • 符号 ^ 匹配文本开头;
  • 符号 $ 匹配文本末尾;
      const message = "My name is WHAT.";

      // 字符串方法
      if (message.startsWith("my")) {
        console.log("以 my 开头");
      }
      if (message.endsWith("what")) {
        console.log("以 what 结尾");
      }

      // 正则: 锚点
      if (/^my/i.test(message)) {
        console.log("以my开头");
      }

      if (/what\.$/i.test(message)) {
        console.log("以 what 结尾");
      }

      const re = /^coder$/;
      const info = "codaaaaer";
      console.log(re.test(info)); //false

词边界(Word boundary)

  • 词边界 \b 是一种检查,就像 ^ 和 $ 一样,它会检查字符串中的位置是否是词边界。
  • 词边界测试 \b 检查位置的一侧是否匹配 \w,而另一侧则不匹配 “\w

在字符串 Hello, Java! 中,以下位置对应于 \b

匹配下面字符串中的时间:

      const message = "my name! is WHAT";

      // 需求: name, name必须是一个单独的词
      if (/\bname\b/i.test(message)) {
        console.log("有name,name有边界");
      }

      // 词边界的应用
      const infos = "now time is 21:09, number is 123:456";
      const timeRe = /\b\d\d:\d\d\b/gi;
      console.log(infos.match(timeRe));

规则 – 转义字符串

如果要把特殊字符作为常规字符来使用,需要对其进行转义:

  • 只需要在它前面加个反斜杠;

常见的需要转义的字符:

  • 斜杠符号 ‘/’ 并不是一个特殊符号,但是在字面量正则表达式中也需要转义;

练习:匹配所有以.js或者jsx结尾的文件名

在webpack当中,匹配文件名时就是以这样的方式。

      // 定义正则: 对 . 进行转义
      const re = /\./gi;
      const message = "abc.what";
      const result = message.match(re);
      console.log(result);
      
      // 特殊: /
      // const re2 = /\//

      // 获取到很多的文件名
      const fileNames = [
        "abc.html",
        "index.html",
        "Home.jsx",
        "index.js",
        "util.js",
        "format.js",
      ];

      // 获取所有的js的文件名
      // jsx -> js文件
      // x?: x是0个或者1个
      const jsFileRe = /\.jsx?$/;
      // 1.for循环
      /* const newFileNames = [];
      for (const filename of fileNames) {
        if (jsFileRe.test(filename)) {
          newFileNames.push(filename);
        }
      }
      console.log(newFileNames); */

      const newFileNames = fileNames.filter((filename) =>
        jsFileRe.test(filename)
      );
      console.log(newFileNames);

集合(Sets)和范围(Ranges)

有时候我们只要选择多个匹配字符的其中之一就可以:

  • 在方括号 […] 中的几个字符或者字符类意味着“搜索给定的字符中的任意一个”;

集合(Sets)

  • 比如说,[eao] 意味着查找在 3 个字符 ‘a’、‘e’ 或者 ‘o’ 中的任意一个;

范围(Ranges)

  • 方括号也可以包含字符范围;
  • 比如说,[a-z] 会匹配从 a 到 z 范围内的字母,[0-5] 表示从 0 到 5 的数字;
  • [0-9A-F] 表示两个范围:它搜索一个字符,满足数字 0 到 9 或字母 A 到 F;
  • \d —— 和 [0-9] 相同;
  • \w —— 和 [a-zA-Z0-9_] 相同;

案例:匹配手机号码

排除范围:除了普通的范围匹配,还有类似 [^…] 的“排除”范围匹配;

      // 手机号的规则: 1[3456789]
      const phoneStarts = ["132", "130", "158", "186"];
      const phoneStartRe = /^1[3-9]\d/;
      const filterPhone = phoneStarts.filter((phone) =>
        phoneStartRe.test(phone)
      );
      console.log(filterPhone);

      const phoneNum = "15836578687";
      const phoneRe = /^1[3-9]\d{9}$/;
      console.log(phoneRe.test(phoneNum));

      // 了解: 排除范围
      // \d => [0-9]
      // \D => [^0-9] 不能是这个范围

量词(Quantifiers)

假设我们有一个字符串 +7(903)-123-45-67,并且想要找到它包含的所有数字。

  • 因为它们的数量是不同的,所以我们需要给与数量一个范围;
  • 用来形容我们所需要的数量的词被称为量词( Quantifiers )。

数量 {n}

  • 确切的位数:
  • 某个范围的位数:

缩写:

  • +:代表“一个或多个”,相当于
  • ?:代表“零个或一个”,相当于 {0,1}。换句话说,它使得符号变得可选;
  • *:代表着“零个或多个”,相当于 {0,}。也就是说,这个字符可以多次出现或不出现;

案例:匹配开始或结束标签

      // 1.量词的使用
      const re = /a{3,5}/gi;
      const message = "fasfsdaafdfnaaaaaanifdos4aaaa35daas4aaaaa3aaaaaa";
      const result = message.match(re);
      console.log(result);
      /*
        2.常见的符号
          +: {1,}
          ?: {0,1}
          *: {0,}
      */

      // 3.案例: 字符串的html元素,匹配出来里面所有的标签
      const htmlElement = "<div><span>哈哈哈</span><h2>我是标题</h2></div>";
      const tagRe = /<\/?[a-z][a-z0-9]*>/gi;
      const result2 = htmlElement.match(tagRe);
      console.log(result2);

贪婪( Greedy)和惰性( lazy)模式

如果我们有这样一个需求:匹配下面字符串中所有使用《》包裹的内容

默认情况下的匹配规则是查找到匹配的内容后,会继续向后查找,一直找到最后一个匹配的内容

  • 这种匹配的方式,我们称之为贪婪模式(Greedy)

懒惰模式中的量词与贪婪模式中的是相反的。

  • 只要获取到对应的内容后,就不再继续向后匹配;
  • 我们可以在量词后面再加一个问号 ‘?’ 来启用它;
  • 所以匹配模式变为 *? 或 +?,甚至将 ‘?’ 变为 ??

      const message =
        "我最喜欢的两本书: 《了不起的盖茨比》和《小王子》、《人类群星闪耀时》";
      // 默认 .+ 采用贪婪模式
      const nameRe = /《.+》/gi;
      const result1 = message.match(nameRe);
      console.log(result1)
      
      // 使用惰性模式
      const nameRe2 = /《.+?》/gi;
      const result2 = message.match(nameRe2);
      console.log(result2);

捕获组(capturing group)

模式的一部分可以用括号括起来 (…),这称为“捕获组(capturing group)”。

这有两个作用:

  • 它允许将匹配的一部分作为结果数组中的单独项;
  • 它将括号视为一个整体;

方法 str.match(regexp),如果 regexp 没有 g 标志,将查找第一个匹配并将它作为一个数组返回:

  • 在索引 0 处:完全匹配。
  • 在索引 1 处:第一个括号的内容。
  • 在索引 2 处:第二个括号的内容。
  • …等等…

案例:匹配到HTML标签,并且获取其中的内容

      // 1.捕获组
      const message =
        "我最喜欢的两本书: 《了不起的盖茨比》和《小王子》、《人类群星闪耀时》";
      // 使用惰性模式
      const nameRe = /(《)(.+?)(》)/gi;
      const iterator = message.matchAll(nameRe);
      for (const item of iterator) {
        console.log(item[2]);
      }

      // 2.将捕获组作为整体
      const info = "degbabchfidshfiabcabchuishifdsabcfhdsuiabcabcabc";
      const abcRe = /(abc){2,}/gi;
      console.log(info.match(abcRe));

捕获组的补充

命名组:

  • 用数字记录组很困难。
  • 对于更复杂的模式,计算括号很不方便。我们有一个更好的选择:给括号起个名字。
  • 这是通过在开始括号之后立即放置 ? 来完成的。

非捕获组:

  • 有时我们需要括号才能正确应用量词,但我们不希望它们的内容出现在结果中。
  • 可以通过在开头添加 ?: 来排除组。

or是正则表达式中的一个术语,实际上是一个简单的“或”。

  • 在正则表达式中,它用竖线 | 表示;
  • 通常会和捕获组一起来使用,在其中表示多个值;

案例练习 – 歌词解析

歌词解析:http://123.207.32.32:9001/lyric?id=167876

function parseLyric(lyricString) {
  // 1.根据换行符切割字符串
  const lyricLineString = lyricString.split("\n");

  // 2.针对每一行歌词进行解析
  // 01:22.66
  const timeRe = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/i;
  const lyricInfos = [];
  for (const lineString of lyricLineString) {
    // 1.获取时间
    const result = lineString.match(timeRe);
    if (!result) continue;
    const minuteTime = result[1] * 60 * 1000;
    const secondTime = result[2] * 1000;
    const mSecondTime = result[3].length === 3 ? result[3] * 1 : result[3] * 10;
    const time = minuteTime + secondTime + mSecondTime;

    // 2.获取内容
    const content = lineString.replace(timeRe, "").trim();

    // 3.将对象放在数组中
    lyricInfos.push({ time, content });
  }
  return lyricInfos;
}
    <script src="./js/parse_lyric.js"></script>
    <script>
      /*
        currentTime:2000ms
        [00:00.000] 作词: 许嵩 => {time:0,content:"作词: 许嵩"}
        [00:01.000] 作曲: 许嵩 => {time:1000,content:"作曲: 许嵩"}
        [00:02.000] 编曲: 许嵩 => {time:0,}
        [00:22.240] 天空好像下雨
      */
      const lyricString =

        "[00:00.000] 作词 : 许嵩\n[00:01.000] 作曲 : 许嵩\n[00:02.000] 编曲 : 许嵩\n[00:22.240]天空好想下雨\n[00:24.380]我好想住你隔壁\n[00:26.810]傻站在你家楼下\n[00:29.500]抬起头数乌云\n[00:31.160]如果场景里出现一架钢琴\n[00:33.640]我会唱歌给你听\n[00:35.900]哪怕好多盆水往下淋\n[00:41.060]夏天快要过去\n[00:43.340]请你少买冰淇淋\n[00:45.680]天凉就别穿短裙\n[00:47.830]别再那么淘气\n[00:50.060]如果有时不那么开心\n[00:52.470]我愿意将格洛米借给你\n[00:55.020]你其实明白我心意\n[00:58.290]为你唱这首歌没有什么风格\n[01:02.976]它仅仅代表着我想给你快乐\n[01:07.840]为你解冻冰河为你做一只扑火的飞蛾\n[01:12.998]没有什么事情是不值得\n[01:17.489]为你唱这首歌没有什么风格\n[01:21.998]它仅仅代表着我希望你快乐\n[01:26.688]为你辗转反侧为你放弃世界有何不可\n[01:32.328]夏末秋凉里带一点温热有换季的颜色\n[01:41.040]\n[01:57.908]天空好想下雨\n[01:59.378]我好想住你隔壁\n[02:02.296]傻站在你家楼下\n[02:03.846]抬起头数乌云\n[02:06.183]如果场景里出现一架钢琴\n[02:08.875]我会唱歌给你听\n[02:10.974]哪怕好多盆水往下淋\n[02:15.325]夏天快要过去\n[02:18.345]请你少买冰淇淋\n[02:21.484]天凉就别穿短裙\n[02:22.914]别再那么淘气\n[02:25.185]如果有时不那么开心\n[02:27.625]我愿意将格洛米借给你\n[02:30.015]你其实明白我心意\n[02:33.327]为你唱这首歌没有什么风格\n[02:37.976]它仅仅代表着我想给你快乐\n[02:42.835]为你解冻冰河为你做一只扑火的飞蛾\n[02:48.406]没有什么事情是不值得\n[02:52.416]为你唱这首歌没有什么风格\n[02:57.077]它仅仅代表着我希望你快乐\n[03:01.993]为你辗转反侧为你放弃世界有何不可\n[03:07.494]夏末秋凉里带一点温热\n[03:11.536]\n[03:20.924]为你解冻冰河为你做一只扑火的飞蛾\n[03:26.615]没有什么事情是不值得\n[03:30.525]为你唱这首歌没有什么风格\n[03:35.196]它仅仅代表着我希望你快乐\n[03:39.946]为你辗转反侧为你放弃世界有何不可\n[03:45.644]夏末秋凉里带一点温热有换季的颜色\n";
      const result = parseLyric(lyricString);
      console.log(result);
    </script>

案例练习 – 时间格式化

时间格式化:从服务器拿到时间戳,转成想要的时间格式

更多正则查询:https://c.runoob.com/front-end/854/

    <h2 class="time"></h2>
    <script>
      // timestamp:1674316800000
      // yyyy/MM/dd hh:mm:ss
      // yyyy*MM*dd hh-mm-ss
      function formatTime(timestamp, fmtString) {
        // 1.将时间戳转成Date
        const date = new Date(timestamp);
        /* // 2.获取到值
        const year = date.getFullYear();
        const month = date.getMonth() + 1;
        const day = date.getDate();
        const hour = date.getHours();
        const minute = date.getMinutes();
        const second = date.getSeconds();
        
        // 3.创建正则
        const yearRe = /y+/;
        const monthRe = /M+/; */

        // 2.正则和值匹配起来
        const dateO = {
          "y+": date.getFullYear(),
          "M+": date.getMonth() + 1,
          "d+": date.getDate(),
          "h+": date.getHours(),
          "m+": date.getMinutes(),
          "s+": date.getSeconds(),
        };

        // 3.for循环进行替换
        for (const key in dateO) {
          const keyRe = new RegExp(key);
          console.log(keyRe,key);
          if (keyRe.test(fmtString)) {
            const value = (dateO[key] + "").padStart(2, 0);
            console.log(fmtString)
            fmtString = fmtString.replace(keyRe, value);
          }
        }
        return fmtString;
      }
      
      // 某一个商品上架时间,活动的结束时间
      const timeEl = document.querySelector(".time");
      const productJSON = {
        name: "iPhone",
        newPrice: 4999,
        oldPrice: 5999,
        endTime: 1674316800000,
      };

      timeEl.textContent = formatTime(
        productJSON.endTime,
        "yyyy/MM/dd hh:mm:ss"
      );

Q.E.D.