字符串是编程中最重要的数据类型之一,ES6加强了字符串与正则表达式。
在ES6之前,JS的字符串以16位字符编码(UCS-2)为基础。每个16位序列都是一个码元(code unit),用于表过一个字符。字符串所有的属性与方法(length与charAt())都是基于16位码元。当然,16位曾经足以容纳任何字符,然后由于Unicode引入了扩展字符集,这就不再够用了。
UTF-16是变长的字符编码方式,有16位与32位两种情况。JS原先使用的则是固定16位(双字节)的字符编码方式,即UCS-2
16位序列(两个字节)一个码元表示一个字符
Unicode的明确目标是给世界上所有的字符提供全局唯一标识符,而16位的字符长度限制已不能满足这种需求。这些全球唯一标识符被称为代码点(code points),是从0开始的简单数字。代码点是如你想象的字符代码那样,用一个数字来代表一个字符。字符编码要求将代码点转换为内部一致的码元,而对UTF-16来说,代码点可以由多个码元组成。
在UTF-16中的第一个2的16次方代码点示单个16位码元,这个范围被称为多语言基本平面(BMP)。任何超出该范围的代码点都不能用单个16位码元表示,而是会落在扩展平面。UTF-16引入了代理对来解决这个问题,允许使用两个16位码元来表示单个代码点。这意味着字符串内的任意单个字符都可以用一个码元或两个码元来表示,前者对应基本平面字符,后都对应扩展平面字符。
在ES5中,所有字符串操作都基于16位码元,这表示在处理包含代理对UTF-16字符时会出现预期外的结果,就像这个例子:
const text = ""
console.log(text.length) // 2
console.log(/^.$/.test(text)) // false
console.log(text.charAt(0)) // ""
console.log(text.charAt(1)) // ""
console.log(text.charCodeAt(0)) // 55362
console.log(text.charCodeAt(1)) // 57271
这个Unicode字符""使用了代理对,因此,上面的JS字符串操作会将该字符串当作两个16位字符串来对待,这意味着:
codePointAt()方法:
ES6为全面支持UTF-16而新增的方法之一是codePointAt(),它可以在给定字符串中按位置提取Unicode代码点。该方法接受的是码元位置而非字符位置,并返回一个整数值。
const text = "a"
console.log(text.charCodeAt(0)) // 97
console.log(text.charCodeAt(1)) // NaN
console.log(text.charCodeAt(2)) // NaN
console.log(text.codePointAt(0)) // 97
console.log(text.codePointAt(1)) // undefined
console.log(text.codePointAt(2)) // undefined
判断字符包含了一个还是两个码元,对该字符调用codePointAt()就是最简单的方法。
function is32Bit(c){
return c.codePointAt(0) > 0xFFFF
}
console.log(is32Bit('嘎'))
console.log(is32Bit("a"))
console.log('嘎'.codePointAt(0))
console.log(String.fromCharCode(22030))
String.fromCodePoint() 方法
当 ECMAScript 提供了某种方法时,它一般也会给出方法来处理相反的操作。你可以使用codePointAt() 来提取字符串内中某个字符的代码点,也可以借助 String.fromCodePoint()用给定的代码点来产生包含单个字符的字符串。例如:
console.log(String.fromCharCode(22030)) // 嘎
normalize() 方法
Unicode 另一个有趣之处是,不同的字符在排序或其它一些比较操作中可能会被认为是相同的。有两种方式可以定义这种关联性:第一种是规范相等性( canonical equivalence ),意味着两个代码点序列在所有方面都被认为是可互换的。例如,两个字符的组合可以按规范等同于另一个字符。第二种关联性是兼容性( compatibility ),两个兼容的代码点序列看起来有差别,但在特定条件下可互换使用。
由于这些关联性,文本内容在根本上相同的两个字符串就可以包含不同的代码点序列。例如,字符 “æ” 与双字符的字符串 “ae” 或许能互换使用,但它们并不严格相等,除非使用某种手段来标准化。
ES6 给字符串提供了 normalize() 方法,以支持 Unicode 标准形式。
该方法接受单个可选的
字符串参数,用于指示需要使用下列哪种 Unicode 标准形式:
Normalization Form Canonical Composition ( “NFC” ),这是默认值;
Normalization Form Canonical Decomposition ( “NFD” );
Normalization Form Compatibility Composition ( “NFKC” );
Normalization Form Compatibility Decomposition ( “NFKD” )。
只需记住,当比较字符串时,它们必须被标准化为同一种形式。例如:
此代码将 values 数组中的字符串转换为一种标准形式,以便让转换后的数组可以被正确排序。你也可以在比较过程中调用 normalize() 来对原始数组进行排序。如下所示:
values.sort(function(first, second){
let firstNormalized = first.normalize()
let secondNormalized = second.normalize()
if(firstNormalized<secondNormalized){
return -1
}else if (firstNormalized===secondNormalized){
return 0
}else{
return 1
}
})
新方法并不是 ES6 为 Unicode 字符串提供的唯一改进,它还新增了两个有用的语法要素。
正则表达式 u 标志
你可以使用正则表达式来完成字符串的很多通用操作。但要记住,正则表达式假定单个字符使用一个 16 位的码元来表示。为了解决这个问题, ES6 为正则表达式定义了用于处理Unicode 的 u 标志。
当一个正则表达式设置了 u 标志时,它的工作模式将切换到针对字符,而不是针对码元。这意味着正则表达式将不会被字符串中的代理对所混淆,而是会如预期那样工作。例如,研究以下代码:
var text = "a"
console.log(text.length)
console.log(/^.$/.test(text))
console.log(/^.$/u.test(text))
经过实验,加不加u标志,在新版的浏览器中都会很好的应用。
indexOf():
indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
如果没有找到匹配的字符串则返回 -1。
注意: indexOf() 方法区分大小写。
提示: 同样你可以查看类似方法 lastIndexOf() 。
参数 描述
第一个参数:searchvalue 必需。规定需检索的字符串值。
第二个参数:start 可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 string Object.length - 1。如省略该参数,则将从字符串的首字符开始检索。
const str = "hello,world"
const n = str.indexOf('l',4)
console.log(n)
const m = str.indexOf('wll')
console.log(m === -1)
includes() 方法,在给定文本存在于字符串中的任意位置时会返回 true ,否则返回false
startsWith() 方法,在给定文本出现在字符串起始处时返回 true ,否则返回 false
endsWith() 方法,在给定文本出现在字符串结尾处时返回 true ,否则返回 false
每个方法都接受两个参数:需要搜索的文本,以及可选的搜索起始位置索引。当提供了第二个参数时, includes() 与 startsWith() 方法会从该索引位置开始尝试匹配;而endsWith() 方法会将字符串长度减去该参数,以此为起点开始尝试匹配。当第二个参数未提供时, includes() 与 startsWith() 方法会从字符串起始处开始查找,而 endsWith() 方法则从尾部开始
const msg = 'Hello world!'
console.log(msg.startsWith("Hello")) //true
console.log(msg.endsWith("!")) //true
console.log(msg.includes('o')) //true
console.log(msg.startsWith('o')) //false
console.log(msg.endsWith('world!')) //true
console.log(msg.includes('x')) //false
console.log(msg.startsWith('o', 4)) //true
console.log(msg.endsWith('o', 8)) //true
console.log(msg.includes('o', 8)) //false
第二个参数是字符串的索引位置
如果向 startsWith() 、 endsWith() 或 includes() 方法传入了正则表达式而不是字符串,会抛出错误。这与 indexOf() 以及 lastIndexOf() 方法的表现形成了反差,它们会将正则表达式转换为字符串并搜索它。
repeat() 方法
ES6 还为字符串添加了一个 repeat() 方法,它接受一个参数作为字符串的重复次数,返回一个将初始字符串重复指定次数的新字符串。例如:
console.log("x".repeat(3)); // "xxx"
console.log("hello".repeat(2)); // "hellohello"
console.log("abc".repeat(4)); // "abcabcabcabc"
此方法比相同目的的其余方法更加方便,在操纵文本时特别有用,尤其是在需要产生缩进的代码格式化工具中.
const text = 'hello1 hello2 hello3'
// \d匹配数字[0-9] \s匹配空格 ? 出现一次或零次
pattern = /hello\d\s?/
result = pattern.exec(text)
globalPattern = /hello\d\s?/g
globalResult = globalPattern.exec(text)
stickyPattern = /hello\d\s?/y
stickyResult = stickyPattern.exec(text)
console.log(result[0]) // hello1
console.log(globalResult[0]) // hello1
console.log(stickyResult[0]) // hello1
pattern.lastIndex = 1
globalPattern.lastIndex = 1
stickyPattern.lastIndex = 1
result = pattern.exec(text)
globalResult = globalPattern.exec(text)
stickyResult = stickyPattern.exec(text)
console.log(result[0]) // hello1
console.log(globalResult[0]) // hello2
console.log(stickyResult[0]) // Error! stickResult is null
上例讲解
此例中有三个正则表达式: pattern 中的表达式没有使用任何标志, globalPattern 使用了g 标志, stickyPattern 则使用了 y 标志。对 console.log() 的第一次调用,三个正则表达式分别都返回了 "hello1 " ,此字符串尾部有个空格。
此后,三个模式的 lastIndex 属性全部被更改为 1 ,表示三个模式的正则表达式都应当从第二个字符开始尝试匹配。不使用任何标志的正则表达式完全忽略了对于 lastIndex 的更改,仍然毫无意外地匹配了 "hello1 " ;而使用 g 标志的正则表达式继续匹配了 "hello2 " ,因为它从第二个字符( “e” )开始,持续向着字符串尾部方向搜索;粘连的正则表达式则在第二个字符处没有匹配成功,因此 stickyResult 的值是 null
const text = 'hello1 hello2 hello3'
// \d匹配数字[0-9] \s匹配空格 ? 出现一次或零次
pattern = /hello\d\s?/
result = pattern.exec(text)
globalPattern = /hello\d\s?/g
globalResult = globalPattern.exec(text)
stickyPattern = /hello\d\s?/y
stickyResult = stickyPattern.exec(text)
console.log(result[0]) // hello1
console.log(globalResult[0]) // hello1
console.log(stickyResult[0]) // hello1
console.log(pattern.lastIndex) // 0
console.log(globalPattern.lastIndex) // 7
console.log(stickyPattern.lastIndex) // 7
result = pattern.exec(text)
globalResult = globalPattern.exec(text)
stickyResult = stickyPattern.exec(text)
console.log(result[0]) // hello1
console.log(globalResult[0]) // hello2
console.log(stickyResult[0]) // hello2
console.log(pattern.lastIndex) // 0
console.log(globalPattern.lastIndex) // 14
console.log(stickyPattern.lastIndex) // 14
有两个关于粘连标志的微妙细节需要牢记:
join()方法:
var fruits = ["Banana", "Orange", "Apple", "Mango"];
var energy = fruits.join();
// 输出结果:Banana,Orange,Apple,Mango
join() 方法用于把数组中的所有元素转换一个字符串。
元素是通过指定的分隔符进行分隔的,参数为空默认分隔符是逗号(,)可指定
// 获取字符串中的数字
// 使用传统的方法
const ky = 'kangyun2023kang8888'
let nums = [...ky].filter(c=>!Number.isNaN(parseInt(c)));
console.log(nums.join(""))
// 使用正则表达式
console.log(ky.match(/\d/g).join(""))
// 两个斜杠中间放正则表达式的语句,这就是字面量创建正则表达式
let ky = "hello,world"
// test测试正则表达式,看字符串中是否有符合正则表达式
console.log(/@/.test(ky)) // false
let ky = 'hello,world!'
// 对象第一个参数匹配正则表达式,第二个参数模式 g是全局模式
let reg = new RegExp('e', 'g')
console.log(reg.test(ky)) // 查找字符串中是否含有e
demo
replace()
replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
语法
stringObject.replace(regexp/substr,replacement)
regexp/substr
必需。规定子字符串或要替换的模式的 RegExp 对象。
请注意,如果该值是一个字符串,则将它作为要检索的直接量文本模式,而不是首先被转换为 RegExp 对象。
replacement 必需。一个字符串值。规定了替换文本或生成替换文本的函数。
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>studytitle>
head>
<body>
<div class="content">
hello,world
div>
body>
html>
<script>
let con = prompt('请输入内容,支持正则表达式');
let reg = new RegExp(con, "g")
let div = document.querySelector("div")
// replace(匹配的正则表达式, 替换逻辑(代码))
div.innerHTML = div.innerHTML.replace(reg, search => `${search}` )
script>
let tel = '020-9999999'
// | 或者
console.log(/(010|020)\-\d{7,8}/.test(tel))
// [] ()
let str = '1'
let reg = /[1234567]/
console.log(str.match(reg))
let price = "23.55"
// .除换行外任何字符 .普通点
// .是普通点需要进行转义(\.)
// d \d 0-9
console.log(/\d+\.\d+/.test(price))
// 对象中的参数需要转换成字面量的样式
let reg = new RegExp("\\d+\\.\\d+")
console.log(reg.test(price))
// ^\d 以数字开始 ^以什么什么开始
let ky = 'kanyunn'
console.log(/^\d/.test(ky))
// 没有加边界限定符,只要匹配到3-6位的字母就返回匹配到的,否则返回null
console.log(ky.match(/[a-z]{3,6}/)) // 返回一个数组
// 加上边界限定符^以什么开始$以什么结束
console.log(ky.match(/^[a-z]{3,6}$/)) // null
let ky = 'hello, 2023'
console.log(ky.match(/\d/)) //只会匹配一个2
// \d匹配一个数字
console.log(ky.match(/\d/g)) // g模式,会将2023四个全匹配出来
// \d+匹配一个或多个数字
console.log(ky.match(/\d+/g))
// g模式,匹配到一个后还会继续匹配直到字符串结束
let ky08 = `张三:0755-99999999, 李四:028-8888888`
console.log(ky08.match(/\d{3,4}-\d{7,8}/))
console.log(ky08.match(/\d{3,4}-\d{7,8}/g))
// \d 数字 \D除了数字
console.log(ky08.match(/\D+/g))
// 要取ky08中的中文,只要中文
// [^a]表示不包括a,把不要的符号排除就只余下中文了
// /s表示空白字符 /S除了空白
console.log(ky08.match(/[^:-\d,\s]+/g))
// 输出结果:
// ['2', index: 7, input: 'hello, 2023', groups: undefined]
// ['2', '0', '2', '3']
// ['2023']
// ['0755-99999999', index: 3, input: '张三:0755-99999999, 李四:028-8888888', groups: undefined]
// ['0755-99999999', '028-8888888']
// ['张三:', '-', ', 李四:', '-']
// ['张三', '李四']
// \w表示字母数字下划线_ \W表示除了字母数字下划线
let email = '[email protected]'
console.log(email.match(/\w+@\w+\.\w+/))
console.log(email.match(/^\w+@\w+\.\w+$/))
// 输出结果
// ['[email protected]', index: 0, input: '[email protected]', groups: undefined]
// ['[email protected]', index: 0, input: '[email protected]', groups: undefined]
// .匹配除了换行符之外的所有字符
// 匹配所有字符[\s\S][\d\D]
// i模式,不区分大小写
// g模式,全部匹配
let str = '[email protected]'
console.log(str.match(/N/gi))
console.log(str.replace(/N/gi, '$'))
// 输出结果:
// ['n', 'n']
// [email protected]
split()方法:
把一个字符串分割成字符串数组
var str=“How are you doing today?”;
var n=str.split(" ");
How,are,you,doing,today?
// m模式,每一行单独处理
let str = `
#1 js, 200元 #
#2 php, 300元 #
#9 kangyun.com # kangyun
#3 node.js,180元 #
`
let lessons = str.match(/^\s*#\d+\s+.+\s+#$/gm).map(v=>{
v = v.replace(/\s*#\d+\s+/,'')
.replace(/\s*#/,'')
return v.split(',')
})
console.log(JSON.stringify(Object.fromEntries(lessons)))
// m模式 Unicode
let str = 'hello,world,哈喽呀,饭已OK'
// 将中文匹配出来
console.log(str.match(/\p{sc=Han}/gu))
// match返回所有匹配正则表达式的字符,exec一个一个的返回
let str = 'hello,world'
let reg = /\w/g
console.log(reg.lastIndex)
console.log(reg.exec(str))
console.log(reg.lastIndex)
console.log(reg.exec(str))
console.log(reg.lastIndex)
console.log(reg.exec(str))
// 输出结果
// 0
// ['h', index: 0, input: 'hello,world', groups: undefined]
// 1
// ['e', index: 1, input: 'hello,world', groups: undefined]
// 2
// ['l', index: 2, input: 'hello,world', groups: undefined]
while(res = reg.exec(str)){
console.log(res)
}