ES6字符串的扩展方法

includes(), startsWith(), endsWith() 

传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。

  • includes():返回布尔值,表示是否找到了参数字符串。

  • startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。

  • endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。

    var s = 'Hello world!';

    s.startsWith('Hello') // true s.endsWith('!') // true s.includes('o') // true

这三个方法都支持第二个参数,表示开始搜索的位置。

1var s = 'Hello world!'; 2 3s.startsWith('world', 6) // true 4s.endsWith('Hello', 5) // true 5s.includes('Hello', 6) // false

上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。

repeat()

repeat方法返回一个新字符串,表示将原字符串重复n次。

1'x'.repeat(3) // "xxx" 2'hello'.repeat(2) // "hellohello" 3'na'.repeat(0) // ""

参数如果是小数,会被取整。

'na'.repeat(2.9) // "nana"

如果repeat的参数是负数或者Infinity,会报错。

1'na'.repeat(Infinity) 2// RangeError 3'na'.repeat(-1) 4// RangeError

但是,如果参数是0到-1之间的小数,则等同于0,这是因为会先进行取整运算。0到-1之间的小数,取整以后等于-0repeat视同为0。

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

参数NaN等同于0。

'na'.repeat(NaN) // ""

如果repeat的参数是字符串,则会先转换成数字。

1'na'.repeat('na') // "" 2'na'.repeat('3') // "nanana"

padStart(),padEnd()

ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。

1'x'.padStart(5, 'ab') // 'ababx' 2'x'.padStart(4, 'ab') // 'abax' 3 4'x'.padEnd(5, 'ab') // 'xabab' 5'x'.padEnd(4, 'ab') // 'xaba'

上面代码中,padStartpadEnd一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。

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

1'xxx'.padStart(2, 'ab') // 'xxx' 2'xxx'.padEnd(2, 'ab') // 'xxx'

如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。

1'abc'.padStart(10, '0123456789') 2// '0123456abc'

如果省略第二个参数,默认使用空格补全长度。

1'x'.padStart(4) // ' x' 2'x'.padEnd(4) // 'x '

padStart的常见用途是为数值补全指定位数。下面代码生成10位的数值字符串。

1'1'.padStart(10, '0') // "0000000001" 2'12'.padStart(10, '0') // "0000000012" 3'123456'.padStart(10, '0') // "0000123456"

另一个用途是提示字符串格式。

1'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12" 2'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

模板字符串

传统的JavaScript语言,输出模板通常是这样写的。

1$('#result').append( 2 'There are <b>' + basket.count + '</b> ' + 3 'items in your basket, ' + 4 '<em>' + basket.onSale + 5 '</em> are on sale!' 6);

上面这种写法相当繁琐不方便,ES6引入了模板字符串解决这个问题。

1$('#result').append(` 2 There are <b>${basket.count}</b> items 3 in your basket, <em>${basket.onSale}</em> 4 are on sale! 5`);

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

1// 普通字符串 2`In JavaScript '\n' is a line-feed.` 3 4// 多行字符串 5`In JavaScript this is 6 not legal.` 7 8console.log(`string text line 1 9string text line 2`); 10 11// 字符串中嵌入变量 12var name = "Bob", time = "today"; 13`Hello ${name}, how are you ${time}?`

上面代码中的模板字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。

var greeting = `\`Yo\` World!`;

如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

1$('#list').html(` 2<ul> 3 <li>first</li> 4 <li>second</li> 5</ul> 6`);

上面代码中,所有模板字符串的空格和换行,都是被保留的,比如<ul>标签前面会有一个换行。如果你不想要这个换行,可以使用trim方法消除它。

1$('#list').html(` 2<ul> 3 <li>first</li> 4 <li>second</li> 5</ul> 6`.trim());

模板字符串中嵌入变量,需要将变量名写在${}之中。

