anjia / blog

博客,积累与沉淀
106 stars 4 forks source link

数据类型 | String #85

Open anjia opened 2 years ago

anjia commented 2 years ago

上篇文章 #84 介绍了原始数据类型 Undefined, Null 和 Boolean 及其使用注意事项,本文将介绍第4种原始类型 String。

通用知识

在计算机编程中,字符串通常是一个字符序列,可以是字面量,也可以是变量。后者可能会允许修改元素和改变长度,也可能是固定的(在创建之后)。字符串是一种数据类型,通常被实现为 bytes/characters/code units 的数组,存储着元素序列,元素通常是字符且会使用某种字符编码。字符串也可以表示更通用的数组/其他序列/列表等数据类型和结构。

用“数组”实现是为了快速访问单个单元的字符和子字符串,也有个别编程语言是用链表来实现的(比如 Haskell)。

字符串通常由字符组成。可用于存储人类可读的数据,如句子、按字母顺序排列的数据列表

根据使用的编程语言和其提供的具体数据类型,声明为字符串的变量在内存中的存储,可能是静态分配预留了最大长度,也可能是动态分配以确保元素的数量是可变的。

字符串是一种非常重要且有用的数据类型,几乎所有的编程语言都实现了它。在不同的编程语言里,字符串可能是原始类型,也可能是复合类型。当字符串以字面量的形式出现在源代码中的时候,它被称为字符串字面量或匿名字符串。

在学习字符串时,可重点关注:

  1. 长度限制:固定长度、可变长度
  2. 编码方式:字符集+字符编码
  3. 实现方式: mutable/immutable, [bytes/characters/code units]
  4. 安全问题
  5. 字面量

String 类型

在 JavaScript 里,String 类型用来表示文本数据。它是一组 16 位无符号整数值的“元素”。字符串里的每个元素都占了一个位置,我们可以通过下标来访问每个元素,下标从0开始。字符串的长度就是它里面元素的个数。如下:

let foo = 'JavaScript';
foo[0];  // "J"
foo[1];  // "a"
foo.length;  // 10

对象包装器

在 JavaScript 中,String 对象是 String 原始值的对象包装器,用来表示和操作字符序列。

  1. 构造器
    • new String() 返回对象。搭配 new 关键字作为构造函数用
    • String() 返回字符串。直接当函数用,是执行类型转换(此方式更有用)
  2. 静态方法
    • String.fromCharCode() 传 Unicode 值序列
    • String.fromCodePoint() 传代码点序列
    • String.raw() 传原始模板字符串
  3. 实例属性
    • String.prototype.length 只读
  4. 实例方法 String.prototype.
实例方法 说明
valueOf()
toString()
at()
charAt()
charCodeAt()
codePointAt()
取单个字符,传下标
indexOf()
lastIndexOf()
取下标,传字符串
startsWith()
endsWith()
includes()
是否以指定字符串开始/结束/包含
concat() 拼接
split() 变数组
slice() 提取子串,传起始下标
substring() 提取子串,传起始下标
search()
match(), matchAll()
replace(), replaceAll()
正则表达式
toLowerCase()
toUpperCase()
toLocaleLowerCase()
toLocaleUpperCase()
大小写
normalize() 返回 Unicode 正规形式
repeat() 传重复次数
trim()
trimStart()
trimEnd()
去首尾空格
padEnd()
padStart()
前后对齐
localeCompare() 比较
@@iterator() 迭代器

题外话:substr() 已不推荐使用

常见操作

创建字符串

创建字符串原始值,可以使用字符串字面量和函数。如下:

let str1 = 'hello world';  // 单引号,字面量
let str2 = "hello world";  // 双引号,字面量
let str3 = `hello world`;  // 字符串模板,字面量

let str4 = String('hello world');  // 函数

创建字符串对象,可以使用字符串对象的构造器。如下:

let str5 = new String('hello world');  // new
console.log(str5.valueOf());  // hello world

原始值和对象

在大多数情况下,字符串原始值和字符串对象是可以互换使用的。JavaScript 会自动把字符串原始值转成对象,以便在字符串原始值上使用字符串对象的方法。

但是,字符串原始值和字符串对象,还是有些区别的。

区别一:typeof 的结果不同

typeof 'hello' === 'string';                // true
typeof (new String('hello')) === 'object';  // true

区别二:eval() 的结果不同

let str1 = '1+2';
let str2 = new String('1+2');
console.log(eval(str1));  // 会输出数字 3
console.log(eval(str2));  // 会输出字符串 '1+2'  

当然,可以通过手动调用 valueOf() 来“修复”。如下:

console.log(eval(str2.valueOf()));  // 此时会输出数字 3

访问单个字符

方法一:使用 charAt()

console.log('hello'.charAt(1));  // "e"

方法二:使用 []。这是在 ES5 里引入的,它把字符串视为类数组(array-like)对象,然后单个字符对应一个数字索引。

console.log('hello'[1]);  // "e"

当使用[]进行字符访问的时候,尝试删除或者重新赋值是不会生效的,因为所涉及的属性是不可写、不可配置的。

字符串比较

可以用运算符>< 以及实例方法 localeCompare()

这里需要注意的是,用==运算符比较的两个字符串是大小写敏感的。如果不想区分大小写,可以先都转成大写再用===。如下:

function isEqual(str1, str2){
    return str1.toUpperCase() === str2.toUpperCase();
}

isEqual('javascript', 'JavaScript');  // 会返回 true

这里用了toUpperCase()而不是toLowerCase(),是考虑到某些 UTF-8 字符转换的问题。

类型转换

.toString()相比,String()更可靠,因为它还适用于 undefined, null 和 symbol。

注意事项

长字符串字面量

方法一:使用操作符 +。如下:

let longStr1 = 'This is a very very long string ' +
               'which needs to wrap across multiple lines ' +
               'because otherwise your code is unreadable.'

方法二:在行尾加 \,以表示该字符串还会在下一行继续。如下:

let longStr2 = 'This is a very very long string \
which needs to wrap across multiple lines \
because otherwise your code is unreadable.'

注意:在\之后不能有空格、注释和其它任何字符(换行符除外),否则它就不工作了。

输出多行字符串

可使用模板字符串,它支持直接换行。

console.log(`This is a very very long string 
which needs to wrap across multiple lines 
because otherwise your code is unreadable.`);

源代码里的任何字符(包括换行符)都是模板字面量的一部分,所以上面的字符串里是含有字符\n的。

慎用字符串模拟数组

在实际使用中,我们可能会用字符串来模拟数组/列表,但这么做是有潜在风险的。

要么分隔符可能就是列表里的元素本身,要么就得挑个特殊的字符来当分隔符。前者可能会导致列表不工作,后者不仅要约定特殊字符而且还会增加后续的维护成本。

所以,尽量用字符串表示文本数据。

字符集和编码方式

话题较为独立,后续单独介绍。

主要参考