Skip to content

字符串扩展

字符 unicode

字符串遍历接口

ES6 为字符串添加了遍历器接口,使得字符串可以被for...of循环遍历。

js
for (let codePoint of 'foo') {
  console.log(codePoint)
}
// "f"
// "o"
// "o"

for...of 优势:识别 0xFFFF码点,传统for无法识别

直接输入 U+2028 和 U+2029

JSON.stringify() 的改造

JSON.stringify()的问题在于,它可能返回0xD8000xDFFF之间的单个码点。

ES2019 改变了JSON.stringify()的行为。如果遇到0xD8000xDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串

js
// Non-BMP characters still serialize to surrogate pairs.
JSON.stringify('𝌆')
// → '"𝌆"'
JSON.stringify('\uD834\uDF06')
// → '"𝌆"'

// Unpaired surrogate code units will serialize to escape sequences.
JSON.stringify('\uDF06\uD834')
// → '"\\udf06\\ud834"'
JSON.stringify('\uDEAD')
// → '"\\udead"'

字符串模板

单双引号字符串:均不解析变量,需要使用 + 号将变量拼接在字符串中

字符串模板(模板字面量):允许使用反引号**``**来创建字符串

  1. 空格换行保留

  2. 变量写在 ${}中,非字符串,如对象,调用toString()

  3. 可用 Js 表达式,能调用函数

js
function fn() {
  return 'Hello World'
}

;`foo ${fn()} bar`
// foo Hello World bar
  1. 支持嵌套
js
const tmpl = (addrs) => `
  <table>
  ${addrs
    .map(
      (addr) => `
    <tr><td>${addr.first}</td></tr>
    <tr><td>${addr.last}</td></tr>
  `
    )
    .join('')}
  </table>
`
  1. 引用本身,写成函数
js
let func = (name) => `Hello ${name}!`
func('Jack') // "Hello Jack!" 引用本身

模板编译

js
let template = `
<ul>
  <% for(let i=0; i < data.supplies.length; i++) { %>
    <li><%= data.supplies[i] %></li>
  <% } %>
</ul>
`

将其转换为 javascript 表达式

js
echo('<ul>')
for (let i = 0; i < data.supplies.length; i++) {
  echo('<li>')
  echo(data.supplies[i])
  echo('</li>')
}
echo('</ul>')

使用非贪婪匹配的原因:保证 <% 遇见下一个 %>就匹配。

js
function compile(template) {
  const evalExpr = /<%=(.+?)%>/g
  const expr = /<%([\s\S]+?)%>/g // 非贪婪匹配 +?

  template = template.replace(evalExpr, '`); \n  echo( $1 ); \n  echo(`').replace(expr, '`); \n $1 \n  echo(`')

  template = 'echo(`' + template + '`);'

  let script = `(function parse(data){
    let output = "";

    function echo(html){
      output += html;
    }

    ${template}

    return output;
  })`

  return script
}

标签模板

标签模板:函数调用特殊形式

模板字符串前面有一个标识名tag,它是一个函数。整个表达式的返回值,就是tag函数处理模板字符串后的返回值

js
let a = 5
let b = 10

tag`Hello ${a + b} world ${a * b}`

// 等同于
tag(['Hello ', ' world ', ''], 15, 50)

参数一:['Hello ', ' world ', '']

参数二:15

参数三:50

js
// 第一个参数为数组,模板字符串中没有被变量替换的部分
// 其他参数为替换变量
function tag(stringArr, value1, value2) {
  // ...
  // stringArr - ['hello ',' world ','']
  // value1 - 15
  // value2 - 50
}
// 等同于
function tag(stringArr, ...values) {
  // ...
}

重要应用

  • 过滤 HTML,防止用户输入恶意内容
  • 多语言转换

模板字符串限制

字符串新增方法

String.fromCodePoint()

ES5 - String.fromCharCode():从 Unicode 码点返回字符串。

问题:不能识别大于 0xFFFF 的字符,0x20BBF 舍弃高位变为 0x0BBF

String.fromCodePoint():与codePointAt()相反,多个参数,合并为一个字符串

fromCodePoint定义在 String 对象上,codePointAt定义在字符串对象上

String.raw()

ES6 为原生 String 对象 提供 raw()方法。

作用:返回斜杠都被转义的字符串。将变量替换,对斜杠转义。

js
String.raw`Hi\n${2 + 3}!`
// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"

// `foo${1 + 2}bar`
// 等同于
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"

实列方法 - charCodeAt()

ES5 - charAt():从一个字符串中返回指定的字符

ES5 - charCodeAt():方法返回 065535 之间的整数,表示给定索引处的 UTF-16 代码单元

JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2个字节。

问题:无法解决 4 个字节字符(大于0xFFFF),JavaScript 会认为它们是两个字符。

codePointAt():处理 4 个字节存储的字符

𠮷 - 码点0x20BB7,UTF-16 编码为0xD842 0xDFB7

js
let s = '𠮷a'

s.codePointAt(0) // 134071
s.codePointAt(1) // 57271

s.codePointAt(2) // 97

测试一个字符由两个字节还是由四个字节组成

js
function is32Bit(c) {
  return c.codePointAt(0) > 0xffff
}

is32Bit('𠮷') // true
is32Bit('a') // false

Unicode 、UTF-8、UTF-8 : 参考末尾文章

Unicode:一个符号集, 它只规定了每个符号的二进制值。将世界各种语言的每个字符定义一个唯一的编码

