关于 JS 的字符串
字符的 Unicode 表示法
在 es6 中加强了对 unicode 支持:\uxxxx
,xxxx 称为码点 ,范围 0000 ~ ffff ,超出指定范围需要两个字节表示(\u{}),比如 𠮷 (jí),码点:0x20BB7, \u20BB7
会错误的转成 \u20BB+7
, 正确的使用 \u{20BB7}
如何表示字符?
// 第1种
'\z' === 'z' // true \z 什么也不表示 会自动去除前面的 \
// 第2种
'\172' === 'z' // true 如果\后面是三个八进制的数,则表示一个字符
'\172'.toString() // 'z'
// 第3种
'\x7A' === 'z' // true 如果\后面是两个十六进制的数,则表示一个字符
'\x7A'.toString() // 'z'
// 第4种
'\u007A' === 'z' // true 007A 叫做码点
'\u{7A}' === 'z' // true
字符串的遍历接口
for(let item of 'hello') {
console.log(item)
}
模板字符串
let name = 'hello'
let str = `${name}, welcome to my world`
let p = `hello
world, welcome to
our feature
!`
console.log(p)
// hello
// world, welcome to
// our feature
// !
// 支持 三元表达式
let class2 = `icon icon-${isLoading ? 'loading' : 'default'}`
// 带标签的模板字符串
const foo = (a, b, c, d) => {
console.log(a, b, c, d)
}
const name = 'lisi'
const age = 18
foo`你好,这是${name},他今年${age}岁`
// ['你好,这是', ',他今年', '岁', raw: Array(3)] 'lisi' 18 undefined
String.fromCodePoint()
注意:这是静态方法
console.log(String.fromCharCode(0x20BB7)) // 是乱码, 这是 ES5 的方法,根据入参(Unicode 的码点)返回字符,超出了 unicode 的返回
console.log(String.fromCodePoint(0x20BB7))
String.prototype.includes()
const str = 'hello'
console.log(str.indexOf('el')) // 1 表示位置
console.log(str.includes('el')) // true
String.prototype.startsWith()
const str = 'hello'
console.log(str.startsWith('he')) // true
String.prototype.endsWith()
const str = 'hello'
console.log(str.endsWith('llo')) // true
String.prototype.repeat()
const str = 'hello'
console.log(str.repeat(2)) // 'hellohello'
console.log(str.repeat()) // ''
'abc'.padStart(10); // " abc"
'abc'.padStart(10, "foo"); // "foofoofabc"
'abc'.padStart(6,"123465"); // "123abc"
'abc'.padStart(8, "0"); // "00000abc"
'abc'.padStart(1); // "abc"
String.prototype.padStart() (ES8/ES2017)
let str = 'hello'
console.log(str.padStart(8)) // ' hello'
console.log(str.padStart(8, '-')) // '---hello'
// Polyfill 方案
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
if (!String.prototype.padStart) {
String.prototype.padStart = function padStart(targetLength,padString) {
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
padString = String((typeof padString !== 'undefined' ? padString : ' '));
if (this.length > targetLength) {
return String(this);
}
else {
targetLength = targetLength-this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
}
return padString.slice(0,targetLength) + String(this);
}
};
}
String.prototype.padEnd() (ES8/ES2017)
'abc'.padEnd(10); // "abc "
'abc'.padEnd(10, "foo"); // "abcfoofoof"
'abc'.padEnd(6, "123456"); // "abc123"
'abc'.padEnd(1); // "abc"
// Polyfill 方案
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
if (!String.prototype.padEnd) {
String.prototype.padEnd = function padEnd(targetLength,padString) {
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
padString = String((typeof padString !== 'undefined' ? padString: ''));
if (this.length > targetLength) {
return String(this);
}
else {
targetLength = targetLength-this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
}
return String(this) + padString.slice(0,targetLength);
}
};
}
放松模板字符串文字限制(ES 9 / ES 2018)
该特性只能用于带标签的模板字符串。
// 带标签的模板字符串
const foo = (a, b, c, d) => {
console.log(a, b, c, d)
}
const name = 'lisi'
const age = 18
foo`你好,这是${name},他今年${age}岁`
// ['你好,这是', ',他今年', '岁', raw: Array(3)] 'lisi' 18 undefined
const bar = arg => {
console.log(arg)
}
bar`\u{61} and \u{62}` // ['a and b', raw: Array(1)] 把unicode 转成了字符
bar`\u{61} and \unicode` // [undefined, raw: Array(1)] es9 之前 这行会报错的
let str = `\u{61} and \unicode` // Uncaught SyntaxError: Invalid Unicode escape sequence 该特性只能用于带标签的模板字符串。这是普通模板字符串
String.prototype.trimStart()(ES10/ES2019)
该方法从字符串的开头删除空格。trimLeft()
是此方法的别名。
const greeting = ' Hello world! ';
console.log(greeting.trimStart()); // 'Hello world! '
console.log(greeting.trimLeft()); // 'Hello world! '
console.log(greeting.replace(/^\s+/g, '')) // 效果同上
问题:正则表达式里 \s
可以匹配哪些空白符? 包括空格、制表符、换页符和换行符。等价于 [ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]
。 但是有一个测试有问题:/\s/.test("\u180e")
结果为 false .
String.prototype.trimEnd()(ES10/ES2019)
该方法从一个字符串的末端移除空白字符。trimRight() 是这个方法的别名。
const greeting = ' Hello world! ';
console.log(greeting.trimEnd()); // ' Hello world!'
console.log(greeting.trimRight()); // ' Hello world!'
console.log(greeting.replace(/\s+$/g, '')) // 效果同上
String.prototype.matchAll() (ES11/ES2020)
该方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。
const str = `
<html>
<body>
<div>第一个div</div>
<p>这是p</p>
<div>第二个div</div>
<span>这是span</span>
</body>
</html>
`
// exec g
function selectDiv(regExp, str) {
let matches = []
while(true) {
console.log(regExp.lastIndex)
const match = regExp.exec(str)
if(match == null) break
matches.push(match[1])
}
return matches
}
const regExp = /<div>(.*)<\/div>/g // 这里的全局模式不可去除,否则会死循环
let res = selectDiv(regExp, str)
console.log(res) // ["第一个div", "第二个div"]
// match 缺点:捕获组会被忽略
console.log(str.match(regExp)) // ['<div>第一个div</div>', '<div>第二个div</div>']
// replace
function selectDiv2(regExp, str) {
let matches = []
str.replace(regExp, (all, first) => {
// all 是完整的匹配结果,first 是第一个子表达式结果
matches.push(first)
})
return matches
}
let res = selectDiv2(regExp, str)
console.log(res) // ['第一个div', '第二个div']
// matchAll
function selectDiv3(regExp, str) {
let matches = []
for(let match of str.matchAll(regExp)) {
matches.push(match[1])
}
return matches
}
let res = selectDiv3(regExp, str)
console.log(res) // ['第一个div', '第二个div']
matchAll 简单实例:
const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';
const array = [...str.matchAll(regexp)];
console.log(array[0]);
// ["test1", "e", "st1", "1"]
console.log(array[1]);
// ["test2", "e", "st2", "2"]
在 matchAll
出现之前,通过在循环中调用 regexp.exec()
来获取所有匹配项信息(regexp 需使用 /g
标志)。 如果使用 matchAll
,就可以不必使用 while 循环加 exec 方式(且正则表达式需使用 /g
标志)。使用 matchAll
会得到一个迭代器的返回值,配合 for...of
, array spread, 或者 Array.from()
可以更方便实现功能。 如果没有 /g
标志,matchAll
会抛出异常。 matchAll
内部做了一个 regexp 的复制,所以不像 regexp.exec, lastIndex
在字符串扫描时不会改变。
const regexp = RegExp('[a-c]','g');
regexp.lastIndex = 1;
const str = 'abc';
Array.from(str.matchAll(regexp), m => `${regexp.lastIndex} ${m[0]}`);
// Array [ "1 b", "1 c" ]
matchAll
的另外一个亮点是更好地获取捕获组。因为当使用 match()
和 /g
标志方式获取匹配信息时,捕获组会被忽略。