你盼世界,我盼望你无bug
。Hello 你们好!我是霖呆呆!javascript
这一期给你们带来的是一篇关于JS
数据类型转换的文章,原由主要是前几天在刷类型转换的题时忽然感受本身对它们理解的还不够深入啊,对于什么[] == ![]、!{} == []
这类题老是只知其一;不知其二,记了忘忘了记。前端
这让我很苦恼,决心给本身下点猛料,完全弄懂它们的转换机制而后出几道魔鬼题来考考本身。java
在写的时候也是蛮纠结的,开始写了一版全是题目的,可是后来发现若是全是题目不讲其原理的话,一些读者可能会一脸懵逼...因此后来我又加了关于toString、valueOf、toPrimitive
的详细解析,再配合一些清晰的流程图,力求能将转换过程说的清楚明了 😁。git
不过预防针可打在前头,因为前面1-3
节是一些基础类型之间的转换,并不难,因此我不会花太多的篇幅在这上面,可能从第4
节开始慢慢的有点内味了吧,[阴笑~],以为本身对基础类型之间转换有信心的小伙伴能够直接跳到第4
节看哦。github
但愿整篇阅读下来你的脑子里并非 "淡黄的长裙...蓬松的头发..."
面试
OK👌,来看看经过阅读你能够学习到:数组
(写的过程当中,看到冴羽大大也发表了一篇关于类型转换的文章《JavaScript深刻之头疼的类型转换(上)》,完了,难道...我已经到了和大佬们心有灵犀的境界了吗,膨胀膨胀了,能够借鉴一下,哈哈)浏览器
另外「数据类型转换」系列我是分为了两篇文章来写,这一篇主要是讲解String()、Number()
这种的转换方式,对于运算符号+、==
这种的转换以及toPrimitive
的一些高级用法我会放在下一篇文章里面。 嘻嘻,仍是那句话,按部就班嘛。markdown
「数据类型转换」系列共有两篇文章:编辑器
建议按顺序阅读,两篇文章帮你彻底弄懂数据类型转换 😁。
转化为布尔值的状况是很简单的。
当咱们在使用Boolean()
来进行转换时,有以下转换规则:
参数类型 | 结果 |
---|---|
false、undefined、null、+0、-0、NaN、"" | false |
除了上面的状况 | true |
(另外须要注意的是,若是在使用Boolean()
时不传参数结果也是为false
的)
数字转布尔值,只须要记住:
0, -0, NaN
这三种转换为false
,其余的一概为true
。console.log(Boolean(0)) console.log(Boolean(-0)) console.log(Boolean(NaN)) console.log(Boolean(1)) console.log(Boolean(Infinity)) console.log(Boolean(-Infinity)) console.log(Boolean(100n)) console.log(Boolean(BigInt(100))) 复制代码
记住上面👆的规律,这边我把bigInt
类型的也拿过来试了一下,发现它也是为true
。
所以答案为:
console.log(Boolean(0)) // false console.log(Boolean(-0)) // false console.log(Boolean(NaN)) // false console.log(Boolean(1)) // true console.log(Boolean(Infinity)) // true console.log(Boolean(-Infinity)) // true console.log(Boolean(100n)) // true console.log(Boolean(BigInt(100))) // true 复制代码
字符串转布尔也很简单,只须要记住:
""
都为true
。console.log(Boolean("")) console.log(Boolean("1")) console.log(Boolean("NaN")) console.log(Boolean("aaa")) 复制代码
这里特别要注意的是"NaN"
,它并非NaN
哦,而是一个字符串。
因此答案为:
console.log(Boolean("")) // false console.log(Boolean("1")) // true console.log(Boolean("NaN")) // true console.log(Boolean("aaa")) // true 复制代码
其它类型,例如null, undefined, 引用
转布尔值,这些相信你们其实也知道:
null、undefined
为false
true
document.all
是一个例外,它在非IE
下用typeof
检测类型为undefined
,因此会被转为false
。(考的很少)(感谢掘友小茗cha提出的document.all
)
var divs = document.getElementsByTagName('div') console.log(Boolean(null)) console.log(Boolean(undefined)) console.log(Boolean({})) console.log(Boolean({ name: 'obj' })) console.log(Boolean([])) console.log(Boolean(divs)) console.log(Boolean(new Date())) console.log(Boolean(/(\[|\])/g)) console.log(typeof document.all) console.log(Boolean(document.all)) 复制代码
结果为:
var divs = document.getElementsByTagName('div') console.log(Boolean(null)) // false console.log(Boolean(undefined)) // false console.log(Boolean({})) // true console.log(Boolean({ name: 'obj' })) // true console.log(Boolean([])) // true console.log(Boolean(divs)) // true console.log(Boolean(new Date())) // true console.log(Boolean(/(\[|\])/g)) // true console.log(typeof document.all) // undefined console.log(Boolean(document.all)) // false 复制代码
(document.all
是文档中全部标签组成的一个数组变量,包括了文档对象中全部元素,document.all[]这个数组能够访问文档中全部元素。它在非IE
的浏览器中是为undefined
的,因此能够用其来判断当前浏览器是不是IE
,不过如今用的已经不多了,我就不展开了)
对于原始值转字符串,也有如下总结:
参数类型 | 结果 |
---|---|
Undefined | "undefined" |
Null | "null" |
Boolean | 若是参数是 true,返回 "true"。参数为 false,返回 "false" |
Number | 能够看题目2.1 |
String | 返回与之相等的值 |
Symbol | "Symbol()" |
来作几道题强化一下吧。
console.log(String(0)) console.log(String(1)) console.log(String(100)) console.log(String(NaN)) console.log(String(10n)) console.log(String(10n) === '10') 复制代码
bigInt
类型会被当成数字来处理。
答案为:
console.log(String(0)) // '0' console.log(String(1)) // '1' console.log(String(100)) // '100' console.log(String(NaN)) // 'NaN' console.log(String(10n)) // '10' console.log(String(10n) === '10') // true 复制代码
这三种类型转换为字符串,比较简单:
console.log(String(true)) console.log(String(false)) console.log(String(Symbol(1))) 复制代码
答案:
console.log(String(true)) // 'true' console.log(String(false)) // 'false' console.log(String(Symbol(1))) // 'Symbol(1)' 复制代码
参数类型 | 结果 |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | 若是参数是 true,返回 1。参数为 false,返回 +0 |
Number | 返回与之相等的值 |
String | 纯数字的字符串(包括小数和负数、各进制的数),会被转为相应的数字,不然为NaN |
Symbol | 使用Number()转会报错 |
先来看看你们都知道的string、null、undefined、Symbol
转数字
console.log(Number("1")) console.log(Number("1.1")) console.log(Number("-1")) console.log(Number("0x12")) console.log(Number("0012")) console.log(Number(null)) console.log(Number("1a")) console.log(Number("NaN")) console.log(Number(undefined)) console.log(Number(Symbol(1))) 复制代码
答案为:
console.log(Number("1")) // 1 console.log(Number("1.1")) // 1.1 console.log(Number("-1")) // -1 console.log(Number("0x12")) // 18 console.log(Number("0012")) // 12 console.log(Number(null)) // 0 console.log(Number("1a")) // NaN console.log(Number("NaN")) // NaN console.log(Number(undefined)) // NaN console.log(Number(Symbol(1))) // TypeError: Cannot convert a Symbol value to a number 复制代码
其实很好记:
null
转为0
Symbol
会报错NaN
,undefined
都会被转为NaN
(Boolean
类型转数字)
布尔值转数字也是很是简单的,只有两种状况:
console.log(Number(true)) // 1 console.log(Number(false)) // 0 复制代码
还有一种你们可能会用到的转数字的方式,那就是使用:
parsetInt
,将结果转换为整数parseFloat
,将结果转换为整数或者浮点数它们在转换为数字的时候是有这么几个特色的:
0x或者0X
开头的话,parseInt
会以十六进制数转换规则将其转换为十进制,而parseFloat
会解析为0
0x、-
的数字字面量,将最终返回NaN
来看几道题练习一下 😄:
console.log(parseInt('10')) // 10 console.log(parseFloat('1.23')) // 1.23 console.log(parseInt("0x11")) // 17 console.log(parseFloat("0x11")) // 0 console.log(parseInt(" 11")) // 11 console.log(parseFloat(" 11")) // 11 console.log(parseInt("1.23a12")) // 1 console.log(parseFloat("1.23a12")) // 1.23 console.log(parseInt(" 11")) // 11 console.log(parseFloat(" 11")) // 11 console.log(parseInt("1a12")) // 1 console.log(parseFloat("1.23a12")) // 1.23 console.log(parseInt("-1a12")) // -1 console.log(parseFloat(".23")) // 0.23 复制代码
一直作到如今感受都还挺简单的哈 😄。
原始值,也就是基础数据类型。
让咱们先来认识一个叫作String
的对象,它的原型链是这样的:
能够看到,它本质上是一个构造函数,String.__proto__
指向的是Function.prototype
。
String
其实有两种用法,一种是配合new
来当构造函数用,一种是不用new
:
Symbol
以后就不推荐使用new String
这种作法了)。什么意思呢 🤔️?通俗点来讲:
typeof String(1) // 'string' typeof new String(1) // 'object' 复制代码
使用typeof
会发现类型都是不一样的。
哈哈哈。
和它一块儿的其实还有另外两个"亲兄弟"
:Number、Boolean
;
以及它的"表哥表妹"
:Symbol、BigInt
。 😄
为何说Number、Boolean
就是亲的,然后面两个就是表的呢 🤔️?
这个霖呆呆是以类似程度来区分的。
也就是说Number、Boolean
和String
同样都有两种用法,带new
和不带new
。
而Symbol、BigInt
就只能不带new
使用。(由于它们是ES6
以后出来的,对它们调用new
会报错)
因此你会看到这个现象:
console.log(Number(1)) // 1 console.log(new Number(1)) // Number{1} console.log(Boolean(true)) // true console.log(new Boolean(true)) // Boolean{true} console.log(Symbol(1)) // Symbol(1) console.log(BigInt(1)) // 1n console.log(new Symbol(1)) // TypeError: Symbol is not a constructor console.log(new BigInt(1)) // TypeError: BigInt is not a constructor 复制代码
而上面的Number{1}、Boolean{true}
,它就是我要介绍的基本类型的包装对象,也被称为基本类型的包装类,也能够叫作原始值包装对象(有不少的叫法不过你们应该都知道它表示的就是这个意思)。
能够看到要想产生一个基础数据类型的包装对象只须要使用new
来调用它们各自的构造函数便可:
console.log(new Number(1)) // Number{1} console.log(new String('1')) // String{1} console.log(new Boolean(true)) // Boolean{true} 复制代码
这个基本类型的包装对象
有什么特色呢?
typeof
检测它,结果是object
,说明它是一个对象toString()
调用的时候返回的是原始值的字符串(题6.8.3
中会提到)But!!!
前面已经说到了,目前ES6
规范是不建议用new
来建立基本类型的包装类的,我想大概是为了和Symbol、BigInt
它们统一吧。
那么如今更推荐用什么方式来建立基本类型的包装类呢?
唔...那就是Object
这个构造函数。
Object()
构造函数它能够接收一个任意类型的变量,而后进行不一样的转换。
也就是说七种基本数据类型,或者引用数据类型你均可以传入进去。
不过这里我主要是为了介绍基本数据类型转对象,因此就以几个基本数据类型来作分析:
console.log(new Object('1')) // String{'1'} console.log(new Object(1)) // Number{1} console.log(new Object(true)) // Boolean{true} console.log(new Object(Symbol(1))) // Symbol{Symbol(1)} console.log(new Object(10n)) // BigInt{10n} console.log(new Object(null)) // {} console.log(new Object(undefined)) // {} 复制代码
能够看到,你传入的基本数据类型是什么类型的,那么最终的结果就会转为对应的包装类,可是对于null、undefined
它们会被忽略,生成的会是一个空对象。
原始值转对象主要有如下总结:
String、Number、Boolean
有两种用法,配合new
使用和不配合new
使用,可是ES6
规范不建议使用new
来建立基本类型的包装类。new Object()
来建立或转换为一个基本类型的包装类。基本类型的包装对象的特色:
typeof
检测它,结果是object
,说明它是一个对象toString()
调用的时候返回的是原始值的字符串(题6.8.3
中会提到)对象转字符串和数字的过程比较复杂,会涉及到一个可能你们以前没有听到过的方法:toPrimitive()
它的做用其实就是输入一个值,而后返回一个必定是基本类型的值,不然会抛出一个类型错误异常。
先上一张执行流程图,让你们感觉一下绝望、孤独、寂寞、冷...
虽然它的功能会有些复杂,不过问题不大,待看完后面的内容以后你就能搞懂它了,在介绍toPrimitive()
以前,我得先详细介绍一下toString()
和valueOf()
方法才行,由于弄懂了它们你才能完全吃透toPrimitive()
。😄
在此以前,我翻了不少关于toString()
的资料,大多都是介绍了它的用法,可是它真正存在于哪里呢?
可能比较常见的一种说法是它存在于Object
的原型对象中,也就是Object.prototype
上,那么对于基本数据类型,Number、String、Boolean、 Symbol、BigInt
呢?它们自身有这个方法吗?或者它们的原型对象上有吗?
本着一探到底的精神,我打印出了Number
和Number.prototype
:
console.log(Number) console.log(Number.prototype) 复制代码
而后我发现了几件事:
Number
只是一个构造函数,打印出来显示的会是源代码Number.prototype
上确实也有toString()
Number.prototype.__proto__
也就是Object.prototype
上也有toString()
而后我又试了一下String、Boolean、Symbol
发现结果也和上面同样。
其实不难理解,看过《💦【何不三连】作完这48道题完全弄懂JS继承(1.7w字含辛整理-返璞归真)》的小伙伴都知道,全部对象的原型链到最后都会指向Object.prototype
,算是都"继承"了Object
的对象实例,所以都能使用toString()
方法,可是对于不一样的内置对象为了能实现更适合自身的功能需求,都会重写该方法,因此你能够看到Number.prototype
上也会有该方法。
因此咱们能够先得出第一个结论:
null、undefined
之外的其它数据类型(基本数据类型+引用数据类型),它们构造函数的原型对象上都有toString()
方法toString()
会覆盖Object
原型对象上的toString()
方法(固然,等你看到6.9
以后你就会发现这种说法其实并不太准确,可是大多数时候咱们都只是关心谁能够用它,而不是它存在于哪里)
这个问题,其实在上面👆已经给出答案了,全部对象除了null、undefined
之外的任何值均可以调用toString()
方法,一般状况下它的返回结果和String
同样。
其实这里,咱们最容易搞混的就是String
和toString
。
以前老是为了将某个类型转为字符串胡乱的用这两个属性。
String
是一个相似于Function
这样的对象,它既能够当成对象来用,用它上面的静态方法,也能够当成一个构造函数来用,建立一个String
对象toString
它是除了null、undefined
以外的数据类型都有的方法,一般状况下它的返回结果和String
同样。可是就会有小伙伴问了,那为何'1'.toString()
也能够成功呢?那是由于代码在运行的时候实际上是作了转换为包装类的处理,相似于下面这段代码:
var str = new Object('1'); str.toString(); str = null; 复制代码
过程解析:
Object
实例,将s
变为了String{"1"}
对象toString()
但是咱们以前不是看到了一个String
的东西吗?这里的第一步为何不能使用var str = new String('1')
呢?
其实前面也已经说到了,因为Symbol
和BigInt
它们是不能使用new
来调用的,会报错,而且目前ES6
的规范也不推荐使用new
来建立这种基本类型的包装类,因此这里使用的是new Object()
。
可是当咱们在代码中试图使用1.toString()
,发现编辑器已经报错不容许咱们这样作了。
难道数字就不能够吗 🤔️?最开始会有这么奇怪的疑问是由于咱们都忽视了一件事,那就是.
它也是属于数字里的一部分啊 😂。
好比1.2
、1.3
。因此当你想要使用1.toString()
的时候,JavaScript
的解释器会把它做为数字的一部分,这样就至关于(1.)toString
了,很显然这是一段错误的代码。
既然这样的话,若是我还(喝唔安黄
)给代码一个.
是否是就能够了,因而我尝试了一下:
console.log(1.1.toString()) 复制代码
发现它居然能正常打印出来:
"1.1" 复制代码
这也就再次证实了1.toString()
会将.
归给1
所属,而不是归给toString()
。
固然若是你用的一个变量来承载这个数字的话也是能够的:
var num = 1; console.log(num.toString()) // "1" // 或者 console.log((1).toString()) // "1" 复制代码
因此在此咱们只须要先记住谁能够调用toString
:
null、undefined
的其它基本数据类型还有对象均可以调用它toString()
的时候会报错,除非这个数字是一个小数或者是用了一个变量来盛放这个数字而后调用。(1.1.toString()
或者var a = 1; a.toString();
)可能你们看的比较多的一种用法是这样的:
Object.prototype.toString.call({ name: 'obj' }) // '[object Object]' 复制代码
先来点硬知识,Object.prototype.toString
这个方法会根据这个对象的[[class]]
内部属性,返回由 "[object " 和 class 和 "]"
三个部分组成的字符串。
啥意思?[[class]]
内部属性是个啥 🤔️?
这里你还真别想多,你就按字面意思来理解它就行了,想一想,class
英文单词的意思->类
。
那好,我就认为它表明的是一类事物就好了。
就好比
[[class]]
是Array
[[class]]
是String
arguments
是一类,它的[[class]]
是Arguments
另外,关于[[class]]
的种类是很是多的,你也不须要记住所有,只须要知道一些经常使用的,基本的,好理解的就能够了。
因此回到Object.prototype.toString.call()
这种调用方式来,如今你能够理解它的做用了吧,它可以帮助咱们准确的判断某个数据类型,也就是辨别出是数组仍是数字仍是函数,仍是NaN
。😊
另外鉴于它的返回结果是"[object Object]"
这样的字符串,并且前面的"[object ]"
这八个字符串都是固定的(包括"t"
后面的空格),因此咱们是否是能够封装一个方法来只拿到"Object"
这样的字符串呢?
很简单,上代码:
function getClass (obj) { let typeString = Object.prototype.toString.call(obj); // "[object Object]" return typeString.slice(8, -1); } 复制代码
能够看到,我给这个函数命名为getClass
,这也就呼应了它本来的做用,是为了拿到对象的[[class]]
内部属性。
另外,在拿到了"[object Object]"
字符串以后,是用了一个.slice(8, -1)
的字符串截取功能,去除了前八个字符"[object ]"
和最后一个"]"
。
如今让咱们来看看一些常见的数据类型吧:
function getClass(obj) { let typeString = Object.prototype.toString.call(obj); // "[object Array]" return typeString.slice(8, -1); } console.log(getClass(new Date)) // Date console.log(getClass(new Map)) // Map console.log(getClass(new Set)) // Set console.log(getClass(new String)) // String console.log(getClass(new Number)) // Number console.log(getClass(true)) // Boolean console.log(getClass(NaN)) // Number console.log(getClass(null)) // Null console.log(getClass(undefined)) // Undefined console.log(getClass(Symbol(42))) // Symbol console.log(getClass({})) // Object console.log(getClass([])) // Array console.log(getClass(function() {})) // Function console.log(getClass(document.getElementsByTagName('p'))) // HTMLCollection console.log(getClass(arguments)) // Arguments 复制代码
"霖呆呆,这么多,这是人干的事吗?"
"性平气和,记住一些经常使用的就好了..."
"啪!"
好滴👌,经过刚刚的学习,咱们了解到了,toString.call
这种方式是为了获取某个变量更加具体的数据类型。
咦~说到数据类型,咱们原来不是有一个typeof
吗?它和toString.call()
又啥区别?
首先帮你们回顾一下typeof
它的显示规则:
number、string
这种),除了null
均可以显示正确的类型null
由于历史版本的缘由被错误的判断为了"object"
object、array
这种),除了函数都会显示为"object"
function
因此呀,typeof
的缺点很明显啊,我如今有一个对象和一个数组,或者一个日期对象,我想要仔细的区分它,用typeof
确定是不能实现的,由于它们获得的都是"object"
。
因此,采用咱们封装的getClass()
显然是一个很好的选择。
(固然,了解instanceof
的小伙伴可能也知道,用instanceof
去判断也是能够的,不过这边不扯远,具体能够看一下三元大大的《(建议收藏)原生JS灵魂之问, 请问你能接得住几个?(上)》,里面的第二篇有提到这个问题。或者你能够期待一下呆呆后面的文章,那里也会详细讲到哦,这里先卖个关子,哈哈)
刚刚咱们说到的toString()
的用法是使用toString.call()
的方式,那么更多的使用确定是某个变量后面之间接着toString()
呀,就好比这样:
true.toString() // 'true' 复制代码
请你们必定要区分清楚true.toString()
和Object.prototype.toString.call(true)
这两种用法啊:
true.toString()
是将true
转为字符串toString.call(true)
是获取true
它的[[class]]
内部属性:true.toString() // 'true' Object.prototype.toString.call(true) // "[object Boolean]" 复制代码
因为toString.call()
这种用法以前说的已经比较详细了,因此下面的内容都是围绕着true.toString()
这种调用方式来说。
那么在不一样的数据类型调用toString()
会有什么不一样呢?
这里我主要是分为两大块来讲:
对于基本数据类型来调用它,超级简单的,你就想着就是把它的原始值换成了字符串而已:
console.log('1'.toString()) // '1' console.log(1.1.toString()) // '1.1' console.log(true.toString()) // 'true' console.log(Symbol(1).toString()) // 'Symbol(1)' console.log(10n.toString()) // '10' 复制代码
比较难的部分是引用类型调用toString()
,并且咱们知道引用类型根据[[class]]
的不一样是分了不少类的,好比有Object
、Array
、Date
等等。
那么不一样类之间的toString()
是否也不一样呢 🤔️?
没错,不一样版本的toString
主要是分为:
toString
方法是将每一项转换为字符串而后再用","
链接{name: 'obj'}
这种)转为字符串都会变为"[object Object]"
函数(class)、正则
会被转为源代码字符串日期
会被转为本地时区的日期字符串toString
会返回原始值的字符串好的👌,扯了这么多知识点,终于能够先上几道题了 😁。
(没有题目作我好难受~)
(数组的toString()
用法)
先来看点简单的:
console.log([].toString()) console.log([1].toString()) console.log([1, 2].toString()) console.log(['1', '2'].toString()) console.log(['', ''].toString()) console.log([' ', ' '].toString()) 复制代码
答案:
console.log([].toString()) // "" console.log([1].toString()) // "1" console.log([1, 2].toString()) // "1,2" console.log(['1', '2'].toString()) // "1,2" console.log(['', ''].toString()) // "," console.log([' ', ' '].toString()) // " , " 复制代码
没啥难度。
须要注意的可能就是[].toString()
的时候,因为数组一项都没有,因此获得的确定是一个空字符串。
另外须要注意的是最后两个,一个是彻底的空字符串,一个是带了空格的。
(非数组类型的其它对象)
console.log({}.toString()) console.log({name: 'obj'}.toString()) console.log(class A {}.toString()) console.log(function () {}.toString()) console.log(/(\[|\])/g.toString()) console.log(new Date().toString()) 复制代码
依照上面👆的第2,3,4
条规则,答案会为:
'[object Object]' '[object Object]' 'class A {}' 'function () {}' '/(\[|\])/g' 'Fri Mar 27 2020 12:33:16 GMT+0800 (中国标准时间)' 复制代码
(原始值包装对象调用toString()
)
原始值包装对象在上面👆的第四章已经讲到了,也就是:
Number{1} String{'1'} Boolean{true} 复制代码
这样的对象。
当它们在调用toString()
方法的时候,会返回它们原始值的字符串,就像这样:
console.log(new Object(true).toString()) // "true" console.log(new Object(1).toString()) // "1" console.log(new Object('1').toString()) // "1" console.log(new Object(Symbol(1)).toString()) // "Symbol(1)" console.log(new Object(BigInt(10)).toString()) // "10" 复制代码
(Map、Set
类型调用toString
)
在作题的时候,我又想着测试一下Map、Set
类型调用toString
会是什么样的。
console.log(new Map().toString()) console.log(new Set().toString()) console.log(new Array(['1']).toString()) 复制代码
发现结果居然是:
console.log(new Map().toString()) // "[object Map]" console.log(new Set().toString()) // "[object Set]" console.log(new Array(['1']).toString()) // "1" 复制代码
这看的我有点懵了,怎么前面两个的结果有点像是Object.prototype.toString.call()
的调用结果呢?而若是是数组的话,却又遵循了数组转字符串的转换规则...
啊啊啊啊...好不容易弄懂了一些,这怎么又跑出来个Map、Set
。
好奇心趋势着我将new Map()
这个实例对象打印出来看看:
console.log(new Map()) 复制代码
我好像嗅到了一丝八卦的气息,我发现Map.prototype
和并无和Number.prototype
同样有它自身的toString()
方法,而只是Object.prototype
上才有。
而且好像有一个咱们历来没有见过的属性:Symbol(Symbol.toStringTag)
,并且它的值正好是"Map"
。
不懂就查,搜了一波Symbol.toStringTag
以后,我就恍然大悟了。
《Symbol.toStringTag》上是这样描述它的:
该Symbol.toStringTag
公知的符号是在建立对象的默认字符串描述中使用的字符串值属性。它由该Object.prototype.toString()
方法在内部访问。
看不懂不要紧,你这样理解就能够了,它其实就是决定了刚刚咱们提到全部数据类型中[[class]]
这个内部属性是什么。
好比数字,咱们前面获得的[[class]]
是Number
,那我就能够理解为数字这个类它的Symbol.toStringTag
返回的就是Number
。
只不过在以前咱们用到的Number、String、Boolean
中并无Symbol.toStringTag
这个内置属性,它是在咱们使用toString.call()
调用的时候才将其辨别返回。
而刚刚咱们打印出了new Map()
,能够看到Symbol.toStringTag
它是确确实实存在于Map.prototype
上的,也就是说它是Map、Set
内置的一个属性,所以当咱们直接调用toString()
的时候,就会返回"[object Map]"
了。
额,咱们是否是就能够这样理解呢?
Symbol.toStringTag
内置属性的类型在调用toString()
的时候至关因而String(obj)
这样调用转换为相应的字符串Symbol.toStringTag
内置属性的类型在调用toString()
的时候会返回相应的标签(也就是"[object Map]"
这样的字符串)咱们经常使用的带有Symbol.toStringTag
内置属性的对象有:
console.log(new Map().toString()) // "[object Map]" console.log(new Set().toString()) // "[object Set]" console.log(Promise.resolve().toString()) // "[object Promise]" 复制代码
并且我发现了它和Symbol.hasInstance
同样,能够容许咱们自定义标签。
(Symbol.hasInsance
的做用是自定义instanceof
的返回值)
什么是自定义标签呢 🤔️?
也就是说,假如咱们如今建立了一个类,而且用toString.call()
调用它的实例对象是会有以下结果:
class Super {} console.log(Object.prototype.toString.call(new Super())) // "[object Object]" 复制代码
很好理解,由于产生的new Super()
是一个对象嘛,因此打印出的会是"[object Object]"
。
可是如今有了Symbol.toStringTag
以后,咱们能够改后面的"Object"
。
好比我重写一下:
class Super { get [Symbol.toStringTag] () { return 'Validator' } } console.log(Object.prototype.toString.call(new Super())) // "[object Validator]" 复制代码
这就是Symbol.toStringTag
的厉害之处,它可以容许咱们自定义标签。
可是有一点要注意了,Symbol.toStringTag
重写的是new Super()
这个实例对象的标签,而不是重写Super
这个类的标签,也就是说这里有区别的:
class Super { get [Symbol.toStringTag] () { return 'Validator' } } console.log(Object.prototype.toString.call(Super)) // "[object Function]" console.log(Object.prototype.toString.call(new Super())) // "[object Validator]" 复制代码
由于Super
它自己仍是一个函数,只有Super
产生的实例对象才会用到咱们的自定义标签。
内容好多啊,咱们总结着理顺来,这样才好记。
谁能够调用toString()?
null、undefined
的其它基本数据类型还有对象均可以调用它,一般状况下它的返回结果和String
同样。toString()
的时候会报错,除非这个数字是一个小数或者是用了一个变量来盛放这个数字而后调用。(1.1.toString()
或者var a = 1; a.toString();
)Object.prototype.toString.call()是作什么用的?
[[class]]
,可以帮助咱们准确的判断出某个数据类型typeof
判断数据类型更加的准确不一样数据类型调用toString()
toString
方法是将每一项转换为字符串而后再用","
链接{name: 'obj'}
这种)转为字符串都会变为"[object Object]"
函数(class)、正则
会被转为源代码字符串日期
会被转为本地时区的日期字符串toString
会返回原始值的字符串Symbol.toStringTag
内置属性的对象在调用时会变为对应的标签"[object Map]"
Symbol.toStringTag
Map、Set、Promise
Object.prototype.toString.call()
的返回结果接下来要介绍的是toString()
的孪生兄弟valueOf
,为何说是它的孪生兄弟呢 🤔️?
由于它们有不少相同的特性,好比前面咱们提到的toString()
的存在位置,咱们能够回头看看6.1
的那张图,发现有toString()
的地方也有valueOf()
。
另外一个要介绍它的重要缘由是在对象转基础数据类型
中,与toString()
相辅相成的就是它了。
它的做用主要是:
把对象转换成一个基本数据的值。
因此咱们能够看出它两的区别:
toString
主要是把对象转换为字符串valueOf
主要把对象转换成一个基本数据的值让咱们先来看看valueOf
的基本用法吧。
基本数据类型的调用也是很简单的,它只要返回调用者本来的值就能够了:
console.log('1'.valueOf()) // '1' console.log(1.1.valueOf()) // 1.1 console.log(true.valueOf()) // true console.log(Symbol(1).valueOf()) // Symbol(1) console.log(10n.valueOf()) // 10n 复制代码
看着好像没变啊,没错,因此你能够用下面👇的方式来验证一下:
var str = '1' console.log(str.valueOf() === str) // true 复制代码
引用类型调用valueOf()
并不难,你只须要记住:
valueOf()
默认是返回它自己1970 年 1 月 1 日以来的毫秒数
。好比:
console.log([].valueOf()) // [] console.log({}.valueOf()) // {} console.log(['1'].valueOf()) // ['1'] console.log(function () {}.valueOf()) // ƒ () {} console.log(/(\[|\])/g.valueOf()) // /(\[|\])/g console.log(new Date().valueOf()) // 1585370128307 复制代码
valueOf()的基本用法
valueOf()
默认是返回它自己1970 年 1 月 1 日以来的毫秒数
(相似于1585370128307
)。弄懂了难啃的toString()
和valueOf()
,终于到了咱们的主角toPrimitive
...
泪牛满面 😢。
不过不可大意,它才是最难啃的那块知识点。
先让咱们来看看它的函数语法:
ToPrimitive(input, PreferredType?)
复制代码
参数:
input
,表示要处理的输入值PerferredType
,指望转换的类型,能够看到语法后面有个问号,表示是非必填的。它只有两个可选值,Number
和String
。而它对于传入参数的处理是比较复杂的,如今让咱们来看看开篇的那幅流程图:
根据流程图,咱们得出了这么几个信息:
(总结来源《冴羽-JavaScript深刻之头疼的类型转换(上)》)
上面👆的图其实只是看着很复杂,细心的小伙伴可能会发现,在图里红框裱起来的地方,只有toString()
和valueOf()
方法的执行顺序不一样而已。
若是 PreferredType 是 String 的话,就先执行 toString()
方法
若是 PreferredType 是 Number 的话,就先执行 valueOf()
方法
霖呆呆建议你先本身在草稿纸上将这幅流程图画一遍,以后再来作题有助于记忆 😁。
(最基本的转换)
好吧,呆呆,我看你扯了这么多toPrimitive
的转换流程,可我也没看出有什么实际的用处啊。
这...没啊,其实咱们很早就用上了啊,只不过你以前可能不知道而已。
好比当咱们使用String()
来转换一个对象为字符串的时候:
console.log(String({})) 复制代码
你们都知道结果是:
"[object Object]" 复制代码
但它为何是这样呢?看着结果和toString()
调用的结果好像啊。
这里其实就用到了toPrimitive
的转换规则呀。
你看看,咱们把上面👆的代码换成toPrimitive
的伪代码看看:
toPrimitive({}, 'string') 复制代码
OK👌,来回顾一下刚刚的转换规则:
input
是{}
,是一个引用类型,PerferredType
为string
toString()
方法,也就是{}.toString()
{}.toString()
的结果为"[object Object]"
,是一个字符串,为基本数据类型,而后返回,到此结束。哇~
是否是一切都说得通了,好像不难吧 😁。
没错,当使用String()
方法的时候,JS
引擎内部的执行顺序确实是这样的,不过有一点和刚刚提到的步骤不同,那就是最后返回结果的时候,其实会将最后的基本数据类型再转换为字符串返回。
也就是说上面👆的第三步咱们得拆成两步来:
{}.toString()
的结果为"[object Object]"
,是一个字符串,为基本数据类型"[object Object]"
字符串再作一次字符串的转换而后返回。(由于"[object Object]"
已是字符串了,因此原样返回,这里看不出有什么区别)将最后的结果再转换为字符串返回这一步,其实很好理解啊。你想一想,我调用String
方法那就是为了获得一个字符串啊,你要是给我返回一个number、null
啊什么的,那不是隔壁老王干的事嘛~
(咳咳,霖呆呆的真名姓王哈 [害羞~])
上面👆的转换好像并不能看出来最后会转为字符串那一步的效果啊,那么来看看这道题:
console.log(String(null)) console.log(String(new Object(true))) 复制代码
想一想这里的转换规则。
对于String(null)
:
input
是个基础数据类型,这简单啊,直接返回它就能够了null
,而后再把它转为字符串"null"
,因此最后返回的是"null"
。对于String(new Object(true))
:
new Object(true)
是一个基本类型的包装类Boolean{true}
toString()
6.8.3
中已经说到了,它调用toString()
方法是会返回原始值的字符串,也就是"true"
"true"
是基本数据类型,最后再进行一层字符串转换(仍是它自己),而后返回"true"
。答案:
console.log(String(null)) // "null" console.log(String(new Object(true))) // true 复制代码
若是你能看到这的话,怎样?是否是有点那啥感受了。
(数组转字符串)
数组转字符串我总结了一下主要是这样:
[]
是被转换为空字符串""
","
链接配合着引用类型转字符串我画了一张图。
先来看点简单的:
console.log(String([])) console.log(String([1])) console.log(String([1, 2])) console.log(String(['1', '2'])) 复制代码
答案:
console.log(String([])) // "" console.log(String([1])) // "1" console.log(String([1, 2])) // "1,2" console.log(String(['1', '2'])) // "1,2" 复制代码
没啥难度。
让咱们用toPrimitive
的转换规则来讲一下:
对于String([1, 2])
:
input
为数组[1, 2]
,所以使用toString()
方法调用[1, 2]
转为字符串为"1,2"
,字符串"1,2"
为原始数据类型,则返回(因为返回值都是字符串我就省略还有一个字符串的转换过程不说了)让咱们加上Boolean、函数、NaN
看看:
console.log(String([true, false])) console.log(String([NaN, 1])) console.log(String([function () {}, 1])) console.log(String([{ name: 'obj' }, { name: 'obj2' }])) 复制代码
解析:
","
链接"function () {}"
"[object, Object]"
,因此结果会有两个"[object Object]"
用","
链接答案为:
console.log(String([true, false])) // "true,false" console.log(String([NaN, 1])) // "NaN,1" console.log(String([function () {}, 1])) // "function () {},1" // "[object Object],[object Object]" console.log(String([{ name: 'obj' }, { name: 'obj2' }])) 复制代码
因此作这类题时,你通常只要谨记这个准则:
","
链接就能够了,而后再看里面具体的每一项会被转成什么。
(日期类型转字符串)
console.log(String(new Date())) console.log(String(new Date('2020/12/09'))) 复制代码
日期类型的对象转字符串在题6.8.2
中也已经说到过了,它会被转为本地时区的日期字符串,因此结果为:
console.log(String(new Date())) // Sat Mar 28 2020 23:49:45 GMT+0800 (中国标准时间) console.log(String(new Date('2020/12/09'))) // Wed Dec 09 2020 00:00:00 GMT+0800 (中国标准时间) 复制代码
对于对象转字符串,也就是调用String()
函数,总结以下:
其实也就是走的toPrimitive(object, 'string')
这种状况。
若是你们弄懂了对象转字符串的话,那么弄懂对象转数字也不难了。
刚刚咱们说了对象转字符串也就是toPrimitive(object, 'string')
的状况,
那么对象转数字就是toPrimitive(object, 'number')
。
区别就是转数字会先调用valueOf()
后调用toString()
。
(最基本的转换)
console.log(Number({})) console.log(Number([])) console.log(Number([0])) console.log(Number([1, 2])) 复制代码
对于Number({})
:
{}
,所以调用valueOf()
方法,该方法在题7.1
中已经提到过了,它除了日期对象的其它引用类型调用都是返回它自己,因此这里仍是返回了对象{}
valueOf()
返回的值仍是对象,因此继续调用toString()
方法,而{}
调用toString()
的结果为字符串"[object Object]"
,是一个基本数据类型"[object Object]"
转为数字为NaN
,因此结果为NaN
对于Number([])
:
[]
,所以调用valueOf()
方法,返回它自身[]
[]
继续调用toString()
方法,而空数组转为字符串是为""
""
转为数字0
返回对于Number([0])
:
[0]
转为字符串是为"0"
,最后在转为数字0
返回对于Number([1, 2])
:
[1, 2]
,因此调用valueOf()
方法返回的是数组自己[1,2]
toString()
方法,此时被转换为了"1,2"
字符串"1,2"
字符串最后被转为数字为NaN
,因此结果为NaN
结果:
console.log(Number({})) // NaN console.log(Number([])) // 0 console.log(Number([0])) // 0 console.log(Number([1, 2])) // NaN 复制代码
(日期类型转数字)
来看个比较特殊的日期类型转数字
console.log(Number(new Date())) 复制代码
过程解析:
new Date()
,所以调用valueOf()
,在题目7.2
中已经说了,日期类型调用valueOf()
是会返回一个毫秒数1585413652137
答案:
console.log(Number(new Date())) // 1585413652137 复制代码
因此对于对象转数字,总结来讲和对象转字符串差很少:
若是对象具备 valueOf 方法,且返回一个原始值,则 JavaScript 将这个原始值转换为数字并返回这个数字
不然,若是对象具备 toString 方法,且返回一个原始值,则 JavaScript 将其转换并返回。
不然,JavaScript 抛出一个类型错误异常。
可算是给👴整完了这206
个console.log()
,吸口气休息一会...
知识无价,支持原创。
参考文章:
你盼世界,我盼望你无bug
。这篇文章就介绍到这里。
其实我在学习数据类型转换的的历程是这样的:
满心欢喜 -> 决心弄懂 -> 眉头紧锁 -> 表情凝重 -> 生无可恋 -> 小彻小悟
确实有一个生无可恋的时候,哈哈哈,不过在坚持下去以后也算是"小彻小悟"
吧,为啥不是大彻大悟,这个...人仍是要谦虚点的哈。
用心创做,好好生活。若是你以为文章对你有帮助的话来个赞👍哦,谢谢啦~ 😁。
喜欢霖呆呆的小伙还但愿能够关注霖呆呆的公众号 LinDaiDai
或者扫一扫下面的二维码👇👇👇.
我会不定时的更新一些前端方面的知识内容以及本身的原创文章🎉
你的鼓励就是我持续创做的主要动力 😊.
相关推荐:
《【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)》
《【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)》
《【何不三连】比继承家业还要简单的JS继承题-封装篇(牛刀小试)》