1function authorize(user, action) { 2 if (!user.hasPrivilege(action)) { 3 throw new Error( 4 // 传统写法为 5 // 'User ' 6 // + user.name 7 // + ' is not authorized to do ' 8 // + action 9 // + '.' 10 `User ${user.name} is not authorized to do ${action}.`); 11 } 12}

大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。

1var x = 1; 2var y = 2; 3 4`${x} + ${y} = ${x + y}` 5// "1 + 2 = 3" 6 7`${x} + ${y * 2} = ${x + y * 2}` 8// "1 + 4 = 5" 9 10var obj = {x: 1, y: 2}; 11`${obj.x + obj.y}` 12// 3

模板字符串之中还能调用函数。

1function fn() { 2 return "Hello World"; 3} 4 5`foo ${fn()} bar` 6// foo Hello World bar

如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。

如果模板字符串中的变量没有声明,将报错。

1// 变量place没有声明 2var msg = `Hello, ${place}`; 3// 报错

由于模板字符串的大括号内部,就是执行JavaScript代码,因此如果大括号内部是一个字符串,将会原样输出。

1`Hello ${'World'}` 2// "Hello World"

模板字符串甚至还能嵌套。

1const tmpl = addrs => ` 2 <table> 3 ${addrs.map(addr => ` 4 <tr><td>${addr.first}</td></tr> 5 <tr><td>${addr.last}</td></tr> 6 `).join('')} 7 </table> 8`;

上面代码中,模板字符串的变量之中,又嵌入了另一个模板字符串,使用方法如下。

1const data = [ 2 { first: '<Jane>', last: 'Bond' }, 3 { first: 'Lars', last: '<Croft>' }, 4]; 5 6console.log(tmpl(data)); 7// <table> 8// 9// <tr><td><Jane></td></tr> 10// <tr><td>Bond</td></tr> 11// 12// <tr><td>Lars</td></tr> 13// <tr><td><Croft></td></tr> 14// 15// </table>

如果需要引用模板字符串本身,在需要时执行,可以像下面这样写。

1// 写法一 2let str = 'return ' + '`Hello ${name}!`'; 3let func = new Function('name', str); 4func('Jack') // "Hello Jack!" 5 6// 写法二 7let str = '(name) => `Hello ${name}!`'; 8let func = eval.call(null, str); 9func('Jack') // "Hello Jack!"

实例:模板编译

下面,我们来看一个通过模板字符串,生成正式模板的实例。

1var template = ` 2<ul> 3 <% for(var i=0; i < data.supplies.length; i++) { %> 4 <li><%= data.supplies[i] %></li> 5 <% } %> 6</ul> 7`;

上面代码在模板字符串之中,放置了一个常规模板。该模板使用<%...%>放置JavaScript代码,使用<%= ... %>输出JavaScript表达式。

怎么编译这个模板字符串呢?

一种思路是将其转换为JavaScript表达式字符串。

1echo('<ul>'); 2for(var i=0; i < data.supplies.length; i++) { 3 echo('<li>'); 4 echo(data.supplies[i]); 5 echo('</li>'); 6}; 7echo('</ul>');

这个转换使用正则表达式就行了。

1var evalExpr = /<%=(.+?)%>/g; 2var expr = /<%([\s\S]+?)%>/g; 3 4template = template 5 .replace(evalExpr, '`); \n echo( $1 ); \n echo(`') 6 .replace(expr, '`); \n $1 \n echo(`'); 7 8template = 'echo(`' + template + '`);';

然后,将template封装在一个函数里面返回,就可以了。

1var script = 2`(function parse(data){ 3 var output = ""; 4 5 function echo(html){ 6 output += html; 7 } 8 9 ${ template } 10 11 return output; 12})`; 13 14return script;

将上面的内容拼装成一个模板编译函数compile

1function compile(template){ 2 var evalExpr = /<%=(.+?)%>/g; 3 var expr = /<%([\s\S]+?)%>/g; 4 5 template = template 6 .replace(evalExpr, '`); \n echo( $1 ); \n echo(`') 7 .replace(expr, '`); \n $1 \n echo(`'); 8 9 template = 'echo(`' + template + '`);'; 10 11 var script = 12 `(function parse(data){ 13 var output = ""; 14 15 function echo(html){ 16 output += html; 17 } 18 19 ${ template } 20 21 return output; 22 })`; 23 24 return script; 25}

compile函数的用法如下。

1var parse = eval(compile(template)); 2div.innerHTML = parse({ supplies: [ "broom", "mop", "cleaner" ] }); 3// <ul> 4// <li>broom</li> 5// <li>mop</li> 6// <li>cleaner</li> 7// </ul>

标签模板

模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。

1alert`123` 2// 等同于 3alert(123)

标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。

但是,如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。

1var a = 5; 2var b = 10; 3 4tag`Hello ${ a + b } world ${ a * b }`; 5// 等同于 6tag(['Hello ', ' world ', ''], 15, 50);

上面代码中,模板字符串前面有一个标识名tag,它是一个函数。整个表达式的返回值,就是tag函数处理模板字符串后的返回值。

函数tag依次会接收到多个参数。

1function tag(stringArr, value1, value2){ 2 // ... 3} 4 5// 等同于 6 7function tag(stringArr, ...values){ 8 // ... 9}

tag函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。

tag函数的其他参数,都是模板字符串各个变量被替换后的值。由于本例中,模板字符串含有两个变量,因此tag会接受到value1value2两个参数。

tag函数所有参数的实际值如下。

  • 第一个参数:['Hello ', ' world ', '']
  • 第二个参数: 15
  • 第三个参数:50

也就是说,tag函数实际上以下面的形式调用。

tag(['Hello ', ' world ', ''], 15, 50)

我们可以按照需要编写tag函数的代码。下面是tag函数的一种写法,以及运行结果。

1var a = 5; 2var b = 10; 3 4function tag(s, v1, v2) { 5 console.log(s[0]); 6 console.log(s[1]); 7 console.log(s[2]); 8 console.log(v1); 9 console.log(v2); 10 11 return "OK"; 12} 13 14tag`Hello ${ a + b } world ${ a * b}`; 15// "Hello " 16// " world " 17// "" 18// 15 19// 50 20// "OK"

下面是一个更复杂的例子。

1var total = 30; 2var msg = passthru`The total is ${total} (${total*1.05} with tax)`; 3 4function passthru(literals) { 5 var result = ''; 6 var i = 0; 7 8 while (i < literals.length) { 9 result += literals[i++]; 10 if (i < arguments.length) { 11 result += arguments[i]; 12 } 13 } 14 15 return result; 16} 17 18msg // "The total is 30 (31.5 with tax)"

上面这个例子展示了,如何将各个参数按照原来的位置拼合回去。

passthru函数采用rest参数的写法如下。

1function passthru(literals, ...values) { 2 var output = ""; 3 for (var index = 0; index < values.length; index++) { 4 output += literals[index] + values[index]; 5 } 6 7 output += literals[index] 8 return output; 9}

“标签模板”的一个重要应用,就是过滤HTML字符串,防止用户输入恶意内容。

1var message = 2 SaferHTML`<p>${sender} has sent you a message.</p>`; 3 4function SaferHTML(templateData) { 5 var s = templateData[0]; 6 for (var i = 1; i < arguments.length; i++) { 7 var arg = String(arguments[i]); 8 9 // Escape special characters in the substitution. 10 s += arg.replace(/&/g, "&amp;") 11 .replace(/</g, "&lt;") 12 .replace(/>/g, "&gt;"); 13 14 // Don't escape special characters in the template. 15 s += templateData[i]; 16 } 17 return s; 18}

上面代码中,sender变量往往是用户提供的,经过SaferHTML函数处理,里面的特殊字符都会被转义。

1var sender = '<script>alert("abc")</script>'; // 恶意代码 2var message = SaferHTML`<p>${sender} has sent you a message.</p>`; 3 4message 5// <p>&lt;script&gt;alert("abc")&lt;/script&gt; has sent you a message.</p>

标签模板的另一个应用,就是多语言转换(国际化处理)。

1i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!` 2// "欢迎访问xxx,您是第xxxx位访问者!"

模板字符串本身并不能取代Mustache之类的模板库,因为没有条件判断和循环处理功能,但是通过标签函数,你可以自己添加这些功能。

1// 下面的hashTemplate函数 2// 是一个自定义的模板处理函数 3var libraryHtml = hashTemplate` 4 <ul> 5 #for book in ${myBooks} 6 <li><i>#{book.title}</i> by #{book.author}</li> 7 #end 8 </ul> 9`;

除此之外,你甚至可以使用标签模板,在JavaScript语言之中嵌入其他语言。

1jsx` 2 <div> 3 <input 4 ref='input' 5 onChange='${this.handleChange}' 6 defaultValue='${this.state.value}' /> 7 ${this.state.value} 8 </div> 9`

上面的代码通过jsx函数,将一个DOM字符串转为React对象。你可以在Github找到jsx函数的具体实现

下面则是一个假想的例子,通过java函数,在JavaScript代码之中运行Java代码。

1java` 2class HelloWorldApp { 3 public static void main(String[] args) { 4 System.out.println(“Hello World!”); // Display the string. 5 } 6} 7` 8HelloWorldApp.main();

模板处理函数的第一个参数(模板字符串数组),还有一个raw属性。

1console.log`123` 2// ["123", raw: Array[1]]

上面代码中,console.log接受的参数,实际上是一个数组。该数组有一个raw属性,保存的是转义后的原字符串。

请看下面的例子。

1tag`First line\nSecond line` 2 3function tag(strings) { 4 console.log(strings.raw[0]); 5 // "First line\\nSecond line" 6}

上面代码中,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方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。

1String.raw`Hi\n${2+3}!`; 2// "Hi\\n5!" 3 4String.raw`Hi\u000A!`; 5// 'Hi\\u000A!'

如果原字符串的斜杠已经转义,那么String.raw不会做任何处理。

1String.raw`Hi\\n` 2// "Hi\\n"

String.raw的代码基本如下。

1String.raw = function (strings, ...values) { 2 var output = ""; 3 for (var index = 0; index < values.length; index++) { 4 output += strings.raw[index] + values[index]; 5 } 6 7 output += strings.raw[index] 8 return output; 9}

String.raw方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。

String.raw方法也可以作为正常的函数使用。这时,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组。

1String.raw({ raw: 'test' }, 0, 1, 2); 2// 't0e1s2t' 3 4// 等同于 5String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);

模板字符串的限制

前面提到标签模板里面,可以内嵌其他语言。但是,模板字符串默认会将字符串转义,导致无法嵌入其他语言。

举例来说,标签模板里面可以嵌入 LaTEX 语言。

1function latex(strings) { 2 // ... 3} 4 5let document = latex` 6\newcommand{\fun}{\textbf{Fun!}} // 正常工作 7\newcommand{\unicode}{\textbf{Unicode!}} // 报错 8\newcommand{\xerxes}{\textbf{King!}} // 报错 9 10Breve over the h goes \u{h}ere // 报错 11`

上面代码中,变量document内嵌的模板字符串,对于 LaTEX 语言来说完全是合法的,但是 JavaScript 引擎会报错。原因就在于字符串的转义。

模板字符串会将\u00FF\u{42}当作 Unicode 字符进行转义,所以\unicode解析时报错;而\x56会被当作十六进制字符串转义,所以\xerxes会报错。也就是说,\u\x在 LaTEX 里面有特殊含义,但是 JavaScript 将它们转义了。

为了解决这个问题,现在有一个提案,放松对标签模板里面的字符串转义的限制。如果遇到不合法的字符串转义,就返回undefined,而不是报错,并且从raw属性上面可以得到原始字符串。

1function tag(strs) { 2 strs[0] === undefined 3 strs.raw[0] === "\\unicode and \\u{55}"; 4} 5tag`\unicode and \u{55}`

上面代码中,模板字符串原本是应该报错的,但是由于放松了对字符串转义的限制,所以不报错了,JavaScript引擎将第一个字符设置为undefined,但是raw属性依然可以得到原始字符串,因此tag函数还是可以对原字符串进行处理。

注意,这种对字符串转义的放松,只在标签模板解析字符串时生效,不是标签模板的场合,依然会报错。

let bad = `bad escape sequence: \unicode`; // 报错
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

一篇文章带你了解JavaScript日期

日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用

ES6学习笔记(3)

参考书《ECMAScript6入门》http://es6.ruanyifeng.com/字符串的扩展ES6之前只能识别\\u0000\\uFFFF之间的字符,超过此范围,识别会出错;ES6弥补了这个错误ES6扩展的新方法codePointAt"𠮷".CodePointAt(0)//返回超过\\u00