javaScript 中的 String 对象用来表示和操作字符序列。字符串对于保存可以以文本形式表示的数据。并且有一些常用的字符串方法,用来查看字符串的长度,用来寻找字符串的位置等。
const string1 = "This is a string";
typeof string1 // string
const objStr = new String('This is a obj string');
typeof objStr. // object
字符串可以使用单引号和双引号两种,他们的处理方式相同,或者使用反引号字符`,来指定一个模版字符串(可用于插入表达式)。
在大多情况下,字符串原语和字符串对象是可以互相使用的。在使用一个字符串的对象的时候,JavaScript 自动将原始类型转换为 String 对象,因此可以使 String 基本类型调用字符串的方法。JavaScript 将自动包装字符串原始并调用方法或执行属性查找。并且在 String 期望是使用原始字符串时,会自动的转换为对应的原始类型。这个过程是不需要我们手动来进行的,是内部做了处理。
let string1 = 'foo';
let objStr = new String(string1);
objStr.valueOf(); // 调用 valueOf 方法, 可以手动将包装对象转换为对应的基础类型。
1. 我们可以使用字符串中的方法, charAt() 方法(后文中会详细介绍)。
'cat'.charAt(1); // ‘a’ 就是访问 cat 字符串的第一个字符。
2. 我们可以使用另一种在 ECMAScript 5 中引入,将字符串视为类数组对象,其中单个字符对应于数字索引。
'cat'[1]; // ‘a’ 通过下标来访问字符串的第一位字符。
\0 |
空字符(U+0000 NULL) |
\' |
单引号 (U+0027 撇号) |
\" |
双引号(U+0022 引号) |
\\ |
反斜杠 (U+005C REVERSE SOLIDUS) |
\n |
换行符(U+000A 换行符;LF) |
\r |
回车(U+000D 回车;CR) |
\v |
垂直制表符(U+000B 行制表) |
\t |
选项卡(U+0009 字符列表) |
\b |
退格(U+0008 退格) |
\f |
换页 (U+000C 换页) |
\uXXXX ...其中XXXX 正好是范围内的 4 个十六进制数字0000 – FFFF ; 例如,\u000A 与\n (LINE FEED)相同;\u0021 是“ ! ” |
U+0000 和之间的 Unicode 代码点U+FFFF (Unicode 基本多语言平面) |
\u{X} … \u{XXXXXX} …其中X …XXXXXX 是范围内的 1–6 个十六进制数字0 – 10FFFF ;例如,\u{A} 与\n (LINE FEED)相同;\u{21} 是“ ! ” |
U+0000 和之间的 Unicode 代码点U+10FFFF (整个 Unicode) |
\xXX ...其中XX 正好是范围内的 2 个十六进制数字00 – FF ; 例如,\x0A 与\n (LINE FEED)相同;\x21 是“ ! ” |
U+0000 和之间的 Unicode 代码点U+00FF (Basic Latin 和 Latin-1 Supplement 块;等效于 ISO-8859-1) |
当我们遇到很长的字符串时,你希望可以转换为多行,并且可以正常运行,还可以随时换行。
1. 我们可以使用 + 运算符来拼接到一起,来展示我们的字符串。
let longString = "This is a very long string which needs " +
"to wrap across multiple lines because " +
"otherwise my code is unreadable."
2. 我们可以使用 \ 来在每一行最后来指示在下一行继续。
let longString = "This is a very long string which needs \
to wrap across multiple lines because \
otherwise my code is unreadable."
3. 下文中还会提到一种更加好的一种表示方法。
String() | 转换为 |
字符串 | 返回字符串 |
数字 | 返回字符串形式number |
null | 'null' |
undefined | 'undefined' |
true | 'true' |
false | 'false' |
如果是对象的话,传入 string 上下文,会先调用 toString(),如果得到基本类型,就直接返回,否则就继续调用 valueOf(),如果得到基本类型,就返回,否则就抛出错误。
1. String.fromCharCode
fromCharCode() 方法可以将字符编码转换为字符串。也算是一种定义字符串的形式。参数可以是任意个,UTF-16编码单元的数字序列。范围在 0~65535(是Unicode 的基本平面的码点),十六进制表示是 0x0000 ~ 0xFFFF,也可以用转义序列 \u0000 ~ \uFFFF 来表示。如果数字大于 0xFFFF,就会被剪短,不做有效检查。
String.fromCharCode(65, 66, 67); // 'ABC'
String.fromCharCode(0x2014); // '—'
String.fromCharCode(0x12014); // '—' // 超出范围,1会被截掉,剩下 0x2014
String.fromCharCode(8212); // '—' 这就是在 0 ~ 65535之间的十进制,转为16进制0x2014
在UTF-16这篇文章中,我们将结果关于 Unicode 的实现方法之一 UTF-16,我们也知道了对于在UTF-16中,经常用到的字符放到了基本多语言平面 (BMP),仅占可寻址 Unicode 代码点总数的 1/17。65536
( 0x010000
) 到1114111
( 0x10FFFF
)范围内的其余代码点称为补充字符。
fromCharCode 只使用于 16位值的。所以它只能表示0x0000~0xFFFF之间的字符,剩下的表示不到的字符,将使用代理对才能返回补充字符。1048576个码点正好代表了 2^20个字符(可以自己算一次2^20 === 1048576)。在 0 ~ 65536 之间正好留出了 两个 2^10 个字符,也就是
U+D800 ~ U+DBFF,另一个是 U+DC00 ~ U+DFFF。我们使用代理的方式,将 2^20 分为两个 2^10 填补。就可以将补充字符通过基本字符表示出来。
2^20 = 1048576; //
补充字符
1114111 - 1048576 = 65535; // 基础平面中的字符。
2^10 = 1024;
DFFF - D800 = 1024 = 2^10;
DFFF - DC00 = 1024 = 2^10;
然后将补充字符 分成两半,前10位映射在U+D800到U+DBFF, 后10位映射在U+DC00到U+DFFF。
注意:不是说 2^10 + 2^10 = 2^20,而是以位来分。
H = Math.floor((c - 0x10000) / 0x400) + 0xD800
高位H, 我们先计算超出基本字符的部分,然后/0x400(2^10), 保留前面十位,加上
U+D800
对应的二进制数为 1101100000000000,结果转为十六进制。L = (c - 0x10000) % 0x400 + 0xDcC00
低位L, 我们先计算超出基本字符的部分,然后%0x400 (2^10), 保留后十位,加上0xDC00 对应的二进制数为 1101110000000000,结果转为十六进制。
String.fromCharCode(0xD83C, 0xDF03); // 代表补充字符 U+1F303 ''' String.fromCharCode(55356, 57091); // "\uD83C\uDF03". 代表补充字符 U+1F303 ''' String.fromCharCode(0x61, 0xD834, 0xDF07); // "a\uD834\uDF07". 'a' 如果给出无效的 Unicode 代码点,使用fromCharCode的话,会返回\x00 String.fromCharCode(NaN); // '\x00' String.fromCharCode(Infinity); // '\x00' String.fromCharCode('3.9'); // '\x03' String.fromCharCode(2e2); // 'È' 参数会带有 Number 上下文, 会转为数字。并且小数的话会进行向下取整。
虽然有补充代码点值之间的数学关系,表示它需要进行计算,使用替代值经过计算每次都是需要的。因为它是不能直接识别补充字符的,必须经还映射代理点的方式,重点是每次都要进行额外步骤的查找。有一个更加简便的静态方法 String.fromCodePoint() 可以用实际的代码点来直接表示补充字符。
fromCodePoint() 方法可以使用指定的代码点创建字符串。它和 String.fromCharCode 相似,但又有所不同。
参数可以是多个,但是参数类型却不同,可以使用0x0000~0x10FFFF的码点,都可以直接使用,不会发生截取。
String.fromCodePoint(42); // "*"
String.fromCodePoint(65, 90); // "AZ"
String.fromCodePoint(0x404); // "\u0404" == "Є"
String.fromCodePoint(0x2F804); // "\uD87E\uDC04"
String.fromCodePoint(194564); // "\uD87E\uDC04"
String.fromCodePoint(0x1D306, 0x61, 0x1D307); // "\uD834\uDF06a\uD834\uDF07"
如果给出无效的Unicode 代码点,会抛出异常。RangeError.
String.fromCodePoint('_'); // RangeError
String.fromCodePoint(Infinity); // RangeError
String.fromCodePoint(-1); // RangeError
String.fromCodePoint(3.14); // RangeError
String.fromCodePoint(3e-2); // RangeError
String.fromCodePoint(NaN); // RangeError
由于String.fromCodePoint()
方法已添加到 ECMAScript 2015 中,所以还有很多版本不支持此方法,所以我们需要填补一下这个方法。
polyfill:
String.fromCodePoint = (function(stringFromCharCode) {
var codeUnits = [], codeLen = 0, result = "";
for (var index=0, len = arguments.length; index !== len; ++index) {
if (!(codePoint < 0x10FFFF && (codePoint>>>0) === codePoint))
throw RangeError("Invalid code point: " + codePoint);
if (codePoint <= 0xFFFF) { // BMP
codeLen = codeUnits.push(codePoint);
} else {
codePoint -= 0x10000;
codeLen = codeUnits.push(
(codePoint >> 10) + 0xD800, // highSurrogate
(codePoint % 0x400) + 0xDC00 // lowSurrogate
);
}
if (codeLen >= 0x3fff) {
result += stringFromCharCode.apply(null, codeUnits);
codeUnits.length = 0;
}
}
return result + stringFromCharCode.apply(null, codeUnits);
}
})(String.fromCharCode)
比较二者的区别。
返回值 | 给出无效代码点 | 代码点范围 | 参数的默认值 | |
fromCharCode | (字符串) | 会经过强制转换,返回\x00 | 只接受基本平面字符,不然就会进行切割。 | 一样,都为0,返回 '' |
fromCodePoint |
(字符串) | 不会进行强制转换,返回RangeError | 可以接受任意 Unicode 码点。 | 一样,都为0,返回 '' |
length属性,返回字符串的长度,以 UTF-16 代码单位表示。上文中我们已经了解了使用UTF-16编码来表示字符,基础字符我们使用两个字节(16位)来表示,对于补充字符我们使用四个字节(32位)来表示。所以我们应该猜到,length 属性并不会就以个数来返回字符串的长度,按我的理解来说,应该是按照字节来表示,一个长度表示为两个字节,所以像 ASCII 这种编码,英文来说占有一个长度,表示两个字节。中文这种属于补充字符,是两个长度,占有四个字节。(JavaScript 中虽然没有收集长度的字节,但我们完全可以自己写一个,只要是在 0 ~ 65536 只是收集两字节,否则就收集四个字节
)。
'a'.length; // 1
'\u12345'.length; // 补充字符. 2
''.length; // 2
''.length; // 0 空字符串
String.length; // 1 它是String函数的元数,与字符串的长度无关。
因为 length 是表示代码单元。而不是准确的字符个数,所以当我们只是想要知道个数,我们需要自己来实现。
function getCharacterLength (str) {
return [...str].length;
}
会对字符串进行拆分放入数组中,我们算数组的个数就不会发生length读取的是代码单元的问题。
字符串是不可变的,所以我们不能修改字符串的长度,因为基本数据是存储在栈中的,所以一经定义,就不可以改变大小长度,是只读的字段,如果你想要修改字符串,只能销毁重新赋值。
let string = 'hello';
string.length; // 5
string.length = 3;
string.length; // 5
因为实例的方法比较多,我们放到下一篇,重点讲解字符串的实例方法(包括实例方法的具体实现)。