十、异常(错误)处理方案和正则表达式RegExp的深入学习

2022/8/11 ES6异常处理throw语句try/catch语句正则表达式RegExp类

# 1、异常处理方案(throw关键字和try/catch语句)

# 1.1.错误处理方案

  • 开发中我们会封装一些工具函数,封装之后给别人使用:

    • 在其他人使用的过程中,可能会传递一些参数;
    • 对于函数来说,需要对这些参数进行验证,否则可能得到的是我们不想要的结果;
  • 很多时候我们可能验证到不是希望得到的参数时,就会直接return:

    • 但是return存在很大的弊端:调用者不知道是因为函数内部没有正常执行,还是执行结果就是一个undefined;
    • 事实上,正确的做法应该是如果没有通过某些验证,那么应该让外界知道函数内部报错了;
//1.需求:封装一个仅可以进行数字相加的函数
function sum(n,m) {
  if(typeof(n) !== 'number' || typeof(m) !== 'number') {
    return
  }
  return n + m
}

//另外一个调用者进行调用我们封装好的工具函数 -> 发现返回一个undefined就会很迷;所以我们要给它抛出一个错误,让他改正调用时需要传入什么类型参数即可
console.log(sum({name:'kobe'},['bba']))//undefined;
1
2
3
4
5
6
7
8
9
10
  • 如何可以让一个函数告知外界自己内部出现了错误呢?
    • 通过throw关键字,抛出一个异常;
//1.需求:封装一个仅可以进行数字相加的函数
function sum(n,m) {
  if(typeof(n) !== 'number' || typeof(m) !== 'number') {
    throw new Error('参数n和m必须是一个number类型')
  }
  return n + m
}

//另外一个调用者进行调用我们封装好的工具函数
// console.log(sum({name:'kobe'},['bba']))//报错:参数n和m必须是一个number类型  (调用者发现类型传错了,进行修改代码;)
console.log(sum(20,500))//520

//通过上面我们就知道throw的具体应用场景了,平时我们业务开发中会写,但是很少;
//一般像一些框架,库 等应用的较多,为了让使用者更好的使用框架跟库,所以使用者操作错误,抛出错误定位问题是必不可少的,不然使用者久久定位不到问题所在,该框架使用的人肯定也会越来越少的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • throw语句:

    • throw语句用于抛出一个用户自定义的异常;
    • 当遇到throw语句时,当前的函数执行会被停止(throw后面的语句不会执行);
  • 如果我们执行代码,就会报错,拿到错误信息的时候我们可以及时的去修正代码。

# 1.2.throw关键字

  • throw表达式就是在throw后面可以跟上一个表达式来表示具体的异常信息:throw expression;

  • throw关键字可以跟上哪些类型呢?

    • 基本数据类型:比如number、string、Boolean
    • 对象类型:对象类型可以包含更多的信息
  • 但是每次写这么长的对象又有点麻烦,所以我们可以创建一个类:

class ykError{
  constructor(errCode,errMessage) {
    this.errCode = errCode,
    this.errMessage = errMessage
  }
}


function sum(n,m) {
  if((typeof(n) !== 'number' || typeof(m) !== 'number')) {
    throw new ykError(-1002,'参数n和m必须是一个number类型')
  }
  return n + m
}


console.log(sum('2',33))//报错:ykError { errCode: -1002, errMessage: '参数n和m必须是一个number类型' }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 1.3.Error类型

  • 事实上,JavaScript已经给我们提供了一个Error类,我们可以直接创建这个类的对象:
function sum(n,m) {
  if((typeof(n) !== 'number' || typeof(m) !== 'number')) {
    throw new Error('参数n和m必须是一个number类型')
    //throw new TypeError('参数n和m必须是一个number类型')
  }
  return n + m
}


console.log(sum('2',33))//报错:

