沈阳做网站优化的公司google网站增加关键词
感谢点赞、关注和收藏!
原始值包装类型
为了方便操作原始值,ECMAScript 提供了 3 种特殊的引用类型:Boolean、Number 和 String。每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法。来看下面的例子:
let s1 = "some text";
let s2 = s1.substring(2);
我们知道,原始值本身不是对象,因此逻辑上不应该有方法。而实际上这个例子又确实按照预期运行了。这是因为后台进行了很多处理,从而实现了上述操作。具体来说,当第二行访问 s1 时,是以读模式访问的,也就是要从内存中读取变量保存的值。在以读模式访问字符串值的任何时候,后台都会执行以下 3 步:
- 创建一个 String 类型的实例;
- 调用实例上的特定方法;
- 销毁实例。
这种行为可以让原始值拥有对象的行为。对布尔值和数值而言,以上 3 步也会在后台发生,只不过
使用的是 Boolean 和 Number 包装类型而已。
引用类型与原始值包装类型的主要区别在于对象的生命周期。在通过 new 实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。这意味着不能在运行时给原始值添加属性和方法。比如下面的例子:
let s1 = "some text";
s1.color = "red";
console.log(s1.color); // undefined
应该在确实必要以显式地使用 Boolean、Number 和 String 构造函数创建原始值包装对象时再这么做,否则容易让开发者疑惑,分不清它们到底是原始值还是引用值。另外,Object 构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。比如:
let obj = new Object("some text");
console.log(obj instanceof String); // true
还有就是不要跟转型函数弄混了,使用new关键字时,调用的是构造函数,没有new时,是转型函数。
Boolean
Boolean 是对应布尔值的引用类型。Boolean 的实例会重写 valueOf()方法,返回一个原始值 true 或 false。toString()方法被调用时也会被覆盖,返回字符串"true"或"false"。
可能是由于布尔值一般就拿来比较,所以这个包装类型工作中很少用。而且使用包装类型反而不好去进行真假值对比,因为对包装类型使用 typeof 返回的是“Object”,自动转换真假值时,是true,但是说不准这个Boolean类型中到底是真还是假。
Number
Number 是对应数值的引用类型。与 Boolean 类型一样,Number 类型重写了 valueOf()、toLocaleString()和 toString()方法。valueOf()方法返回 Number 对象表示的原始数值,另外两个方法返回数值字符串。toString() 方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串,如下所示:
let num = 10;
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010"
console.log(num.toString(8)); // "12"
console.log(num.toString(10)); // "10"
console.log(num.toString(16)); // "a"
除了继承的方法,Number 类型还提供了几个用于将数值格式化为字符串的方法。toFixed() 方法返回包含指定小数点位数的数值字符串,如:
let num = 10;
console.log(num.toFixed(2)); // "10.00"
另一个用于格式化数值的方法是 toExponential(),返回以科学记数法(也称为指数记数法)表
示的数值字符串。与 toFixed()一样,toExponential()也接收一个参数,表示结果中小数的位数。
来看下面的例子:
let num = 10;
console.log(num.toExponential(1)); // "1.0e+1"
一般来说,这么小的数不用表示为科学记数法形式。如果想得到数值最适当的形式,那么可以使用 toPrecision()。toPrecision()方法会根据情况返回最合理的输出结果,可能是固定长度,也可能是科学记数法形式。这个方法接收一个参数,表示结果中数字的总位数(不包含指数)。来看几个例子:
let num = 99;
console.log(num.toPrecision(1)); // "1e+2"
console.log(num.toPrecision(2)); // "99"
console.log(num.toPrecision(3)); // "99.0"
本质上, toPrecision() 方法会根据数值和精度来决定调用 toFixed() 还是 toExponential()。为了以正确的小数位精确表示数值,这 3 个方法都会向上或向下舍入。

