ES5 规范第 9 节中定义了一些“抽象操作”(即“仅供内部使用的操作”)和转 换规则。

ToString

规范的 9.8 节中定义了抽象操作 ToString,它负责处理非字符串到字符串的强制类型转换。
基本类型值的字符串化规则为:

  1. null 转换为 "null"
  2. undefined 转换为 "undefined"
  3. //上述在Chrome91,已报错
  4. true 转换为 "true"

数字的字符串化则遵循通用规则,那些极小和极大的数字使用指数形式:

  1. // 1.07 连续乘以七个 1000
  2. var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
  3. // 七个1000一共21位数字
  4. a.toString(); // "1.07e21"

对普通对象来说,除非自行定义,否则 toString()(Object.prototype.toString())返回内部属性 [[Class]] 的值,如 “[object Object]”。

  1. var a = {A: '122'};
  2. a.toString(); // "[object Object]"

如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。

  1. var a = {
  2. a: 123,
  3. toString() {
  4. return '456'
  5. }
  6. }
  7. a.toString() // "456"

数组的默认 toString()方法经过了重新定义,将所有单元字符串化以后再用 “,” 连接起来:

  1. var a = [1,2,3];
  2. a.toString(); // "1,2,3"

toString() 可以被显式调用,或者在需要字符串化时自动调用。

JSON 字符串化

工具函数 JSON.stringify(..) 在将 JSON 对象序列化为字符串时也用到了 ToString。
对大多数简单值来说,JSON 字符串化和 toString() 的效果基本相同,只不过序列化的结果总是字符串:

  1. JSON.stringify( 42 ); // "42"
  2. JSON.stringify( "42" ); // ""42""(含有双引号的字符串)
  3. "42".toString(); // "42"
  4. JSON.stringify( null ); // "null"
  5. JSON.stringify( true ); // "true"

所有安全的 JSON 值(JSON-safe)都可以使用 JSON.stringify(..) 字符串化。安全的 JSON 值是指能够呈现为有效 JSON 格式的值。
不安全的 JSON 值:undefined、function、symbol (ES6+)和包含循环引用(对象之间相互引用,形成一个无限循环)的对象都不符合 JSON结构标准,支持 JSON 的语言无法处理它们。
JSON.stringify(..) 在对象中遇到 undefined、function 和 symbol 时会自动将其忽略,在
数组中则会返回 null(以保证单元位置不变)。

  1. JSON.stringify( undefined ); // undefined
  2. JSON.stringify( function(){} ); // undefined
  3. JSON.stringify(
  4. [1,undefined,function(){},4]
  5. ); // "[1,null,null,4]"
  6. JSON.stringify(
  7. { a:2, b:function(){} }
  8. ); // "{"a":2}"

对包含循环引用的对象执行 JSON.stringify(..) 会出错。
如果对象中定义了 toJSON() 方法,JSON 字符串化时会首先调用该方法,然后用它的返回值来进行序列化。
如果要对含有非法 JSON 值的对象做字符串化,或者对象中的某些值无法被序列化时,就需要定义 toJSON() 方法来返回一个安全的 JSON 值。

  1. var o = { };
  2. var a = {
  3. b: 42,
  4. c: o,
  5. d: function(){}
  6. };
  7. // 在a中创建一个循环引用
  8. o.e = a;
  9. // 循环引用在这里会产生错误
  10. // JSON.stringify( a );
  11. // 自定义的JSON序列化
  12. a.toJSON = function() {
  13. // 序列化仅包含b
  14. return { b: this.b };
  15. };
  16. JSON.stringify( a ); // "{"b":42}"

toJSON() 返回的应该是一个适当的值,可以是任何类型,然后再由 JSON.stringify(..) 对其进行字符串化。
也就是说,toJSON() 应该“返回一个能够被字符串化的安全的 JSON 值”,而不是“返回 一个 JSON 字符串”。

  1. var a = {
  2. val: [1,2,3],
  3. // 可能是我们想要的结果! toJSON: function(){
  4. return this.val.slice( 1 );
  5. }
  6. };
  7. var b = {
  8. val: [1,2,3],
  9. // 可能不是我们想要的结果!
  10. toJSON: function(){
  11. return "[" +
  12. this.val.slice( 1 ).join() +
  13. "]";
  14. }
  15. };
  16. JSON.stringify( a ); // "[2,3]"
  17. JSON.stringify( b ); // ""[2,3]""

第二个函数是对 toJSON 返回的字符串做字符串化,而非数组本身。
可以向 JSON.stringify(..) 传递一个可选参数 replacer,它可以是数组或者函数,用来指定对象序列化过程中哪些属性应该被处理,哪些应该被排除,和 toJSON() 很像。
如果 replacer 是一个数组,那么它必须是一个字符串数组,其中包含序列化要处理的对象的属性名称,除此之外其他的属性则被忽略。
如果 replacer 是一个函数,它会对对象本身调用一次,然后对对象中的每个属性各调用一次,每次传递两个参数,键和值。如果要忽略某个键就返回 undefined,否则返回指定的值。

  1. var a = {
  2. b: 42,
  3. c: "42",
  4. d: [1,2,3]
  5. };
  6. JSON.stringify( a, ["b","c"] ); // "{"b":42,"c":"42"}"
  7. JSON.stringify( a, function(k,v){
  8. if (k !== "c") return v;
  9. } );
  10. // "{"b":42,"d":[1,2,3]}"