console.log('script end')//上面抛出错误后,该行没有打印,即没有执行
1
2
3
4
5
6
7
8
9
10
11
12
  • Error包含三个属性:

    • messsage:创建Error对象时传入的message;
    • name:Error的名称,通常和类的名称一致;
    • stack:整个Error的错误信息(内部已经封装好了),包括函数的调用栈,当我们直接打印Error对象时,打印的就是stack(像多少行报错等信息);
  • Error有一些自己的子类:

    • RangeError:下标值越界时使用的错误类型;
    • SyntaxError:解析语法错误时使用的错误类型;
    • TypeError:出现类型错误时,使用的错误类型;

# 1.4.异常的处理

  • 我们会发现在之前的代码中,一个函数抛出了异常,调用它的时候程序会被强制终止:

    • 这是因为如果我们在调用一个函数时,这个函数抛出了异常,但是我们并没有对这个异常进行处理,那么这个异常会继续传递到上一个函数调用中;
    • 而如果到了最顶层(全局)的代码中依然没有对这个异常的处理代码,这个时候就会报错并且终止程序的运行;
  • 我们先来看一下这段代码的异常传递过程:

    • foo函数在被执行时会抛出异常,也就是我们的bar函数会拿到这个异常;
    • 但是bar函数并没有对这个异常进行处理,那么这个异常就会被继续传递到调用bar函数的函数,也就是test函数;
    • 但是test函数依然没有处理,就会继续传递到我们的全局代码逻辑中;
    • 依然没有被处理,这个时候程序会终止执行,并抛出错误,后续代码都不会再执行了;
function foo() {
  throw 'error message'//抛出错误,外部没有捕获异常,直接浏览器抛出错误,直接终止执行
}
function bar(){
  foo()
}
function test() {
  bar()
}
test()
console.log('test后续代码')
1
2
3
4
5
6
7
8
9
10
11

# 1.5.异常的捕获(try/catch语句)

  • 但是很多情况下当出现异常时,我们并不希望程序直接推出,而是希望可以正确的处理异常:
    • 这个时候我们就可以使用try catch
function foo() {
  throw 'error message'//抛出错误,被bar函数内部捕获异常;代码继续正常执行
  console.log('foo')//不会执行
}
function bar(){
  try {
    foo()
  }catch(err) {
    console.log(err)//error message
  }
}
function test() {
  bar()
}
test()
console.log('test后续代码')//test后续代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 在ES10(ES2019)中,catch后面绑定的error可以省略
  • 当然,如果有一些必须要执行的代码,我们可以使用finally来执行:
    • finally表示最终一定会被执行的代码结构;
    • 注意:如果try和finally中都有返回值,那么会使用finally当中的返回值;
function foo() {
  throw 'error message'//抛出错误,被bar函数内部捕获异常;代码继续正常执行 (注意抛出异常的throw语句后面的不会进行执行;于return类似)
  console.log('foo')//不会执行
}
function bar() {
  foo()
  console.log('bar')//不会执行
}
function test() {
  bar()
  console.log('test')//不会执行
}

//上面代码相当于下面注释内容的代码
// function test() {
//   function bar() {
//     function foo() {
//       throw 'error message'
//       console.log('foo')
//     }
//     foo()
//     console.log('bar')
//   }
//   bar()
//   console.log(test)
// }

try {
  test()
  console.log('全局代码')//不会执行
} catch (err) {
  console.log(err)//error message
} finally {
  console.log('finally内部内容打印')//finally内部内容打印
}

console.log('test后续代码')//test后续代码

//注意:如果try和finally中都有返回值,那么会使用finally当中的返回值;
function fn() {
  try {
    return 'try'
  }catch{

  }finally{
    return 'finally'
  }
}
console.log(fn())//finally
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 2、正则表达式RegExp的学习

