ES6标准入门 第四章:字符串的扩展
1、字符串的Unicode 表示法
JavaScript 允许采用 \uxxxx 表示一个字符,其中 xxxx 表示字符的码点。
"\u0061" // "a"
ES5中的缺陷:
以上表示法只限于 \u0000——\uFFFF 之间的字符。超出这个范围的=字符,必须用两个双字节表示。
"\uD842\uDFB7" //"𠮷"
如果直接在\u 后面直接加上超过\uFFFF 的数值:
"\u20BB7" // " 7"
如上代码,JavaScript会理解成 “\u20BB+7” 。由于\u20BB 是一个不可打印字符, 所以显示一个空格。
ES6的改进:
只要将码点放入大括号,就能正确解读改字符。
"\u{20BB7}" // "𠮷" "\u{41}\u{42}\u{43}" // "ABC" let hello = 123; hell\u{6F} // 123
'\u{1F680}' === '\uD83D\uDE80' // true 此行代码表示:大括号表示法 与 四字节的UTF-16编码是等价的。
在JavaScript 中共有6种方法可以表示一个字符:
'\z' === 'z' // true '\172' === 'z' // true '\x7A' === 'z' // true '\u007A' === 'z' // true '\u{7A}' === 'z' // true
2、codePointAt() --- 【定义在字符串实例对象上】
JavaScript内部,字符以UTF-16格式存储,每个字符固定为2字节。对于Unicode 码点大于0xFFFF 的字符(需要4个字节存储),js会认为它们是2个字符。
ES5的缺陷:charCodeAt
var s = "𠮷"; s.length // 2 s.charCodeAt(0) // 55362 s.charCodeAt(1) // 57271
上面的代码中,𠮷 的码点是0x20BB7,UTF-16 编码为0xD842 0xDFB7
(十进制为55362 57271
),需要4
个字节储存。
对于这种四字节字符,JavaScript不能正确处理,字符串长度会被误认为2;charCodeAt 方法只能分别返回前两个字节的值 和 后两个字节的值。
ES6 的改进:codePointAt
提供了codePointAt方法,能够正确处理4 字节字符,返回一个32位的UTF-16字符的码点。(返回的码点是十进制的)
let s = '𠮷a'; s.codePointAt(0) // 134071 s.codePointAt(1) // 57271 s.codePointAt(2) // 97
参数为字符在字符串中的位置(从0开始); 对于两个字节存储的常规字符,它的返回结果与charCodeAt方法相同。
若要codePointAt方法返回的是十六进制的值,可以使用 toString方法转换一下。
let s = '𠮷a'; s.codePointAt(0).toString(16) // "20bb7" s.codePointAt(2).toString(16) // "61"
以上代码仍然存在问题:codePointAt方法的参数仍然是不正确的。【a在字符串的位置是1, 但是传参却是2】
解决方法: 使用for…of 循环,因为它可以正确识别32位的 UTF-16字符。
let s = '𠮷a'; for (let i of s) { console.log( i.codePointAt(0).toString(16) ); } // "20bb7" // "61"
解决问题:codePointAt 方法 是测试一个字符由 2个字节 还是 4个字节 组成的最简单的方法。
function is32Bit( c ) { return c.codePiontAt(0) > 0xFFFF; }
is32Bit("𠮷"); // true
is32Bit("a"); // true
3、String.fromCodePoint() --- 【定义在String对象上】
ES5 的缺陷:String.fromCharCode
String.fromCharCode 用于从码点返回对应的字符,但是不能识别大于Unicode编码 0xFFFF 的字符(32位的UTF-16字符)
String.fromCharCode(0x20BB7) // "ஷ"
上面代码 0x20BB7发生溢出最高位两位被舍弃,最后返回码点U+0BB7
对应的字符,而不是码点U+20BB7
对应的字符。
ES6的改进:String.fromCodePoint
String.fromCodePoint 方法,可以识别大于0xFFFF 的码点;作用上正好与 codePointAt 相反。
String.fromCodePoint(0x20BB7) // "𠮷" String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y' // true
方法中有多个参数,则它们会被合并成一个字符串返回。
注意: fromCodePoint 方法定义在 String对象上, 而codePointAt 方法定义在字符串的实例对象上。
4、字符串的遍历器接口
ES6位字符串添加了遍历器接口,使字符串可以由for…of 循环遍历。
for (let codePoint of 'foo') { console.log(codePoint); } // 'f' // 'o' // 'o'
除了遍历字符串,这个遍历器最大的优点就是可以识别大于 0xFFFF的码点。(传统的for循环无法识别这样的码点)
var text = String.fromCodePoint(0x20BB7); for (let i=0; i<text.length; i++) { console.log( text[i] ) } // "" // "" for (let i of text) { console.log() } // "𠮷"
5、at()
ES5的缺陷:charAt()
charAt方法,返回字符串给定位置的字符。该方法不能识别码点大于 0xFFFF 的字符。
'abc'.charAt(0); // "a" '𠮷'.charAt(0); // "\uD842" 返回的是UTF-16的第一个字节,实际上是无法显示的
ES7中改进:at()
ES7为字符串实例提供了at 方法,识别Unicode编码大于0xFFFF的字符,返回正确的字符。(chrome浏览器已经支持)
'abc'.charAt(0); // "a" '𠮷'.charAt(0); // "𠮷"
6、normalize()
为了表示语调和重音符号,Unicode提供了两种方法。
方法一:直接提供带重音符号的字符。比如Ǒ
(\u01D1)。
方法二:提供合成符号,即原字符和重音字符合成为一个字符。比如O
(\u004F)和ˇ
(\u030C)合成Ǒ
(\u004F\u030C)。
这两种表示方法在视觉和语义上是等价的,但是JavaScript 无法识别。
'\u01D1'==='\u004F\u030C' //false '\u01D1'.length // 1 '\u004F\u030C'.length // 2
ES6为字符串实例提供了normalize方法,用来将字符的不同表示方法统一为同样的样式,称为Unicode正规化。
'\u01D1'.normalize() === '\u004F\u030C'.normalize() // true
normalize方法可以指定一个参数用来指定normalize的方式,参数有四个可选值:
NFC,默认参数,表示“标准等价合成”,返回多个简单字符的合成字符。
NFD,表示“标准等价分解”,即在标准等价的前提下,返回合成字符分解出的多个简单字符。
NFKC,表示“兼容等价合成”,返回合成字符。
NFKD,表示“兼容等价分解”,在兼容等价的前提下,返回合成字符分解出的多个简单字符。
标准等价:视觉和语义上的等价。 兼容等价:语义上等价,但视觉上不等价。
目前,normalize方法不能识别3个或者3个以上的字符合成。
7、includes()、startsWith()、endsWith()
ES5中,只有 indexof 方法可以用来确定一个字符串是否包含在另一个字符串内。
ES6,又提供了3种新方法:
includes(): 返回布尔值,是否找到参数字符串;
startsWith():返回布尔值,参数字符串是否在原字符串头部;
endsWith():返回布尔值,参数字符串是否在原字符串尾部;
这三种方法都支持第2个参数,表示开始搜索的位置。 endsWith中的行为是不一样的,他表示的是前n个字符。
var s = 'hello world!'; s.startsWith('world', 6); // true s.endsWith('hello',5); // true 表示前5个字符中是不是以“hello”结束 s.includes('hello', 6); // false
8、repeat()
repeat方法,表示将原字符串重复n次,返回一个新字符串。
'x'.repeat(3); // 'xxx' 'na'.repeat(0); //""
参数如果是小数,会被取整。
'na'.repeat(2.9); //"nana"
参数如果是负数 或 Infinity,会报错。
'na'.repeat(Infinity); //报错 'na'.repeat(-1); //报错
参数是-1到0 之间的小数,则会等同于0;【取整之后等于-0,repeat等同于0】
'na'.repeat(-0.6); //""
参数NaN等同于0
'na'.repeat(NaN); //""
参数为字符串,则会先转换成数字。
'na'.repeat(‘3’); //"nanana" 'na'.repeat(‘na’); //""
9、padStart()、 padEnd()
ES7 推出了字符串补全功能。如果某个字符串未达到指定长度,会在头部或尾部补全。
第一个参数:指定字符串的最小长度;
第二个参数:用来补全的字符串;
'x'.padStart(5, 'ab'); //"ababx" 'x'.padStart(4, 'ab'); //"abax" 'x'.padEnd(4, 'ab'); //"xaba"
如果原字符串的长度 等于或大于 指定的最小长度,则返回原字符串。
'xxx'.padStart(3, 'ab'); //"xxx" 'xxx'.padEnd(2, 'ab'); //"xxx"
如果省略第二个参数,则会用空格补全。
'x'.padStart(4); // " x" 'x'.padEnd(4); // "x "
10、模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识;在模板字符串中嵌入变量需要将变量名写在 ${} 中。
var name = 'Bob', time = 'today'; `hello ${name}, how are you ${time}`
若在模板字符串中使用反引号,需要在其前面加上反斜杠转义。
var greeting = ` \`yo\` world `;
若使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出中。
模板字符串中的的大括号内可以放入任意的表达式,可以进行运算,以及引用对象属性。
var x = 1; var y = 2; ` ${x} + ${y} = ${x+y} ` // 1+2=3 var obj = {x:1, y:2}; ` ${obj.x + obj.y} ` // 3
模板字符串还能调用函数。
function fn() { return "hello world"; } ` foo ${fn()} bar `; // foo hello world bar
如果大括号中的值不是字符串,将按照一定的规则转换为字符串。【若大括号中是对象,将默认调用对象的toString方法】
如果模板字符串内的变量没有声明,将报错。
var msg = ` Hello, ${place} `; // 变量place 没有声明 抱错
由于模板字符串的大括号内就是要执行的JavaScript代码,因此如果大括号内部是一个字符串,将会原样输出。
` hello ${'world'} `; // hello world
如果需要引用模板字符串本身,可以如下写法。
//写法1 let str = 'return' + ' `hello ${name}` '; let func = new Function('name', str); func('jack'); //hello jack
//写法2
let str = '(name) => `Hello ${name}` ';
let func = eval.call(null, str);
func('jack'); //"Hello Jack"
11、标签模板
标签模板,其实不是模板,而是函数调用的一种形式。 标签就是函数,紧跟在后面的模板字符串就是它的参数。
var a = 5; var b = 10; tag` Hello ${a+b} world ${a*b} `;
function tag(stringArr,value1, value2) { //…… }
function tag(stringArr,...values) { //…… }
函数tag会依次接受多个参数:
- 第一个参数: ['Hello', 'world', ''] ---> 数组成员就是模板字符串中那些没有变量替换的部分, 变量替换只发生在数组成员之间!!
- 第二个参数: 15
- 第三个参数: 50
下面这个例子展示如何将各个参数按照原来的位置拼回去:*****
let total = 30; let msg = passthru` The total is ${total} (${total*1.5} with tax) `; function passthru (literals) { let result = ''; let i = 0; while (i < literals.length) { result += literals[i++]; if (i <arguments.length ) { result += arguments[i]; } } return result; }
12、String.raw()
ES6为原生的String对象提供了 raw 方法。
String.raw()方法用来充当模板字符串的处理函数,返回一个反斜线都会被转义(即反斜线前再加一个反斜线)的字符串。
String.raw`Hi \n${2+3}`; // Hi \\n5
如果原字符串的反斜线已经转义过了, 那么string.raw 不会做任何处理。
String.raw`Hi \\n`; // Hi \\n
String.raw 方法可以作为处理模板字符串的方法,它会将所有的变量替换,并对反斜线进行转义。。
String.raw方法还可以作为正常函数使用。第一个参数是一个具有raw属性的对象, 其raw属性的值应该是一个数组。
String.raw({ raw:'test' }, 0, 1, 2); // "t0e1s2t" //等同于 String.raw({ raw:['t', 'e', 's', 't'] }, 0, 1, 2);
String.raw的代码实现:*****
String.raw = function (strings, ...values) { let output = ""; let i; for (i=0; i<values.length; i++) { output += strings.raw[i] + values[i]; } output += strings.raw[i]; return output; }