如果 replacer 是函数,它的参数 k 在第一次调用时为 undefined(就是对对象 本身调用的那次)。if语句将属性 “c” 排除掉。由于字符串化是递归的,因此数组 [1,2,3] 中的每个元素都会通过参数 v 传递给 replacer,即 1、2 和 3, 参数 k 是它们的索引值,即 0、1 和 2。
JSON.stringify 还有一个可选参数 space,用来指定输出的缩进格式。space 为正整数时是指定每一级缩进的字符数,它还可以是字符串,此时最前面的十个字符被用于每一级的缩进:

  1. var a = { b: 42,
  2. c: "42",
  3. d: [1,2,3] };
  4. JSON.stringify( a, null, 3 );
  5. // "{
  6. // "b": 42,
  7. // "c": "42",
  8. // "d": [
  9. // 1,
  10. // 2,
  11. // 3
  12. // ]
  13. // }"
  14. JSON.stringify( a, null, "-----" );
  15. // "{
  16. // -----"b": 42,
  17. // -----"c": "42",
  18. // -----"d": [
  19. // ----------1,
  20. // ----------2,
  21. // ----------3
  22. // -----]
  23. // }"

JSON.stringify(..) 并不是强制类型转换。在这里介绍是因为它涉及 ToString 强制类型转换,具体表现在以下两点。
(1) 字符串、数字、布尔值的 JSON.stringify(..) 规则与 ToString 基本相同。
(2) 如果传递给 JSON.stringify(..) 的对象中定义了 toJSON() 方法,那么该方法会在字符串化前调用,以便将对象转换为安全的 JSON 值。

ToNumber

有时我们需要将非数字值当作数字来使用,比如数学运算。为此 ES5 规范在 9.3 节定义了 抽象操作 ToNumber。
其中 true 转换为 1false 转换为 0undefined 转换为 NaNnull 转换为 0
ToNumber 对字符串的处理基本遵循数字常量的相关规则 / 语法。处理失败时返回 NaN(处理数字常量失败时会产生语法错误)。不同之处是 ToNumber 对以 0 开头的十六进制数并不按十六进制处理(而是按十进制)。
数字常量的语法规则与 ToNumber 处理字符串所遵循的规则之间差别不大,可参考 ES5 规范的 9.3.1 节。
对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
为了将值转换为相应的基本类型值,抽象操作 ToPrimitive(参见 ES5 规范 9.1 节)会首先 (通过内部操作 DefaultValue,参见 ES5 规范 8.12.8 节)检查该值是否有 valueOf() 方法。 如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString()的返回值(如果存在)来进行强制类型转换。
如果 valueOf()toString() 均不返回基本类型值,会产生 TypeError 错误。
从 ES5 开始,使用 Object.create(null) 创建的对象 [[Prototype]]属性为 null,并且没 有 valueOf()toString() 方法,因此无法进行强制类型转换。例如:

  1. var a = {
  2. valueOf: function () {
  3. return "42";
  4. }
  5. };
  6. var b = {
  7. toString: function () {
  8. return "42";
  9. }
  10. };
  11. var c = [4, 2];
  12. c.toString = function () {
  13. return this.join(""); // "42"
  14. };
  15. Number(a); // 42
  16. Number(b); // 42
  17. Number(c); // 42
  18. Number(""); // 0
  19. Number([]); // 0
  20. Number(["abc"]); // NaN

ToBoolean

js 中有两个关键词 true 和 false,分别代表布尔类型中的真和假。我们常误以为数值 1 和 0 分别等同于 true 和 false。在有些语言中可能是这样,但在 js 中布尔值和数字是不一样的。虽然我们可以将 1 强制类型转换为 true, 将 0 强制类型转换为 false,反之亦然,但它们并不是一回事。

假值(falsy value)

js 中的值可以分为以下两类:
(1) 可以被强制类型转换为 false 的值
(2) 其他(被强制类型转换为 true 的值)
以下这些是假值:

  • undefined
  • null
  • false
  • +0、-0和NaN
  • “”

假值的布尔强制类型转换结果为 false
从逻辑上说,假值列表以外的都应该是真值(truthy)。但 js 规范对此并没有明确定义,只是给出了一些示例,例如规定所有的对象都是真值,可以理解为假值列表以外的值都是真值。

假值对象(falsy object)

可能会以为假值对象就是包装了假值的封装对象,其实不是。

  1. var a = new Boolean( false );
  2. var b = new Number( 0 );
  3. var c = new String( "" );

它们都是封装了假值的对象,它们都为true

  1. var d = Boolean( a && b && c );
  2. d; // true

Boolean(..)a && b && c进行了封装,强制转换为布尔类型。没有Boolean(...)的情况:

  1. var e = a && b && c;
  2. e; // String {""}

假值对象看起来和普通对象并无二致(都有属性,等等),但将它们强制类型转换为布尔值时结果为 false
最常见的例子是 document.all,它是一个类数组对象,包含了页面上的所有元素,由 DOM(而不是 js 引擎)提供给 js 程序使用。它以前曾是一个真正意义上的对象,布尔强制类型转换结果为 true,不过现在它是一个假值对象。
document.all 并不是一个标准用法,早就被废止了。

真值(truthy value)

真值就是假值列表之外的值。

  1. var a = "false";
  2. var b = "0";
  3. var c = "''";
  4. var d = Boolean( a && b && c );
  5. d; // true
  1. var a = []; // 空数组——是真值还是假值?
  2. var b = {}; // 空对象——是真值还是假值?
  3. var c = function(){}; // 空函数——是真值还是假值?
  4. var d = Boolean( a && b && c );
  5. d; // true

因为除了以下假值,其他都是真值。

  1. undefined
  2. null
  3. false
  4. +0、-0 和 NaN
  5. “”