# 2.1.什么是正则表达式?

  • 我们先来看一下维基百科对正则表达式的解释:
    • 正则表达式(英语:Regular Expression,常简写为regex、regexp或RE),又称正则表示式、正则表示法、规则表达式、常规表示法,是计算机科学的一个概念;
    • 正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。
    • 许多程序设计语言都支持利用正则表达式进行字符串操作。
  • 简单概况:正则表达式是一种字符串匹配利器,可以帮助我们搜索、获取、替代字符串;
  • 在JavaScript中,正则表达式使用RegExp类来创建,也有对应的字面量的方式:
    • 正则表达式主要由两部分组成:模式(patterns)和修饰符(flags)
const re1 = new RegExp('hello','i')  //通过RegExp类来创建
const re2 = /hello/i  //字面量创建正则
1
2

# 2.2.正则表达式的使用方法

  • 有了正则表达式我们要如何使用它呢?
    • JavaScript中的正则表达式被用于 RegExp 的 exec 和 test 方法;
    • 也包括 String 的 match、matchAll、replace、search 和 split 方法;
方法 描述
exec (opens new window) 一个在字符串中执行查找匹配的 RegExp 方法,它返回一个数组(未匹配到则返回 null)。
test (opens new window) 一个在字符串中测试是否匹配的 RegExp 方法,它返回 true 或 false。
match (opens new window) 一个在字符串中执行查找匹配的 String 方法,它返回一个数组,在未匹配到时会返回 null。
matchAll (opens new window) 一个在字符串中执行查找所有匹配的 String 方法,它返回一个迭代器(iterator)。
search (opens new window) 一个在字符串中测试匹配的 String 方法,它返回匹配到的位置索引,或者在失败时返回-1。
replace (opens new window) 一个在字符串中执行查找匹配的 String 方法,并且使用替换字符串替换掉匹配到的子字符串。
split (opens new window) 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的 String 方法。
const re = /aa/ig
const str = 'kobeaajamesaarose'
const str1 = 'kobebbjamesbbrose'

//1.正则实例方法:test
console.log(re.test(str),re.test(str1))//true  false

//2.正则实例方法:exec
console.log(re.exec(str))//[ 'aa', index: 4, input: 'kobeaajamesaarose', groups: undefined ]
console.log(re.exec(str1))//null

//3.字符串实例方法split
console.log(str.split(re))//[ 'kobe', 'james', 'rose' ]

//4.字符串实例方法replace
console.log(str.replace(/aa/i,'bb'))//kobebbjamesaarose
console.log(str.replace(/aa/ig,'bb'))//kobebbjamesbbrose

//5.字符串实例方法match
console.log(str.match(re))//[ 'aa', 'aa' ]
console.log(str1.match(re))//null

//6.字符串实例方法matchAll
const iterator = str.matchAll(re)
console.log(iterator)//Object [RegExp String Iterator] {}
console.log(iterator.next())//{value: [ 'aa', index: 4, input: 'kobeaajamesaarose', groups: undefined ],done: false}
console.log(iterator.next())//{value: [ 'aa', index: 11, input: 'kobeaajamesaarose', groups: undefined ],done: false}
console.log(iterator.next())//{ value: undefined, done: true }

console.log(str1.matchAll(re).next())//{ value: undefined, done: true }

//7.字符串实例方法search
console.log(str.search(re))//4
console.log(str1.search(re))//-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 2.3.修饰符flag的使用

  • 常见的修饰符:/模式/修饰符 => /aa/ig
flag 含义
g 全部的,给我匹配全部的(全局匹配)
i 忽略大小写
m 多行匹配
  • 需求:
    • 获取一个字符串中所有的abc;
    • 将一个字符串中的所有abc换成大写;
let message = "Hello ABC, abc, Abc, AAaBC"

// 1. 获取一个字符串中所有的abc;
console.log(message.match(/abc/ig))//[ 'ABC', 'abc', 'Abc', 'aBC' ]

// 2. 将一个字符串中的所有abc换成大写;
console.log(message.replace(/abc/ig,'ABC'))//Hello ABC, ABC, ABC, AAABC
1
2
3
4
5
6
7

# 2.5.规则 – 字符类 - 反向类(Character classes)

  • 字符类(Character classes) 是一个特殊的符号,匹配特定集中的任何符号。
