JavaScript 深入之头疼的类型转换(上)

发布于 2022-06-25 19:39:51 字数 12546 浏览 1072 评论 20

在 JavaScript 中,有一部分内容,情况复杂,容易出错,饱受争议但又应用广泛,这便是类型转换。

前言

将值从一种类型转换为另一种类型通常称为类型转换。ES6 前,JavaScript 共有六种数据类型:Undefined、Null、Boolean、Number、String、Object。我们先捋一捋基本类型之间的转换。

原始值转布尔

我们使用 Boolean 函数将类型转换成布尔类型,在 JavaScript 中,只有 6 种值可以被转换成 false,其他都会被转换成 true。

console.log(Boolean()) // false

console.log(Boolean(false)) // false

console.log(Boolean(undefined)) // false
console.log(Boolean(null)) // false
console.log(Boolean(+0)) // false
console.log(Boolean(-0)) // false
console.log(Boolean(NaN)) // false
console.log(Boolean("")) // false

注意,当 Boolean 函数不传任何参数时,会返回 false。

原始值转数字

我们可以使用 Number 函数将类型转换成数字类型,如果参数无法被转换为数字,则返回 NaN。

在看例子之前,我们先看 ES5 规范 15.7.1.1 中关于 Number 的介绍:

1

根据规范,如果 Number 函数不传参数,返回 +0,如果有参数,调用 ToNumber(value)

注意这个 ToNumber 表示的是一个底层规范实现上的方法,并没有直接暴露出来。

ToNumber 则直接给了一个对应的结果表。表如下:

参数类型结果
UndefinedNaN
Null+0
Boolean如果参数是 true,返回 1。参数为 false,返回 +0
Number返回与之相等的值
String这段比较复杂,看例子

让我们写几个例子验证一下:

console.log(Number()) // +0

console.log(Number(undefined)) // NaN
console.log(Number(null)) // +0

console.log(Number(false)) // +0
console.log(Number(true)) // 1

console.log(Number("123")) // 123
console.log(Number("-123")) // -123
console.log(Number("1.2")) // 1.2
console.log(Number("000123")) // 123
console.log(Number("-000123")) // -123

console.log(Number("0x11")) // 17

console.log(Number("")) // 0
console.log(Number(" ")) // 0

console.log(Number("123 123")) // NaN
console.log(Number("foo")) // NaN
console.log(Number("100a")) // NaN

如果通过 Number 转换函数传入一个字符串,它会试图将其转换成一个整数或浮点数,而且会忽略所有前导的 0,如果有一个字符不是数字,结果都会返回 NaN,鉴于这种严格的判断,我们一般还会使用更加灵活的 parseInt 和 parseFloat 进行转换。

parseInt 只解析整数,parseFloat 则可以解析整数和浮点数,如果字符串前缀是 "0x" 或者"0X",parseInt 将其解释为十六进制数,parseInt 和 parseFloat 都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。如果第一个非空格字符是非法的数字直接量,将最终返回 NaN:

console.log(parseInt("3 abc")) // 3
console.log(parseFloat("3.14 abc")) // 3.14
console.log(parseInt("-12.34")) // -12
console.log(parseInt("0xFF")) // 255
console.log(parseFloat(".1")) // 0.1
console.log(parseInt("0.1")) // 0

原始值转字符

我们使用 String 函数将类型转换成字符串类型,依然先看 规范15.5.1.1中有关 String 函数的介绍:

如果 String 函数不传参数,返回空字符串,如果有参数,调用 ToString(value),而 ToString 也给了一个对应的结果表。表如下:

参数类型结果
Undefined"undefined"
Null"null"
Boolean如果参数是 true,返回 "true"。参数为 false,返回 "false"
Number又是比较复杂,可以看例子
String返回与之相等的值

让我们写几个例子验证一下:

console.log(String()) // 空字符串

console.log(String(undefined)) // undefined
console.log(String(null)) // null

console.log(String(false)) // false
console.log(String(true)) // true

console.log(String(0)) // 0
console.log(String(-0)) // 0
console.log(String(NaN)) // NaN
console.log(String(Infinity)) // Infinity
console.log(String(-Infinity)) // -Infinity
console.log(String(1)) // 1

注意这里的 ToString 和上一节的 ToNumber 都是底层规范实现的方法,并没有直接暴露出来。

原始值转对象

原始值到对象的转换非常简单,原始值通过调用 String()、Number() 或者 Boolean() 构造函数,转换为它们各自的包装对象。

null 和 undefined 属于例外,当将它们用在期望是一个对象的地方都会造成一个类型错误 (TypeError) 异常,而不会执行正常的转换。

var a = 1;
console.log(typeof a); // number
var b = new Number(a);
console.log(typeof b); // object

对象转布尔值