isInteger()方法与安全整数
ES6 新增了 Number.isInteger()方法,用于辨别一个数值是否保存为整数。有时候,小数位的 0 可能会让人误以为数值是一个浮点值:
console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.00)); // true
console.log(Number.isInteger(1.01)); // false
IEEE 754 数值格式有一个特殊的数值范围,在这个范围内二进制值可以表示一个整数值。这个数值范围从 Number.MIN_SAFE_INTEGER(-2^53 + 1)到 Number.MAX_SAFE_INTEGER(2^53 - 1)。对超出这个范围的数值,即使尝试保存为整数,IEEE 754 编码格式也意味着二进制值可能会表示一个完全不同的数值。为了鉴别整数是否在这个范围内,可以使用 Number.isSafeInteger()方法:
console.log(Number.isSafeInteger(-1 * (2 ** 53))); // false
console.log(Number.isSafeInteger(-1 * (2 ** 53) + 1)); // true
console.log(Number.isSafeInteger(2 ** 53)); // false
console.log(Number.isSafeInteger((2 ** 53) - 1)); // true
String
String 是对应字符串的引用类型。String 对象的方法可以在所有字符串原始值上调用。3个继承的方法 valueOf()、toLocaleString() 和 toString()都返回对象的原始字符串值。每个 String 对象都有一个 length 属性,表示字符串中字符的数量。
String 类型提供了很多方法来解析和操作字符串。我们来看看有哪些。
1. JavaScript 字符
JavaScript 字符串由 16 位码元(code unit)组成。对多数字符来说,每 16 位码元对应一个字符。
charAt()方法返回给定索引位置的字符,由传给方法的整数参数指定。具体来说,这个方法查找指定索引位置的 16 位码元,并返回该码元对应的字符。
JavaScript 字符串使用了两种 Unicode 编码混合的策略:UCS-2 和 UTF-16。对于可以采用 16 位编码的字符(U+0000~U+FFFF),这两种编码实际上是一样的。

