You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// 普通字符串`In JavaScript '\n' is a line-feed.`// 多行字符串`In JavaScript this is not legal.`console.log(`string text line 1string text line 2`);// 字符串中嵌入变量varname="Bob",time="today";`Hello ${name}, how are you ${time}?`
functionauthorize(user,action){if(!user.hasPrivilege(action)){thrownewError(// 传统写法为// 'User '// + user.name// + ' is not authorized to do '// + action// + '.'`User ${user.name} is not authorized to do ${action}.`);}}
tag 函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。
tag 函数的其他参数,都是模板字符串各个变量被替换后的值。由于本例中,模板字符串含有两个变量,因此 tag 会接受到 value1 和 value2 两个参数。
tag 函数所有参数的实际值如下。
第一个参数:['Hello ', ' world ', '']
第二个参数: 15
第三个参数:50
也就是说,tag 函数实际上以下面的形式调用。
tag(['Hello ',' world ',''],15,50)
我们可以按照需要编写 tag 函数的代码。下面是 tag 函数的一种写法,以及运行结果。
vara=5;varb=10;functiontag(s,v1,v2){console.log(s[0]);console.log(s[1]);console.log(s[2]);console.log(v1);console.log(v2);return"OK";}tag`Hello ${a+b} world ${a*b}`;// "Hello "// " world "// ""// 15// 50// "OK"
下面是一个更复杂的例子。
vartotal=30;varmsg=passthru`The total is ${total} (${total*1.05} with tax)`;functionpassthru(literals){varresult='';vari=0;while(i<literals.length){result+=literals[i++];if(i<arguments.length){result+=arguments[i];}}returnresult;}msg// "The total is 30 (31.5 with tax)"
varmessage=SaferHTML`<p>${sender} has sent you a message.</p>`;functionSaferHTML(templateData){vars=templateData[0];for(vari=1;i<arguments.length;i++){vararg=String(arguments[i]);// Escape special characters in the substitution.s+=arg.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");// Don't escape special characters in the template.s+=templateData[i];}returns;}
varsender='<script>alert("abc")</script>';// 恶意代码varmessage=SaferHTML`<p>${sender} has sent you a message.</p>`;message// <p><script>alert("abc")</script> has sent you a message.</p>
标签模板的另一个应用,就是多语言转换(国际化处理)。
i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`// "欢迎访问xxx,您是第xxxx位访问者!"
// 下面的hashTemplate函数// 是一个自定义的模板处理函数varlibraryHtml=hashTemplate` <ul> #for book in ${myBooks} <li><i>#{book.title}</i> by #{book.author}</li> #end </ul>`;
字符的 Unicode 表示法
JavaScript 允许采用
\uxxxx
形式表示一个字符,其中xxxx
表示字符的Unicode
码点。但是,这种表示法只限于码点在
\u0000
~\uFFFF
之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。上面代码表示,如果直接在\u后面跟上超过
0xFFFF
的数值(比如\u20BB7
),JavaScript
会理解成\u20BB+7
。由于\u20BB
是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
上面代码中,最后一个例子表明,大括号表示法与四字节的 UTF-16 编码是等价的。
有了这种表示法之后,JavaScript 共有6种方法可以表示一个字符。
codePointAt()
JavaScript内部,字符以UTF-16的格式储存,每个字符固定为
2
个字节。对于那些需要4
个字节储存的字符(Unicode码点大于0xFFFF
的字符),JavaScript会认为它们是两个字符。上面代码中,汉字“𠮷”(注意,这个字不是”吉祥“的”吉“)的码点是
0x20BB7
,UTF-16编码为0xD842 0xDFB7
(十进制为55362 57271),需要4
个字节储存。对于这种4
个字节的字符,JavaScript不能正确处理,字符串长度会误判为2
,而且charAt
方法无法读取整个字符,charCodeAt
方法只能分别返回前两个字节和后两个字节的值。ES6提供了
codePointAt
方法,能够正确处理4
个字节储存的字符,返回一个字符的码点。codePointAt方法的参数,是字符在字符串中的位置(从0开始)。上面代码中,JavaScript将“𠮷a”视为三个字符,codePointAt方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点134071(即十六进制的20BB7)。在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt方法的结果与charCodeAt方法相同。
总之,codePointAt方法会正确返回32位的UTF-16字符的码点。对于那些两个字节储存的常规字符,它的返回结果与charCodeAt方法相同。
codePointAt方法返回的是码点的十进制值,如果想要十六进制的值,可以使用toString方法转换一下。
你可能注意到了,codePointAt方法的参数,仍然是不正确的。比如,上面代码中,字符a在字符串s的正确位置序号应该是1,但是必须向codePointAt方法传入2。解决这个问题的一个办法是使用for...of循环,因为它会正确识别32位的UTF-16字符。
codePointAt方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。
String.fromCodePoint()
ES5提供String.fromCharCode方法,用于从码点返回对应字符,但是这个方法不能识别32位的UTF-16字符(Unicode编号大于0xFFFF)。
上面代码中,String.fromCharCode不能识别大于0xFFFF的码点,所以0x20BB7就发生了溢出,最高位2被舍弃了,最后返回码点U+0BB7对应的字符,而不是码点U+20BB7对应的字符。
ES6提供了String.fromCodePoint方法,可以识别大于0xFFFF的字符,弥补了String.fromCharCode方法的不足。在作用上,正好与codePointAt方法相反。
上面代码中,如果String.fromCodePoint方法有多个参数,则它们会被合并成一个字符串返回。
注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。
字符串的遍历器接口
ES6为字符串添加了遍历器接口,使得字符串可以被
for...of
循环遍历。除了遍历字符串,这个遍历器最大的优点是可以识别大于
0xFFFF
的码点,传统的for
循环无法识别这样的码点。上面代码中,字符串
text
只有一个字符,但是for
循环会认为它包含两个字符(都不可打印),而for...of
循环会正确识别出这一个字符。at()
ES5对字符串对象提供
charAt
方法,返回字符串给定位置的字符。该方法不能识别码点大于0xFFFF
的字符。
上面代码中,
charAt
方法返回的是UTF-16编码的第一个字节,实际上是无法显示的。目前,有一个提案,提出字符串实例的
at
方法,可以识别Unicode编号大于0xFFFF
的字符,返回正确的字符。normalize()
许多欧洲语言有语调符号和重音符号。为了表示它们,Unicode提供了两种方法。一种是直接提供带重音符号的字符,比如Ǒ(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。
这两种表示方法,在视觉和语义上都等价,但是JavaScript不能识别。
上面代码表示,JavaScript将合成字符视为两个字符,导致两种表示方法不相等。
ES6提供字符串实例的normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为Unicode正规化。
normalize方法可以接受一个参数来指定normalize的方式,参数的四个可选值如下。
上面代码表示,NFC参数返回字符的合成形式,NFD参数返回字符的分解形式。
不过,normalize方法目前不能识别三个或三个以上字符的合成。这种情况下,还是只能使用正则表达式,通过Unicode编号区间判断。
includes(), startsWith(), endsWith()
传统上,JavaScript只有
indexOf
方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。这三个方法都支持第二个参数,表示开始搜索的位置。
上面代码表示,使用第二个参数
n
时,endsWith
的行为与其他两个方法有所不同。它针对前n
个字符,而其他两个方法针对从第n
个位置直到字符串结束。repeat()
repeat
方法返回一个新字符串,表示将原字符串重复n
次。参数如果是小数,会被取整。
如果
repeat
的参数是负数或者Infinity
,会报错。但是,如果参数是
0
到-1
之间的小数,则等同于0
,这是因为会先进行取整运算。0
到-1
之间的小数,取整以后等于-0
,repeat
视同为0
。参数
NaN
等同于0
。如果
repeat
的参数是字符串,则会先转换成数字。padStart(),padEnd()
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。
padStart()
用于头部补全,padEnd()
用于尾部补全。上面代码中,
padStart
和padEnd
一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。
如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
如果省略第二个参数,默认使用空格补全长度。
padStart
的常见用途是为数值补全指定位数。下面代码生成10位的数值字符串。另一个用途是提示字符串格式。
模板字符串
传统的JavaScript语言,输出模板通常是这样写的。
上面这种写法相当繁琐不方便,ES6引入了模板字符串解决这个问题。
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
上面代码中的模板字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。
上面代码中,所有模板字符串的空格和换行,都是被保留的,比如
<ul>
标签前面会有一个换行。如果你不想要这个换行,可以使用trim
方法消除它。模板字符串中嵌入变量,需要将变量名写在
${}
之中。大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。
模板字符串之中还能调用函数。
如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。
如果模板字符串中的变量没有声明,将报错。
由于模板字符串的大括号内部,就是执行JavaScript代码,因此如果大括号内部是一个字符串,将会原样输出。
上面代码中,模板字符串的变量之中,又嵌入了另一个模板字符串,使用方法如下。
如果需要引用模板字符串本身,在需要时执行,可以像下面这样写。
实例:模板编译
下面,我们来看一个通过模板字符串,生成正式模板的实例。
上面代码在模板字符串之中,放置了一个常规模板。该模板使用<%...%>放置JavaScript代码,使用<%= ... %>输出JavaScript表达式。
怎么编译这个模板字符串呢?
一种思路是将其转换为JavaScript表达式字符串。
这个转换使用正则表达式就行了。
然后,将template封装在一个函数里面返回,就可以了。
将上面的内容拼装成一个模板编译函数compile。
compile函数的用法如下。
标签模板
模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。
标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。
但是,如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。
上面代码中,模板字符串前面有一个标识名
tag
,它是一个函数。整个表达式的返回值,就是tag
函数处理模板字符串后的返回值。函数
tag
依次会接收到多个参数。tag
函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。tag
函数的其他参数,都是模板字符串各个变量被替换后的值。由于本例中,模板字符串含有两个变量,因此tag
会接受到value1
和value2
两个参数。tag
函数所有参数的实际值如下。也就是说,
tag
函数实际上以下面的形式调用。我们可以按照需要编写
tag
函数的代码。下面是tag
函数的一种写法,以及运行结果。下面是一个更复杂的例子。
上面这个例子展示了,如何将各个参数按照原来的位置拼合回去。
passthru
函数采用rest
参数的写法如下。“标签模板”的一个重要应用,就是过滤HTML字符串,防止用户输入恶意内容。
上面代码中,
sender
变量往往是用户提供的,经过SaferHTML
函数处理,里面的特殊字符都会被转义。标签模板的另一个应用,就是多语言转换(国际化处理)。
模板字符串本身并不能取代Mustache之类的模板库,因为没有条件判断和循环处理功能,但是通过标签函数,你可以自己添加这些功能。
除此之外,你甚至可以使用标签模板,在JavaScript语言之中嵌入其他语言。
上面的代码通过
jsx
函数,将一个DOM
字符串转为React对象。你可以在Github找到jsx
函数的 具体实现。下面则是一个假想的例子,通过
java
函数,在JavaScript
代码之中运行Java
代码。模板处理函数的第一个参数(模板字符串数组),还有一个
raw
属性。上面代码中,
console.log
接受的参数,实际上是一个数组。该数组有一个raw
属性,保存的是转义后的原字符串。请看下面的例子。
上面代码中,
tag
函数的第一个参数strings
,有一个raw
属性,也指向一个数组。该数组的成员与strings
数组完全一致。比如,strings
数组是["First line\nSecond line"]
,那么strings.raw
数组就是["First line\\nSecond line"]
。两者唯一的区别,就是字符串里面的斜杠都被转义了。比如,strings.raw
数组会将\n
视为\\
和n
两个字符,而不是换行符。这是为了方便取得转义之前的原始模板而设计的。String.raw()
ES6还为原生的
String
对象,提供了一个raw
方法。String.raw
方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。String.raw
方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。String.raw
方法也可以作为正常的函数使用。这时,它的第一个参数,应该是一个具有raw
属性的对象,且raw
属性的值应该是一个数组。The text was updated successfully, but these errors were encountered: