4 字符串的扩展(ECMAScript6入门)

4.1 字符的unicode表示法

unicode表示法 码点 兼容性
\uxxxx 一个两个字节长度的16进制数字 all
\uxxxx\uxxxx 两个两个字节长度的16进制数字 all
\u{xx...} 码点为任意unicode编码 es6+
1
2
3
4
5
6
7
8
9
10
11
"\u{20BB7}"
// "𠮷"

"\u{41}\u{42}\u{43}"
// "ABC"

let hello = 123;
hell\u{6F} // 123

'\u{1F680}' === '\uD83D\uDE80'
// true

4.2 codePointAt()

背景:javascript使用utf-16(两个字节)存储字符,使用charCodeAt无法正确处理四字节字符

类似charCodeAt,区别是codePointAt能够正确识别四字节字符(比如吉祥)。

1
2
3
4
5
6
7
var s = '吉a';

s.codePointAt(0) // 134071(吉,0xD842 0xDFB7)
s.codePointAt(1) // 57271(0xDFB7)

s.charCodeAt(2) // 97
s.codePointAt(2) // 97

注意:codePointAt仍会按照两个字节一个字符的方式计算字符串中字符数量,因此传递给codePointAt的参数和charCodeAt是一样的。
扩展:使用for-of可以正确识别32位(4字节)UTF-16字符。

1
2
3
4
5
6
var s = '吉a';
for (let ch of s) {
console.log(ch.codePointAt(0).toString(16));
}
// 20bb7
// 61

技巧:使用codePointAt获取字符的UTF-16完整编码,并判断其大小,就可以识别4字节字符。

1
2
3
4
5
6
function is32Bit(c) {
return c.codePointAt(0) 0xFFFF;
}

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

4.3 String.fromCodePoint()

背景:String.fromCharCode用于从码点返回对应的字符,但对32位的UTF-16字符进行转换会发生溢出。

String.fromCodePoint对应codePointAt,可以正确返回32为UTF-16字符对应的字符;当传递多个参数给String.fromCodePoint时,会返回每个参数(码点)对应的字符拼接成的字符串。

1
2
3
4
String.fromCodePoint(0x20BB7)
// "𠮷"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true

4.4 字符串的遍历器接口

说明:es6为字符串添加了便利器接口,因而可以通过for-of遍历字符串。
特点:能够正确识别码点大于\uffff的字符
技巧:利用for-of计算字符串正确的字符个数(传统的forfor-in无法做到)

1
2
3
4
5
var text = String.fromCodePoint(0x20BB7);
for (let i of text) {
console.log(i);
}
// "吉"

4.5 at()

说明:字符串的方法(提案状态?),能够识别码点大于\uffff的字符
ES5:字符串的charAt方法用于返回字符串给定位置的字符,但不能正确识别码点大于\uffff的字符

1
'吉'.at(0) // "吉"

4.6 normalize()

说明:es6提供字符串实例的normalize方法,用来将字符的不同表示方法统一为同样的形式,称为unicode正规化

4.7 includes(), startsWith(), endsWith()

方法 返回值 第一个参数 第二个参数(可选)
includes 布尔值,表示是否找到了参数字符串 要搜索的字串 开始搜索的位置
startsWith 布尔值,表示参数字符串是否在源字符串的头部 要搜索的字串 开始搜索的位置
endsWith 布尔值,表示参数字符串是否在源字符串的尾部 要搜索的字串 停止搜索的位置
1
2
3
4
5
var s = 'Hello world!';

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

4.8 repeat()

用途:返回一个将源字符串重复n次的新字符串
注意:如果参数不是整数,将会按照一定规则取整

非整数参数 转换后
小数 向下取整
负数或Infinity 报错
0~-1 0
NaN 0
字符串 相应的数字
1
2
3
4
5
6
7
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

'na'.repeat(-0.9) // ""
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"

4.9 padStart(), padEnd()

ES7:补全字符串的长度

函数 用途
padStart 用于头部补全
padEnd 用于尾部补全
参数 说明
第一个参数 指定字符串的最小长度
第二个参数(可选) 用来补全的字符串,默认为空格。如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串

注意:如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。

1
2
3
4
5
6
7
8
// 为数值补全指定位数
'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"

// 提示字符串格式
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

4.10 模版字符串

语法:使用反引号括起来

  • 可以当作普通字符串来使用,也可以用来定义多行字符串
  • 可以在字符串中嵌入变量
  • 如果模版字符串中使用反引号,则前面要用反斜杠转义
  • 所有的空格和缩进都会被保留在输出之中,不需要的话可以使用trim方法消除
  • 模板字符串中嵌入变量,需要将变量名写在${}之中

4.10.1 嵌入JS表达式

大括号内部可以放入任意的JavaScript表达式,可以

  • 进行运算
  • 引用对象属性
  • 调用函数

注意:

  • 如果${}中的值不是字符串,将按照一般的规则转为字符串
  • 如果${}内部是一个字符串,将会原样输出
  • 如果模板字符串中的变量没有声明,将报错
  • 通过${},模板字符串甚至还能嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 定义返回模版字符串的函数