对象到布尔值的转换非常简单:所有对象(包括数组和函数)都转换为 true。对于包装对象也是这样,举个例子:

console.log(Boolean(new Boolean(false))) // true

对象转字符串和数字

对象到字符串和对象到数字的转换都是通过调用待转换对象的一个方法来完成的。而 JavaScript 对象有两个不同的方法来执行转换,一个是 toString,一个是 valueOf。注意这个跟上面所说的 ToStringToNumber 是不同的,这两个方法是真实暴露出来的方法。

所有的对象除了 null 和 undefined 之外的任何值都具有 toString 方法,通常情况下,它和使用 String 方法返回的结果一致。toString 方法的作用在于返回一个反映这个对象的字符串,然而这才是情况复杂的开始。

Object.prototype.toString 方法会根据这个对象的[[class]]内部属性,返回由 "[object " 和 class 和 "]" 三个部分组成的字符串。举个例子:

Object.prototype.toString.call({a: 1}) // "[object Object]"
({a: 1}).toString() // "[object Object]"
({a: 1}).toString === Object.prototype.toString // true

我们可以看出当调用对象的 toString 方法时,其实调用的是 Object.prototype 上的 toString 方法。

然而 JavaScript 下的很多类根据各自的特点,定义了更多版本的 toString 方法。例如:

  1. 数组的 toString 方法将每个数组元素转换成一个字符串,并在元素之间添加逗号后合并成结果字符串。
  2. 函数的 toString 方法返回源代码字符串。
  3. 日期的 toString 方法返回一个可读的日期和时间字符串。
  4. RegExp 的 toString 方法返回一个表示正则表达式直接量的字符串。

读文字太抽象?我们直接写例子:

console.log(({}).toString()) // [object Object]

console.log([].toString()) // ""
console.log([0].toString()) // 0
console.log([1, 2, 3].toString()) // 1,2,3
console.log((function(){var a = 1;}).toString()) // function (){var a = 1;}
console.log((/\d+/g).toString()) // /\d+/g
console.log((new Date(2010, 0, 1)).toString()) // Fri Jan 01 2010 00:00:00 GMT+0800 (CST)

而另一个转换对象的函数是 valueOf,表示对象的原始值。默认的 valueOf 方法返回这个对象本身,数组、函数、正则简单的继承了这个默认方法,也会返回对象本身。日期是一个例外,它会返回它的一个内容表示: 1970 年 1 月 1 日以来的毫秒数。

var date = new Date(2017, 4, 21);
console.log(date.valueOf()) // 1495296000000

对象接着转字符串和数字

了解了 toString 方法和 valueOf 方法,我们分析下从对象到字符串是如何转换的。看规范 ES5 9.8,其实就是 ToString 方法的对应表,只是这次我们加上 Object 的转换规则:

参数类型结果
Object1. primValue = ToPrimitive(input, String)
2. 返回ToString(primValue).

所谓的 ToPrimitive 方法,其实就是输入一个值,然后返回一个一定是基本类型的值。

我们总结一下,当我们用 String 方法转化一个值的时候,如果是基本类型,就参照 “原始值转字符” 这一节的对应表,如果不是基本类型,我们会将调用一个 ToPrimitive 方法,将其转为基本类型,然后再参照“原始值转字符” 这一节的对应表进行转换。

其实,从对象到数字的转换也是一样:

参数类型结果
Object1. primValue = ToPrimitive(input, Number)
2. 返回ToNumber(primValue)。

虽然转换成基本值都会使用 ToPrimitive 方法,但传参有不同,最后的处理也有不同,转字符串调用的是 ToString,转数字调用 ToNumber

ToPrimitive

那接下来就要看看 ToPrimitive 了,在了解了 toString 和 valueOf 方法后,这个也很简单。

让我们看规范 9.1,函数语法表示如下:

ToPrimitive(input[, PreferredType])
  • 第一个参数是 input,表示要处理的输入值。
  • 第二个参数是 PreferredType,非必填,表示希望转换成的类型,有两个值可以选,Number 或者 String。

当不传入 PreferredType 时,如果 input 是日期类型,相当于传入 String,否则,都相当于传入 Number。

如果传入的 input 是 Undefined、Null、Boolean、Number、String 类型,直接返回该值。

如果是 ToPrimitive(obj, Number),处理步骤如下:

  1. 如果 obj 为 基本类型,直接返回
  2. 否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。
  3. 否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。
  4. 否则,JavaScript 抛出一个类型错误异常。

如果是 ToPrimitive(obj, String),处理步骤如下:

  1. 如果 obj为 基本类型,直接返回
  2. 否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。
  3. 否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。
  4. 否则,JavaScript 抛出一个类型错误异常。

对象转字符串