UTF-8:一种变长字符编码,将码点编码为 1 至 4 个字节,字节数取决于码点数值中有效二进制位的数量

UTF-16:UTF-16 也是一种变长字符编码, 这种编码方式比较特殊, 它将字符编码成 2 字节 或者 4 字节

实例方法 - normalize()

normalize():将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化

实例方法 - includes(),startsWith(),endsWith()

es5 - indexOf():查找一个字符串是否在里一个字符串中。

问题:不够直观,返回的 number

includes(string, number)找到 string,number 为起始位置

startsWith(string, number)以 string 开头的字符串,number 为起始位置

endsWith(string, number)以 string 结尾的字符串,number 为前 n 个字符

js
let s = 'Hello world!'

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

均返回 boolean

实例方法 - repeat()

repeat():返回新字符串,原字符串重复 n 次

  • n 为小数,取整
  • n 为负数infinity报错
  • n 为-1~0NaN ,均为 0,返回空值
js
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
'na'.repeat(NaN) // ""
js
// 1.小数点取整
'na'.repeat(-0.9) // ""
// 2.负数和 Infinity 报错
'na'.repeat(Infinity) // RangeError
'na'.repeat(-1) // RangeError
// 3.字符串转为数字
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"

实例方法 - padStart(),padEnd()

padStart():头部补全

  • 参数 1:补全最大长度
  • 参数 2:补全字符串(默认空格)

补全规则

  1. 大于等于长度,返回原串

  2. 补全长度 + 原串 > 最大长度,截取补全字符串

js
'x'.padStart(5, 'ab') // 'ababx'
'x'.padEnd(5, 'ab') // 'xabab'

'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'

'abc'.padStart(10, '0123456789') // '0123456abc'

padEnd():尾部补全,同上

用途

  1. 数值补 0
js
'123456'.padStart(10, '0') // "0000123456"
  1. 字符串格式提示
js
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

实例方法 - trimStart(),trimEnd()

trimStart():消除头部空格

trimEnd():消除尾部空格

返回新字符串

实例方法 - matchAll()

ES5 - regexp.exec():获取匹配项信息

获取所有匹配项,需要循环

js
const regexp = RegExp('foo[a-z]*', 'g')
const str = 'table football, foosball'
let match

while ((match = regexp.exec(str)) !== null) {
  console.log(`Found ${match[0]} start=${match.index} end=${regexp.lastIndex}.`)
  // expected output: "Found football start=6 end=14."
  // expected output: "Found foosball start=16 end=24."
}

ES2020 - matchAll()返回一个正则表达式在当前字符串的所有匹配

js
const regexp = RegExp('foo[a-z]*', 'g')
const str = 'table football, foosball'
const matches = str.matchAll(regexp)

for (const match of matches) {
  console.log(`Found ${match[0]} start=${match.index} end=${match.index + match[0].length}.`)
}
// expected output: "Found football start=6 end=14."
// expected output: "Found foosball start=16 end=24."

没有 /g 标志,matchAll 会抛出异常

实例方法 - replaceAll()

ES5 - replace():将匹配的字符串,用指定字符串替换

js
'aabbcc'.replace('b', '_') // 替换第一个匹配
'aabbcc'.replace(/b/g, '_') // 替换所有匹配

replaceAll( searchValue, replacement):所有匹配的字符串替换

searchValue - 字符串 or 全局正则表达式(带g修饰符,不带报错)

replacement - 字符串 or 函数,标识替换文本。

  • 字符串
    • $&:匹配的子字符串
    • $`:匹配结果前面的文本。
    • $':匹配结果后面的文本。
    • $n:匹配成功的第n组内容(n 从 1开始),前提是,第一个参数必须是正则表达式。
    • $$:指代美元符号$
js
// $& 表示匹配的字符串,即`b`本身
// 所以返回结果与原字符串一致
'abbc'.replaceAll('b', '$&')
// 'abbc'

// $` 表示匹配结果之前的字符串
// 对于第一个`b`,$` 指代`a`
// 对于第二个`b`,$` 指代`ab`
'abbc'.replaceAll('b', '$`')
// 'aaabc'

// $' 表示匹配结果之后的字符串
// 对于第一个`b`,$' 指代`bc`
// 对于第二个`b`,$' 指代`c`
'abbc'.replaceAll('b', `$'`)
// 'abccc'

// $1 表示正则表达式的第一个组匹配,指代`ab`
// $2 表示正则表达式的第二个组匹配,指代`bc`
'abbc'.replaceAll(/(ab)(bc)/g, '$2$1')
// 'bcab'

// $$ 指代 $
'abc'.replaceAll('b', '$$')
// 'a$c'
  • 函数
js
const str = '123abc456'
const regex = /(\d+)([a-z]+)(\d+)/g
/*
params #1 - 匹配内容
params #2 - 捕捉到是组匹配(有多少个组匹配,就有多少个对应的参数)
...
params #n-1 - 捕获内容在字符串位置
parmas #n - 原字符串
*/
function replacer(match, p1, p2, p3, offset, string) {
  return [p1, p2, p3].join(' - ')
}
str.replaceAll(regex, replacer)
// 123 - abc - 456

实例方法 - at()

at():接受一个整数作为参数,返回参数指定位置的字符。(支持负索引)

js
const str = 'hello'
str.at(1) // "e"
str.at(-1) // "o"
str.at(1111) // undefined

超出范围,返回 undefined

相关链接

[-] 字符串扩展

[-] 字符串新增方法

[-] Unicode、UTF-8、UTF-16