字符 含义
\d(“d” 来自 “digit”) 数字:从 0 到 9 的字符。
\s(“s” 来自 “space”) 空格符号:包括空格,制表符 \t,换行符 \n 和其他少数稀有字符,例如 \v,\f 和 \r。
\w(“w” 来自 “word”) “单字”字符:拉丁字母或数字或下划线 _。
.(点) 点 . 是一种特殊字符类,它与 “除换行符之外的任何字符” 匹配
  • 反向类(Inverse classes)
    • \D 非数字:除 \d 以外的任何字符,例如字母。
    • \S 非空格符号:除 \s 以外的任何字符,例如字母。
    • \W 非单字字符:除 \w 以外的任何字符,例如非拉丁字母或空格。
const message = "CSS2.5 is good"
const pattern = /CSS\d(\.\d)?/i
console.log(message.match(pattern))//['CSS2.5','.5',index: 0,input: 'CSS2.5 is good',groups: undefined]
1
2
3

# 2.6.规则 – 锚点(Anchors)

  • 符号 ^ 和符号 $ 在正则表达式中具有特殊的意义,它们被称为“锚点”。
    • 符号 ^ 匹配文本开头;
    • 符号 $ 匹配文本末尾;
const message = "My name is kobe"
if(/^my/ig.test(message)) {
  console.log('忽略大小写以my开头')
}
if(/kobe$/ig.test(message)) {
  console.log('忽略大小写以kobe结尾')
}

//一起使用的情况: ^kobenba$ 代表字符串一定要是kobenba
const str1 = 'kobe1nba'
const str2 = 'kobenba'
const pattern = new RegExp('^kobenba$','ig')
console.log(pattern.test(str1))//false
console.log(pattern.test(str2))//true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 词边界(Word boundary)
    • 词边界 \b 是一种检查,就像 ^ 和 $ 一样,它会检查字符串中的位置是否是词边界。
    • 词边界测试 \b 检查位置的一侧是否匹配 \w,而另一侧则不匹配 “\w”
    • 在字符串 Hello, Java! 中,以下位置对应于 \b:

vNGmin.png

const message = "Hello, Java!"
console.log(/\bHell\b/ig.test(message))//false
console.log(/\bHello\b/ig.test(message))//true
console.log(/\bJava\b/ig.test(message))//true
console.log(/\bJava!\b/ig.test(message))//false
1
2
3
4
5
  • 匹配下面字符串中的时间:
const message = "now time 22:42,number is 1234.343"
console.log(message.match(/\b\d\d:\d\d\b/g))//['22:42',index: 9,input: 'now time 22:42,number is 1234.343',groups: undefined]
1
2

# 2.7.规则 – 转义字符串

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

    • 只需要在它前面加个反斜杠\;
  • 常见的需要转义的字符:

    • []\^$.|?*+()

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

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

const fileName = ['abc.js', 'cba.java', 'nba.html', 'mba.js', 'aaa.jsx','ccdjs']
const newNames = file[ 'abc.js', 'mba.js', 'aaa.jsx' ]Name.filter(item => {
  return /\.jsx?$/ig.test(item)
})
console.log(newNames)//[ 'abc.js', 'mba.js', 'aaa.jsx' ]
1
2
3
4
5
  • 在webpack当中,匹配文件名时就是以这样的方式。

# 2.8.集合(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_] 相同;
  • 案例:匹配手机号码

const phones = ['14238394939','18984940293','13484934034','15179384950','1272109383']
const phonePettrens = phones.filter(item => /^1[356789]\d{9}$/g.test(item)) 
console.log(phonePettrens)//[ '18984940293', '13484934034', '15179384950' ]
1
2
3
  • 排除范围:除了普通的范围匹配,还有类似 [^…] 的“排除”范围匹配;\