使用 charCodeAt()方法可以查看指定码元的字符编码。这个方法返回指定索引位置的码元值,索
引以整数指定。比如:
let message = "abcde";
// Unicode "Latin small letter C"的编码是 U+0063
console.log(message.charCodeAt(2)); // 99
// 十进制 99 等于十六进制 63
console.log(99 === 0x63); // true
// fromCharCode()方法用于根据给定的 UTF-16 码元创建字符串中的字符。这个方法可以接受任意
// 多个数值,并返回将所有数值对应的字符拼接起来的字符串:
// Unicode "Latin small letter A"的编码是 U+0061
// Unicode "Latin small letter B"的编码是 U+0062
// Unicode "Latin small letter C"的编码是 U+0063
// Unicode "Latin small letter D"的编码是 U+0064
// Unicode "Latin small letter E"的编码是 U+0065
console.log(String.fromCharCode(0x61, 0x62, 0x63, 0x64, 0x65)); // "abcde"
// 0x0061 === 97
// 0x0062 === 98
// 0x0063 === 99
// 0x0064 === 100
// 0x0065 === 101
console.log(String.fromCharCode(97, 98, 99, 100, 101)); // "abcde"
对于 U+0000~U+FFFF 范围内的字符,length、charAt()、charCodeAt()和 fromCharCode()
返回的结果都跟预期是一样的。这是因为在这个范围内,每个字符都是用 16 位表示的,而这几个方法也都基于 16 位码元完成操作。
这个对应关系在扩展到 Unicode 增补字符平面时就不成立了。问题很简单,即 16 位只能唯一表示 65 536 个字符。这对于大多数语言字符集是足够了,在 Unicode 中称为基本多语言平面(BMP)。为了表示更多的字符,Unicode 采用了一个策略,即每个字符使用另外 16 位去选择一个增补平面。这种每个字符使用两个 16 位码元的策略称为代理对。
这里书上举了一个笑脸符号的例子,实际上代理对就是相当于两个16位码元组合了一下,但是浏览器又可以识别,把他们识别成看似是一个字符,可想而知,在读取length的时候会有一些预期之外的值,包括刚才的 charCodeAt()方法,而 fromCharCode()方法返回正确的结果,原因是它实际上是基于提供的二进制表示直接组合成字符串。
想要正确解析既包含单码元字符又包含代理对字符的字符串,就可以使用 codePointAt(),和fromCodePoint()这两个方法,用法都是一样的,因为它们识别的是码点,码点是 Unicode 中一个字符的完整标识。
那么问题来了,实际上使用的时候,如果传入 codePointAt() 的索引不是代理对的起点的话,一样会获得错误的答案:
let message = "ab☺de";
console.log(message.codePointAt(1)); // 98
console.log(message.codePointAt(2)); // 128522
console.log(message.codePointAt(3)); // 56842 ❌
console.log(message.codePointAt(4)); // 100
要想正确分隔,可以使用迭代字符串:
console.log([..."ab☺de"]); // ["a", "b", "☺", "d", "e"]
2. normalize()方法
某些 Unicode 字符可以有多种编码方式。有的字符既可以通过一个 BMP 字符表示,也可以通过一个代理对表示。比如:
// U+00C5:上面带圆圈的大写拉丁字母 A
console.log(String.fromCharCode(0x00C5)); // Å
// U+212B:长度单位“埃”
console.log(String.fromCharCode(0x212B)); // Å
// U+004:大写拉丁字母 A
// U+030A:上面加个圆圈
console.log(String.fromCharCode(0x0041, 0x030A)); // Å// 比较操作符不在乎字符看起来是什么样的,因此这 3 个字符互不相等。
let a1 = String.fromCharCode(0x00C5), a2 = String.fromCharCode(0x212B), a3 = String.fromCharCode(0x0041, 0x030A);
console.log(a1, a2, a3); // Å, Å, Å
console.log(a1 === a2); // false
console.log(a1 === a3); // false
console.log(a2 === a3); // false
为解决这个问题,Unicode提供了 4 种规范化形式,可以将类似上面的字符规范化为一致的格式,无论底层字符的代码是什么。这 4 种规范化形式是:NFD(Normalization Form D)、NFC(Normalization Form C)、NFKD(Normalization Form KD)和 NFKC(Normalization Form KC)。可以使用 normalize()方法对字符串应用上述规范化形式,使用时需要传入表示哪种形式的字符串:"NFD"、"NFC"、"NFKD"或"NFKC"。

通过比较字符串与其调用 normalize()的返回值,就可以知道该字符串是否已经规范化了:
let a1 = String.fromCharCode(0x00C5), a2 = String.fromCharCode(0x212B), a3 = String.fromCharCode(0x0041, 0x030A);
// U+00C5 是对 0+212B 进行 NFC/NFKC 规范化之后的结果
console.log(a1 === a1.normalize("NFD")); // false
console.log(a1 === a1.normalize("NFC")); // true
console.log(a1 === a1.normalize("NFKD")); // false
console.log(a1 === a1.normalize("NFKC")); // true
// U+212B 是未规范化的
console.log(a2 === a2.normalize("NFD")); // false
console.log(a2 === a2.normalize("NFC")); // false
console.log(a2 === a2.normalize("NFKD")); // false
console.log(a2 === a2.normalize("NFKC")); // false
// U+0041/U+030A 是对 0+212B 进行 NFD/NFKD 规范化之后的结果
console.log(a3 === a3.normalize("NFD")); // true
console.log(a3 === a3.normalize("NFC")); // false
console.log(a3 === a3.normalize("NFKD")); // true
console.log(a3 === a3.normalize("NFKC")); // false
// 选择同一种规范化形式可以让比较操作符返回正确的结果:
let a1 = String.fromCharCode(0x00C5), a2 = String.fromCharCode(0x212B), a3 = String.fromCharCode(0x0041, 0x030A);
console.log(a1.normalize("NFD") === a2.normalize("NFD")); // true
console.log(a2.normalize("NFKC") === a3.normalize("NFKC")); // true
console.log(a1.normalize("NFC") === a3.normalize("NFC")); // true
这篇有点长了,关于String类型的方法还有很多,下一篇接着说。