【JS红宝书⁰⁷】数据类型之String类型

449 阅读8分钟

引子

本文为【JSRedBook】中数据类型的篇章, 主要讲述 ECMAScript 的 6 种简单数据类型(原始类型)中的其中一种:String类型;主要说明六个方面:String定义字符串字面量字符串的特点转换字符串模板字面量常用模板字符串

String 类型

String(字符串)数据类型表示零或多个 16 位 Unicode 字符序列

字符串可以使用双引号(")、单引号(')或反引号(`)包裹标示

基础定义

请看如下例子:

 const one = "Double quotess" // ✅
 const two = 'apostrophe'  // ✅
 const three = `backticks`  // ✅
 ​
 const four = "test'  //报错

如上述例子,前三个代码都是合法的,只有最后一个报错;因为必须按照ECMAScript 语法的规定:以某种引号作为字符串开头,必须仍然以该种引号作为字符串结尾,不然就会报错

字符字面量

字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符

字面量含义
\n换行
\t制表
\b退格
\r回车
\f换页
\反斜杠(\)
'单引号(')
"双引号(")
\ `反引号(`)

另外还有两个字符字面量,分别是:

\xnn :以十六进制编码 nn 表示的字符(其中 n 是十六进制数字 0~F),如 \x41 等于"A"

\unnnn以十六进制编码 nnnn 表示的 Unicode 字符(其中 n 是十六进制数字 0~F),例如\u03a3 等于希腊字符"Σ"

字符字面量小知识

这些字符字面量可以出现在字符串中的任意位置,且可以作为单个字符被解释:

 let text = "This is the letter sigma: \u03a3."; 

在这个例子中,即使包含 6 个字符长的转义序列,变量 text 仍然是 28 个字符长;

因为转义序列表示一个字符,所以只算一个字符的长度

字符串的特点

ECMAScript 中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了;要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量。

如下所示:

 let lang = "Java"; 
 lang = lang + "Script";   // JavaScript

解析: 整个过程首先会分配一个足够容纳 10 个字符的空间,然后填充上"Java"和"Script",最后销毁原始的字符串"Java"和字符串"Script",因为这两个字符串都没有用了。

所有处理都是在后台发生的,而这也是一些早期的浏览器(如 Firefox 1.0 之前的版本和 IE6.0)在拼接字符串时非常慢的原因,这些浏览器在后来的版本中都有针对性地解决了这个问题。

转换为字符串

转换为字符串有两方法 toString()String(),还有一个加号操作符 "+"

toString()方法

toString()用于返回当前值的字符串等价物,几乎所有值都有这个方法。 比如:

 let age = 11; 
 age.toString(); // "11" 
 let found = true; 
 found.toString(); // "true" 

toString()方法规范

  • toString() 方法可见于数值、布尔值、对象和字符串值;
  • 字符串值的 toString() 方法只是用于简单地返回自身的一个副本
  • null 和 undefined 值没有 toString()方法
  • 大多情况下toString()不接收任何参数,但在对数值调用这个方法时,toString()可以接收一个底数参数,即以什么底数来输出数值的字符串表示

toString()方法进制使用

默认情况下,toString()返回数值的十进制用字符串表示;而通过传入参数,可以得到数值的二进制、八进制、十六进制,或者其他任何有效基数的字符串,如下:

 let num = 10; 
 num.toString(); // "10" 
 num.toString(2); // "1010" 
 num.toString(8); // "12" 
 num.toString(10); // "10" 
 num.toString(16); // "a"

这个例子展示了传入底数参数时,toString() 输出的字符串值也会随之改变;数值 10 可以输出为任意数值格式;

String()方法

默认情况下(不传参数),使用 toString() 的输出与传入参数 10 得到的结果相同,如果你不确定一个值是不是 null 或 undefined,可以使用 String() 转型函数,它始终会返回表示相应类型值的字符串

