在我的上一篇文章中JavaScript的家族谱(数据类型)已经介绍了关于object和普通类型在数据类型呈现方式上的不同,object是复杂类型,不同于其他基本类型的不可再分割,object内部可能有函数、数组和其他数据类型混合其中。而本文将从另外一个角度——储存方式来阐述object的特殊之处。(注意本文不谈symbol)
数据类型之间的相互转化
为什么要先谈这个,因为基本类型和复杂类型(object)的存储方式并不一样,在数据转化过程中这个事实最为明显。
其他基本类型转化为String(特别注意object)
方式一 :原始数据.toString()
var a = 1
a.toString()
"1" //数值转化为字符串
var a = true
a.toString()
"true" //布尔值转化为字符串
var a = null
a.toString()
VM426:1 Uncaught TypeError: Cannot read property 'toString' of null
at :1:3
//null不能直接转化为字符串,因为null没有toString这个属性
var a = undefined
undefined
a.toString()
VM446:1 Uncaught TypeError: Cannot read property 'toString' of undefined
at :1:3
(anonymous) @ VM446:1
// 同理undefined也没有
var a = {}
undefined
typeof a
"object"
a.toString()
"[object Object]"
// 突出1:要么就老老实实转化,要么就像null一样说没有不就好了,给个奇怪的返回值
方式二:String(原始数据)
var a = 1
String(a)
"1"
//剩下的就省略了,结果和上面的相同
方式三:原始数据 + "
这种方式并不是调用属性,而是让原始数据匹配字符串,所以null和undefined也是可以转化的
a + ''
"1"
null+''
"null"
undefined+''
"undefined"
var a = {key:1}
undefined
a+""
"[object Object]" //行,你NB
其他类型转化为布尔值
五个falsy值
这里说一下,所有数据类型转化成布尔值,其中只有5个会被转化为false:
0
NaN
-
''
(空字符串) null
undefined
方法
方法一:Boolean(原始数据)
Boolean(a)
true
Boolean(0)
false
Boolean(NaN)
false
Boolean('')
false
Boolean(null)
false
Boolean(undefined)
false
Boolean({})
true // 突出2:object啥也没有也是true
方法二:!!原始数据
(!是取反的意思)
!!1
true
!!null
false
// 其他的就不举例了
其他类型转化成数值(这个不突出,主要是有NaN背锅)
方法
方法一:Number(原始值)
var a = {key :'1'}
typeof a
"object"
Number(a)
NaN
Number('1')
1
Number('1sss')
NaN //这个方法只要字符串中有非数字的就返回NaN
Number(true)
1 //后面从储存的角度来解释
Number(false)
0 //后面从储存的角度来解释
Number(null)
0
Number(undefined)
NaN
方法二:parseInt(原始值,'进制数')
--注意是十进制时可以不写,系统默认是存在的
这个方法和上面不同之处在于这个命令只取整,除了数值和能转化的字符串,其他的全是NaN。另外,他能提取字符串前面的数字,具体用法见parseInt
parseInt('1sss')
1 //
方法三:parseFloat('原始值')
,具体用法见parseFloat
方法四(简单写法):原始数据 - 0
方法五(更简单写法):+ 原始数据
true -0
1
'a' -0
NaN
+ true
1
+'a'
NaN
(symbol转化我们就不谈及,至于怎么转化成null或者undefined,请把你的需求写在评论区,让大家一起来深入探讨)
那么其他数据类型转化成对象怎么做?
答案是:obeject(原始值)
var a =1
typeof(a)
"number"
b = Object(a)
typeof b
"object" //给值一个对象包裹器
讨论(开始闲扯)
现在我们来回顾一下上面所有的转化过程,只从数据的展现形式什么也看不出来。这时候我们只能从数据的储存方式来看看。
简单的存储模型
JS有两个储存数据的区域(Stack和Heap),基本数据类型几乎全部存放在Stack里面,如此我们假设一个简单模型 stack里面保存数据都是用哈希表的方式保存(key:value
),Heap保存数据的方式是堆(无序存放,只有地址)。--比实际情况简单便于理解
模型中基本类型数据的储存方式(注意所有的数据都是用二进制表示)
布尔值(boolean
)true
保存的时候就是1
,false
就是0
;
字符串(String
)把引号内部的内容用utf-16保存,单双引号就是单双引号,前后各一个表示开头结尾(真实情况并非如此);
数值(number
)用二进制表示;
null:null
用二进制表示;
undefined:undefined
用二进制表示;
以上全部保存在Stack里面模型中复杂类型数据(object)的储存方式(突出3:存的地方有两个)
object保存在Stack里面的是object Object(二进制表示) +地址(二进制表示)
,Heap保存的是这个地址引用的数据,也就是object里面的内容。String等命令只在Stack里面寻找数据。
推演
基本类型之间转化在储存的角度上的事件(简单模型中)
那么所有的基本类型的转化,只需要把他原始的数据修改或者全部删除然后写入,读取的时候把要读取的部分取出来就可以了。object在上文中的突出点的解读
突出1:为什么字符串转化是"[object Object]"
var a = {}
undefined
typeof a
"object"
a.toString()
"[object Object]"
看看我们的对象在Stack里面是怎么保存的:object Object(二进制表示) +地址(二进制表示)
。字符串没有解读地址而已,而每一个对象都是这个格式保存着。这个模型也就解释了突出2:空的对象转成布尔值也是true
,因为里面写着object Object
,不是5个Falsy值,返回true
。至于地址有没有,对不起,布尔值无法解读。
- 综上看,实际上object和其他数据类型转化这涉及到了储存数据方式的变化
拓展--深拷贝和浅拷贝
深拷贝就是我变你也不变,浅拷贝就是我变你也变
用上面这个简单模型可以很好地解决深浅拷贝的问题
例如
var a = {name:'a'}
var b = a
b.name = 'b'
// a = ?
模型分析:
- Stack存储着哈希表
a:object Object 地址1
- Heap里面存储着堆数据:
地址1{name:'a'}
- b = a 使又增加一个哈希表
b:object Object 地址1
,这时b变量也是引用地址1 -
b.name
(这是object的命令)调用地址1里面的属性name,让name:'a'
变成name:'b'
,此时Heap里的地址1变成地址1{name:'b'}
- 然后问a,a的stack并没有变化
a:object Object 地址1
,而引用的地址1数据是地址1{name:'b'}
,所以a = 'b'
。这就是浅拷贝,你变我也变。变的原因在于地址里的内容变了。
再举一个深拷贝的例子
var a = 1
var b = a
b = 2
// a = ?
模型分析:
- Stack存储着哈希表
a:1
-
b = a
使Stack中又多出一个哈希表b:1
-
b = 2
使Stack中b的value由1变成2,哈希表为b:2
- 问a是多少,哈希表
a:1
并没有变化,所以a=1
。这就是深拷贝,你变我不变。
由这两个例子可以看出,只涉及Stack的数据变化并不会发生浅拷贝,所以基本类型的拷贝全是深拷贝
文末要谢谢方方老师精彩的知识讲解,这篇文章中大部分知识借鉴方方老师的知识,另外因为内存图是方方老师原创,而且不能加动态图,所以这里使用了简单模型代替,如有错误欢迎指正。