const tmpl = addrs =`
<table>
${addrs.map(addr =`
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`;

// 定义填充模版字符串的数据
const data = [
{ first: '<Jane>', last: 'Bond' },
{ first: 'Lars', last: '<Croft>' },
];

console.log(tmpl(data));
// <table>
//
// <tr><td><Jane></td></tr>
// <tr><td>Bond</td></tr>
//
// <tr><td>Lars</td></tr>
// <tr><td><Croft></td></tr>
//
// </table>

4.11 实例:模版编译

l04_template.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<script>
export default {
data () {
return {
template: '',
templateCompiled: '',
templateOutput: ''
}
},
methods: {
/**
* 模版编译函数
* @param {string} template 模版字符串
* @return {string} 可执行的js代码
*/
compile: function (template){
var evalExpr = /<%=(.+?)%>/g;
var expr = /<%([\s\S]+?)%>/g;

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

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

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

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

${ template }

return output;
})`;

return script;
}
},
ready: function () {
// 定义原始模版
this.template = `
<ul>
<% for(var i=0; i < data.supplies.length; i++) { %>
<li><%= data.supplies[i] %></li>
<% } %>
</ul>
`;
this.templateCompiled = this.compile(this.template);

// eval 中的代码会定义一个函数,并返回这个函数的引用
var parse = eval(this.templateCompiled);

// 调用函数完成拼接
this.templateOutput = parse({ supplies: [ "broom", "mop", "cleaner" ] });
}
}
</script>

<template>
<h3>模版</h3>
<code>{{template}}</code>
<h3>编译后的模版</h3>
<code>{{templateCompiled}}</code>
<h3>灌入数据的模版</h3>
<code>{{templateOutput}}</code>
</template>

1
2
# http://localhost:8080/#!/l04
$ npm start

Alt text

4.12 标签模版

说明:一种针对模版字符串的特殊函数调用。
语法:

1
2
3
4
5
6
7
8
函数名`模${表达式1}版字${表达式2}串`;
// 等价于

/**
* @param {array like} 第一个参数
* @param {...string} 不定参数
*/

函数名(['模','版','字','符','串'], 表达式1返回值, 表达式2返回值);

参数:调用函数传入的第一个参数有一个row属性,是一个和第一个参数本身内容相同的数组,不过元素中的所有\都被转义了,从而可以获得字符串的原始版本。
用途:通过实现相应的处理模版字符串的函数,标签模版常用于

  • 处理HTML字符串过滤
  • 多语言转换(国际化处理)
  • 为模版增加条件判断和循环处理等模版基础能力
  • 嵌入其它语言

l04_taggedTemplate.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<script>
export default {
data () {
return {
template: "Hello ${ a + b } world ${ a * b}",
templateOutput: ''
}
},
methods: {
/**
* 将各个参数按照原来的位置拼合回去(写法1)
* @param {array} literals 被插入的表达式隔开的字符串组成的数组
* @return {string} 各个参数按照原来的位置拼合的字符串
*/
passthru1: function (literals) {
var result = ''
var i = 0
while (i < literals.length) {
// 拼接单纯的字符串
result += literals[i++]

// 拼接插入的表达式运算结果
if (i < arguments.length) {
result += arguments[i]
}
}
return result;
},
/**
* 将各个参数按照原来的位置拼合回去(写法2)
* @param {array} literals 被插入的表达式隔开的字符串组成的数组
* @param {...array} values 插入的所有表达式结果(从左到右摘出来)
* @return {string} 各个参数按照原来的位置拼合的字符串
*/
passthru2: function (literals, ...values) {
var output = ''
for (var index = 0; index < values.length; index++) {
output +=literals[index] + values[index]
}
// 被插入的表达式隔开的字符串组成的数组 比 插入的所有表达式结果长度大1
output += literals[index]

return output
}
},
ready: function () {
let a = 2
let b = 4
this.templateOutput = this.passthru2`Hello ${ a + b } world ${ a * b}`
}
}
</script>

<template>
<p>运行</p>
<code>passthru`{{template}}`</code>
<p>输出</p>
<code>{{templateOutput}}</code>
</template>

4.13 String.raw()

用途:两种使用方式

  • 作为模版标签使用
  • 作为普通函数调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<script>
export default {
data () {
return {
hiHuanhang: '',
hiUtf16: '',
test1: '',
test2: ''
}
},
methods: {
/**
* 自己实现一个 String.raw
* @param {array} strings 被插入的表达式隔开的字符串组成的类数组,需要有raw属性
* @param {...string} value 插入的表达式返回值
* @return {string} 转义后的字符串
*/
myRaw: function (strings, ...value) {
var output = '';
for (var index = 0; index < values.length; index++) {
output += strings.raw[index] + values[index];
}
output += strings.raw[index];
return output;
}
},
ready: function () {

/* 用法1:当作模版标签使用 */
this.hiHuanhang = String.raw`Hi\n${2+3}!`;
// "Hi\\n5!"

this.hiUtf16 = String.raw`Hi\u000A!`;
// 'Hi\\u000A!'

/* 用法2:当作普通函数调用 */
this.test1 = String.raw({ raw: 'test' }, 0, 1, 2);
// 't0e1s2t'

// 等同于
this.test2 = String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);
}
}
</script>
<template>
<p>转换换行符</p>
<p>{{hiHuanhang}}</p>

<p>转换utf-16表示方式</p>
<p>{{hiUtf16}}</p>

<p>当做普通函数调用</p>
<p>{{test2}}</p>
</template>