String()方法规范

  • 如果值有 toString()方法,则调用该方法(不传参数)并返回结果。
  • 如果值是 null,返回"null"。
  • 如果值是 undefined,返回"undefined"。

下面看几个例子:

 let value1 = 10; 
 let value2 = true; 
 let value3 = null; 
 let value4; 
 ​
 String(value1); // "10" 
 String(value2); // "true" 
 String(value3); // "null"
 String(value4); // "undefined" 

这里展示了将 4 个值转换为字符串的情况:一个数值、一个布尔值、一个 null 和一个 undefined。数值和布尔值的转换结果与调用 toString() 相同;因为 null 和 undefined 没有 toString()方法,所以 String()方法就直接返回了这两个值的字面量文本;

加号操作符

用加号操作符给一个值加上一个空字符串""也可以将其转换为字符串,举例如下:

 let auto = 996
 let autoX = auto + ""  // "996"

这里不花太多时间说明,下次会详细讲操作符与运算符 (●'◡'●)

模板字面量

ECMAScript 6 新增了使用模板字面量定义字符串的能力;

与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串,如下:

 let a = 'first line\nsecond line'; 
 let b = `first line 
         second line`; 
 console.log(a); 
 // first line 
 // second line 
 ​
 console.log(b); 
 // first line 
 // second line 
 ​
 console.log(a === b); // true 
常用模板字符串

顾名思义,模板字面量在定义模板时特别有用,比如下面这个 HTML 模板:

 let pageHTML = ` <div> 
 <a href="#"> 
 <span>Jake</span> 
 </a> 
 </div>`; 
错误示范

由于模板字面量会保持反引号内部的空格,因此在使用时要格外注意。

格式正确的模板字符串看起来可能会缩进不当,如下:

 // 这个模板字面量在换行符之后有 25 个空格符
 let myTemplateLiteral = `
                 first line 
                 second line`; 
 console.log(myTemplateLiteral.length); // 56 
 console.log(myTemplateLiteral[0] === '\n'); // true 

如上,本身规范写法的first line second line只有25个长度,但是中间加入了31个空格导致长度的增加,在某些情况下如判断可能会错判;再者,首行换行导致识别出来的是换行符,从而同等与 '\n'

字符串插值

模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。

技术上讲,模板字面量不是字符串,而是一种特殊的 JavaScript 句法表达式,只不过求值后得到的是字符串,模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最接近的作用域中取值。

常规写法

字符串插值通过在${}中使用一个 JavaScript 表达式实现,如下:

 let value = 5; 
 let exponent = 'second'; 
 let test = `${value} to the ${exponent} power is ${value * value}`; 
 console.log(test); // 5 to the second power is 25 

所有插入的值都会使用 toString()强制转型为字符串,而且任何 JavaScript 表达式都可以用于插值

其他写法

嵌套的模板字符串无须转义:

 console.log(`Hello, ${`Outside`} !`); // Hello, Outside!

将表达式转换为字符串时会调用 toString()

 let foo = { toString:() => 'Futrue' };
 console.log(`Hello,${ foo }`); // Hello,Futrue

在插值表达式中可以调用函数和方法:

 function capitalize(word) { 
  return `${ word[0].toUpperCase() }${ word.slice(1) }`; 
 } 
 ​
 console.log(`${ capitalize('hello') }, ${ capitalize('world') }!`); // Hello, World!

此外,模板也可以插入自己之前的值:

 let value = ''; 
 function append() { 
  value = `${value}abc` 
  console.log(value); 
 }
 ​
 append(); // abc 
 append(); // abcabc 
 append(); // abcabcabc 

模板字面量标签函数

标签函数(tag function)会接收被插值记号分隔后的模板和对每个表达式求值的结果,通过标签函数可以自定义插值行为

标签函数本身是一个常规函数,通过前缀到模板字面量来应用自定义行为;标签函数接收到的参数依次是原始字符串数组和对每个表达式求值的结果,返回值是对模板字面量求值得到的字符串。

基本定义

