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;
}   

 

posted @ 2018-03-13 19:58  见证LBJ  阅读(293)  评论(0编辑  收藏  举报