所以总结下,对象转字符串(就是 Number() 函数)可以概括为:

  1. 如果对象具有 toString 方法,则调用这个方法。如果他返回一个原始值,JavaScript 将这个值转换为字符串,并返回这个字符串结果。
  2. 如果对象没有 toString 方法,或者这个方法并不返回一个原始值,那么 JavaScript 会调用 valueOf 方法。如果存在这个方法,则 JavaScript 调用它。如果返回值是原始值,JavaScript 将这个值转换为字符串,并返回这个字符串的结果。
  3. 否则,JavaScript 无法从 toString 或者 valueOf 获得一个原始值,这时它将抛出一个类型错误异常。

对象转数字

对象转数字的过程中,JavaScript 做了同样的事情,只是它会首先尝试 valueOf 方法

  1. 如果对象具有 valueOf 方法,且返回一个原始值,则 JavaScript 将这个原始值转换为数字并返回这个数字
  2. 否则,如果对象具有 toString 方法,且返回一个原始值,则 JavaScript 将其转换并返回。
  3. 否则,JavaScript 抛出一个类型错误异常。

举个例子:

console.log(Number({})) // NaN
console.log(Number({a : 1})) // NaN

console.log(Number([])) // 0
console.log(Number([0])) // 0
console.log(Number([1, 2, 3])) // NaN
console.log(Number(function(){var a = 1;})) // NaN
console.log(Number(/\d+/g)) // NaN
console.log(Number(new Date(2010, 0, 1))) // 1262275200000
console.log(Number(new Error('a'))) // NaN

注意,在这个例子中,[][0] 都返回了 0,而 [1, 2, 3] 却返回了一个 NaN。我们分析一下原因:

当我们 Number([]) 的时候,先调用 []valueOf 方法,此时返回 [],因为返回了一个对象而不是原始值,所以又调用了 toString 方法,此时返回一个空字符串,接下来调用 ToNumber 这个规范上的方法,参照对应表,转换为 0, 所以最后的结果为 0

而当我们 Number([1, 2, 3]) 的时候,先调用 [1, 2, 3]valueOf 方法,此时返回 [1, 2, 3],再调用 toString 方法,此时返回 1,2,3,接下来调用 ToNumber,参照对应表,因为无法转换为数字,所以最后的结果为 NaN

JSON.stringify

值得一提的是:JSON.stringify() 方法可以将一个 JavaScript 值转换为一个 JSON 字符串,实现上也是调用了 toString 方法,也算是一种类型转换的方法。下面讲一讲JSON.stringify 的注意要点:

1.处理基本类型时,与使用toString基本相同,结果都是字符串,除了 undefined

console.log(JSON.stringify(null)) // null
console.log(JSON.stringify(undefined)) // undefined,注意这个undefined不是字符串的undefined
console.log(JSON.stringify(true)) // true
console.log(JSON.stringify(42)) // 42
console.log(JSON.stringify("42")) // "42"

2.布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。

JSON.stringify([new Number(1), new String("false"), new Boolean(false)]); // "[1,"false",false]"

3.undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。

JSON.stringify({x: undefined, y: Object, z: Symbol("")}); 
// "{}"

JSON.stringify([undefined, Object, Symbol("")]);          
// "[null,null,null]" 

4.JSON.stringify 有第二个参数 replacer,它可以是数组或者函数,用来指定对象序列化过程中哪些属性应该被处理,哪些应该被排除。

function replacer(key, value) {
  if (typeof value === "string") {
    return undefined;
  }
  return value;
}

var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
var jsonString = JSON.stringify(foo, replacer);

console.log(jsonString)
// {"week":45,"month":7}
var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
console.log(JSON.stringify(foo, ['week', 'month']));
// {"week":45,"month":7}

5.如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为:不是那个对象被序列化,而是调用 toJSON 方法后的返回值会被序列化,例如:

var obj = {
  foo: 'foo',
  toJSON: function () {
    return 'bar';
  }
};
JSON.stringify(obj);      // '"bar"'
JSON.stringify({x: obj}); // '{"x":"bar"}'

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(20

缱倦旧时光 2022-05-04 13:48:39

var a = {toString: function(){
console.log('to string')
},valueOf: function(){
console.log('to value')
}}

a + '' // 为啥打印value

一梦浮鱼 2022-05-04 13:48:39

var a = {
    toString: function(){
        console.log('to string')
    },
    valueOf: function(){
        console.log('to value')
    }
}

a + '' // 为啥打印value

@consunmida     这是+号运算的规则:

  1. 将+号两边的变量转为原型类型,对于对象类型的变量,会先调用valueOf方法,如果是原始类型就返回;如果不是原始类型就调用toString方法。也就是上面的ToPrimitive方法
  2. 如果+号两侧有一个string类型,将另一个也转为string类型进行字符串拼接
  3. 否则转为number类型进行加法运算。

所以上面的代码会打印to value,返回的是undefined属于原始类型。

如果把上面的代码修改下:

var a = {
    toString: function(){
        console.log('to string')
    },
    valueOf: function(){
        console.log('to value')
        return {};
    }
}

a + ''

就会先打印to value,再打印to string

夏末染殇 2022-05-04 13:48:39


大佬 这个是不是写错了呢

停顿的约定 2022-05-04 13:48:39

对象在转换为原始数据类型时:

  1. 先调用内置的[toPrimitive](hint)函数
  2. hintstring,则尝试调用toStringvalueOf
  3. hintnumber,则尝试调用valueOftoString
  4. hintdefault,同number逻辑
  5. 如果还未返回原始数据类型,则报错

当然也可重写Symbol.toPrimitive,在做数据类型转换时调用优先级最高。

这样记忆会不会清楚点?至于基本数据类型转换为基本数据类型基本上见多了规则就熟悉了,或者说记住了。

default 逻辑应该是同 string

Date.prototype[@@toPrimitive]

埋情葬爱 2022-05-04 13:48:38

对象在转换为原始数据类型时:

  1. 先调用内置的[toPrimitive](hint)函数
  2. hintstring,则尝试调用toStringvalueOf
  3. hintnumber,则尝试调用valueOftoString
  4. hintdefault,同number逻辑
  5. 如果还未返回原始数据类型,则报错

当然也可重写Symbol.toPrimitive,在做数据类型转换时调用优先级最高。

这样记忆会不会清楚点?至于基本数据类型转换为基本数据类型基本上见多了规则就熟悉了,或者说记住了。

忆沫 2022-05-04 13:48:38

原始值转对象中 null 和 undefined 并不会错误呀

看规范上是调用ToObject才会报错

罪歌 2022-05-04 13:48:38

所以总结下,对象转字符串(就是 Number() 函数)可以概括为:
这里是不是写错了,不应该是Sring() 函数吗

烟雨凡馨 2022-05-04 13:48:34

有时候看书或者学习,脑袋就会浑浑噩噩的,难受,本身又想学习,大佬可以有解决之法。

脑不喜欢学习 他觉得枯燥乏味 他觉得痛苦 于是潜意识里在逃避痛苦,换句话来说就是大脑讨厌自己转起来,然后给你制作乱子,让你觉得困 让你停下 不再学习,这是大脑的一种保护机制但是同样长时间使用脑子 为什么你在打游戏的时候不会觉得困 不会想睡觉 而是玩了一局又一局都不觉得累 因为有刺激 而大脑喜欢高刺激的东西 , 你可以定一下今天学习的目标 如果完成了 奖励自己玩一把游戏,或者是喜欢的事情,这样应该会很好。

无人问我粥可暖 2022-05-04 13:48:34

好详细

辞别 2022-05-04 13:48:21

有时候看书或者学习,脑袋就会浑浑噩噩的,难受,本身又想学习,大佬可以有解决之法。

眼泪也成诗 2022-05-04 13:46:55

总结很好,各种书籍和学习网站上都是单挑出来讲的,其实在这一块的知识点上,如果能在开头加上"类型转换分为显式数据类型转换和隐式数据类型转换。无论哪种数据类型转换都包括 基本数据类型间的转换 和 基本数据类型与引用数据类型间的转换",效果应该可以好一点。
不是每种数据类型都包括toString()

对象转字符串

所以总结下,对象转字符串(就是 Number() 函数)可以概括为

这里是不是写错了,应该是String()函数
落叶缤纷 2022-05-04 13:14:51

这个类型转换推荐直接看《你所不知道的JavaScript》,这个写的感觉条例不太行

平生欢 2022-05-04 12:56:24

认真读了三遍,豁然开朗,对类型转化有了一个新的全面认识。

季末如歌 2022-05-04 11:15:03

对象转字符串的总结中:“所以总结下,对象转字符串(就是 Number() 函数)可以概括为...”,这里应该是 String() 函数吧

怪我太投入 2022-05-04 09:35:54

@mqyqingfeng

文中的 ToPrimitive(input[, PreferredType])

应该是ToPrimitive(input,[PreferredType])吧...

梦言归人 2022-05-04 03:51:15

写的太好了..

反目相谮 2022-05-03 15:09:35

怎么归纳知识体系呢 有点难串联。。。

两相知 2022-05-02 05:34:45

感觉已经是第三遍学习js类型转换,好像又深入理解了些~

-小熊_ 2022-04-29 03:37:48

这种东西该怎么记忆呢,头疼

~没有更多了~

关于作者

0 文章
0 评论
23 人气
更多

推荐作者

lorenzathorton8

文章 0 评论 0

Zero

文章 0 评论 0

萧瑟寒风

文章 0 评论 0

mylayout

文章 0 评论 0

tkewei

文章 0 评论 0

17818769742

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文