参数: 标签函数strings(第一个参数)是以${}分割的数组,后面其余的参数对应相对的值

这部分有点难懂,我们通过一个例子来理解:

 let a = 6; 
 let b = 9; 
 function simpleTag(strings, aValExpression, bValExpression, sumExpression) { 
  console.log(strings); 
   //  ["", " + ", " = ", ""] 
  console.log(aValExpression);  // 6
  console.log(bValExpression); // 9
  console.log(sumExpression); // 15  
  return 'foobar'; 
 } 
 ​
 let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`;
 ​
 console.log(taggedResult); // "foobar" 

剩余操作符

因为表达式参数的数量是可变的,所以通常应该使用剩余操作符(rest operator)将它们收集到一个数组中:

 let a = 6; 
 let b = 9; 
 ​
 function simpleTag(strings, ...expressions) {
  console.log(strings); 
  for(const expression of expressions) { 
     console.log(expression); 
  } 
  return 'foobar'; 
 } 
 ​
 let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`; 
 // ["", " + ", " = ", ""] 
 // 6 
 // 9 
 // 15 
 ​
 console.log(taggedResult); // "foobar" 

特殊写法

对于有 n 个插值的模板字面量,传给标签函数的表达式参数的个数始终是 n,而传给标签函数的第一个参数所包含的字符串个数则始终是 n+1;

如果你想把这些字符串和对表达式求值的结果拼接起来作为默认返回的字符串,可以这样做:

 let a = 6; 
 let b = 9; 
 function zipTag(strings, ...expressions) { 
  return strings[0] +  expressions.map((e, i) => `${e}${strings[i + 1]}`) .join(''); 
 } 
 ​
 let taggedResult = zipTag`${ a } + ${ b } = ${ a + b }`; 
 ​
 console.log(taggedResult); // "6 + 9 = 15" 

这部分有点超纲,涉及到 数组方法map与箭头函数 (可以省略)

原始字符串

String.raw() 是一个模板字符串的标签函数,用来获取一个模板字符串的原始字符串的

通过使用String.raw(),让模板字面量也能直接获取原始的模板字面量内容(如换行符或 Unicode 字符),而不是被转换后的字符表示

常规写法

使用默认的 String.raw 标签函数,如下:

  • Unicode 示例
 /*  \u00A9 是版权符号 */
 ​
 console.log(`\u00A9`); // © 
 console.log(String.raw`\u00A9`); // \u00A9 
 ​
  • 换行符示例
 ​
 console.log(`first line\nsecond line`); 
 // first line 
 // second line 
 console.log(String.raw`first line\nsecond line`); // "first line\nsecond line" 
 ​
 // 但这一点对实际的换行符来说是不行的(如空格,tab),因为它们不会被转换成转义序列的形式

标签函数写法

也可以通过标签函数的第一个参数,即字符串数组的 .raw 属性取得每个字符串的原始内容,如下:

 function printRaw(strings) { 
     for (const string of strings) { 
         console.log(string); 
         // © 
         //(换行符)
     } 
 ​
     for (const rawString of strings.raw) { 
         console.log(rawString); 
         // \u00A9 
         // \n 
     } 
 } 
 ​
 printRaw`\u00A9${ 'and' }\n`; 

总结

String 数据类型表示为字符串或一个字符序列

  • 遇到需要转义的字符可以使用字符串字面量,常用的有\n\f
  • ECMAScript中的字符串是不可变的,可以使用拼接的方法改变
  • 使用toString的方法可以使其他不为字符串的类型值转换为字符串(除了null与undefined)
  • 使用String方法也可以转换字符串,不同的是对于null与undefined会返回本身
  • 可以使用模板字符串替换常规字符串定义方法去使用,优点是会保留换行符,建议搭配字符串插值一起使用
  • 模板字面量标签函数用来自定义模板字面量中的插值的行为
  • 使用String.raw()可以让模板字面量也能直接获取原始的模板字面量内容