//匹配两位数字,第一位数字是1或3 且 第二位数字不能为3 的字符串
const numbers = ['12','32','33','24','99','13','102']
const numberPettrens = numbers.filter(item => /^[13][^3]$/g.test(item))
console.log(numberPettrens)//[ '12', '32' ]
1
2
3
4

# 2.9.量词(Quantifiers)

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

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

    • 确切的位数:{5}
    • 某个范围的位数:{3,5}
  • 缩写:

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

const htmlElement = "<div><h1>标题</h1><p>哈哈哈</p><span>呵呵</span></div>"
console.log(htmlElement.match(/<\/?[a-z][a-z0-9]*>/ig))//['<div>', '<h1>', '</h1>', '<p>', '</p>', '<span>', '</span>', '</div>']
1
2

# 2.10.贪婪( Greedy)和惰性( lazy)模式

  • 如果我们有这样一个需求:匹配下面字符串中所有使用《》包裹的内容
const message = "我喜欢的两本书籍为:《Javascript高级程序设计》和《你不知道的Javascript》"
console.log(message.match(/《.+》/ig))//[ '《Javascript高级程序设计》和《你不知道的Javascript》' ]  -> 不是我们想要的结果:贪婪模式
1
2
  • 默认情况下的匹配规则是查找到匹配的内容后,会继续向后查找,一直找到最后一个匹配的内容
    • 这种匹配的方式,我们称之为贪婪模式(Greedy)
  • 懒惰模式中的量词与贪婪模式中的是相反的。
    • 只要获取到对应的内容后,就不再继续向后匹配;
    • 我们可以在量词后面再加一个问号 ‘?’ 来启用它;
    • 所以匹配模式变为 *? 或 +?,甚至将 '?' 变为 ??
const message = "我喜欢的两本书籍为:《Javascript高级程序设计》和《你不知道的Javascript》"
console.log(message.match(/《.+?》/ig))//[ '《Javascript高级程序设计》', '《你不知道的Javascript》' ]
1
2

# 2.11.捕获组(capturing group)

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

  • 这有两个作用:

    • 它允许将匹配的一部分作为结果数组中的单独项;
    • 它将括号视为一个整体
  • 方法 str.match(regexp),如果 regexp 没有 g 标志,将查找第一个匹配并将它作为一个数组返回:

    • 在索引 0 处:完全匹配。
    • 在索引 1 处:第一个括号的内容。
    • 在索引 2 处:第二个括号的内容。
    • …等等…
// regexp 没有 g 标志
const str = "<h1>title</h1>"
const result = str.match(/<(.+?)>/i)
console.log(result)//[ '<h1>', 'h1', index: 0, input: '<h1>title</h1>', groups: undefined ]

// regexp 有 g 标志
console.log(str.match(/<(.+?)>/ig))//[ '<h1>', '</h1>' ]
1
2
3
4
5
6
7
  • 案例:匹配到HTML标签,并且获取标签名
const str = "<h1>title</h1>"
const result = str.matchAll(/<(.+?)>/ig)//返回的是一个迭代器
for(const item of result) {
  console.log(item)//item[0]  item[1]
  //[ '<h1>', 'h1', index: 0, input: '<h1>title</h1>', groups: undefined ]
  //['</h1>','/h1',index: 9,input: '<h1>title</h1>',groups: undefined]
}
1
2
3
4
5
6
7
  • 补充
const message = "我喜欢的两本书籍为:《Javascript高级程序设计》和《你不知道的Javascript》"
const iterator = message.matchAll(/(《)(.*?)(》)/ig)
for(const item of iterator) {
  console.log(item)
}
1
2
3
4
5

# 2.12.捕获组的补充

  • 命名组

  • 用数字记录组很困难。

  • 对于更复杂的模式,计算括号很不方便。我们有一个更好的选择:给括号起个名字

  • 这是通过在开始括号之后立即放置 ?<name>来完成的。

const message = "我喜欢的两本书籍为:《Javascript高级程序设计》和《你不知道的Javascript》"
const iterator = message.matchAll(/(《)(?<kobe>.*?)(》)/ig)
for (const item of iterator) {
  console.log(item)
  /*item打印结果 (有两条打印结果,这里只展示一项,仅供参考)
  [ '《Javascript高级程序设计》',
    '《',
    'Javascript高级程序设计',
    '》',
    index: 10,
    input: '我喜欢的两本书籍为:《Javascript高级程序设计》和《你不知道的Javascript》',
    groups: [Object: null prototype] { kobe: 'Javascript高级程序设计' }
  ]
  Javascript高级程序设计
  */
  console.log(item.groups.kobe)//Javascript高级程序设计   //你不知道的Javascript
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 非捕获组
    • 有时我们需要括号才能正确应用量词,但我们不希望它们的内容出现在结果中。
    • 可以通过在开头添加 ?: 来排除组
const message = "我喜欢的两本书籍为:《Javascript高级程序设计》和《你不知道的Javascript》"
const iterator = message.matchAll(/(?:《)(?<kobe>.*?)(?:》)/ig)
for (const item of iterator) {
  console.log(item)
  /* item打印结果如下 (有两条打印结果,这里只展示一项,仅供参考)
  
  [ '《Javascript高级程序设计》',
    'Javascript高级程序设计',
    index: 10,
    input: '我喜欢的两本书籍为:《Javascript高级程序设计》和《你不知道的Javascript》',
    groups: [Object: null prototype] { kobe: 'Javascript高级程序设计' }
  ]
  Javascript高级程序设计
  */
  console.log(item.groups.kobe)//Javascript高级程序设计   //你不知道的Javascript
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • or是正则表达式中的一个术语,实际上是一个简单的“或”。
    • 在正则表达式中,它用竖线 | 表示;
    • 通常会和捕获组一起来使用,在其中表示多个值;
const message = 'abcabcsdkfjsdkcbadksfskfcbacbakjskfjkjkNbanbaNBadsf'

console.log(message.match(/(abc|cba|nba){2,}/ig))//[ 'abcabc', 'cbacba', 'NbanbaNBa' ]
1
2
3

# 2.13.案例练习 – 歌词解析

const data = { "sgc": false, "sfy": false, "qfy": false, "lrc": { "version": 30, "lyric": "[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" }, "klyric": { "version": 0, "lyric": "" }, "tlyric": { "version": 0, "lyric": "" }, "code": 200 }
const lyricString = data.lrc.lyric

const lyricLineStrings = lyricString.split("\n")
const lyricLines = []
const timePattern = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/
for (const lyric of lyricLineStrings) {
  const timeString = timePattern.exec(lyric)
  if (!timeString) continue
  const time1 = timeString[1] * 60 * 1000
  const time2 = timeString[2] * 1000
  const time3 = timeString[3].length === 3 ? timeString[3] * 1 : timeString[3] * 10
  console.log(time1, time2, time3)
  const time = time1 + time2 + time3
  const content = lyric.replace(timePattern, "").trim()
  lyricLines.push({ time, content })
}
console.log(lyricLines)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 2.14.案例练习 – 时间格式化

  • 时间格式化:从服务器拿到时间戳,转成想要的时间格式
function formatDate(time, formatString) {
  const date = new Date(time)
  // 定义正则和值之间的关系
  const obj = {
    "y+": date.getFullYear(),
    "M+": date.getMonth() + 1,
    "d+": date.getDate(),
    "h+": date.getHours(),
    "m+": date.getMinutes(),
    "s+": date.getSeconds()
  }
  // 替换
  for (const key in obj) {
    if (new RegExp(`(${key})`).test(formatString)) {
      const value = (obj[key] + "").padStart(2, "0")
      formatString = formatString.replace(RegExp.$1, value)
    }
  }
  return formatString
}

const result = formatDate(1453452322323, "yyyy/MM/dd hh:mm:ss")
console.log(result)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
最后更新时间: 2022/08/16, 15:42:33
彩虹
周杰伦