在我的上一篇文章中JavaScript的家族譜(數(shù)據(jù)類型)已經(jīng)介紹了關(guān)于object和普通類型在數(shù)據(jù)類型呈現(xiàn)方式上的不同买置,object是復(fù)雜類型,不同于其他基本類型的不可再分割强霎,object內(nèi)部可能有函數(shù)忿项、數(shù)組和其他數(shù)據(jù)類型混合其中。而本文將從另外一個(gè)角度——儲存方式來闡述object的特殊之處城舞。(注意本文不談symbol)
數(shù)據(jù)類型之間的相互轉(zhuǎn)化
為什么要先談這個(gè)轩触,因?yàn)榛绢愋秃蛷?fù)雜類型(object)的存儲方式并不一樣,在數(shù)據(jù)轉(zhuǎn)化過程中這個(gè)事實(shí)最為明顯家夺。
其他基本類型轉(zhuǎn)化為String(特別注意object)
方式一 :原始數(shù)據(jù).toString()
var a = 1
a.toString()
"1" //數(shù)值轉(zhuǎn)化為字符串
var a = true
a.toString()
"true" //布爾值轉(zhuǎn)化為字符串
var a = null
a.toString()
VM426:1 Uncaught TypeError: Cannot read property 'toString' of null
at <anonymous>:1:3
//null不能直接轉(zhuǎn)化為字符串怕膛,因?yàn)閚ull沒有toString這個(gè)屬性
var a = undefined
undefined
a.toString()
VM446:1 Uncaught TypeError: Cannot read property 'toString' of undefined
at <anonymous>:1:3
(anonymous) @ VM446:1
// 同理undefined也沒有
var a = {}
undefined
typeof a
"object"
a.toString()
"[object Object]"
// 突出1:要么就老老實(shí)實(shí)轉(zhuǎn)化,要么就像null一樣說沒有不就好了秦踪,給個(gè)奇怪的返回值
方式二:String(原始數(shù)據(jù))
var a = 1
String(a)
"1"
//剩下的就省略了褐捻,結(jié)果和上面的相同
方式三:原始數(shù)據(jù) + "
這種方式并不是調(diào)用屬性,而是讓原始數(shù)據(jù)匹配字符串椅邓,所以null和undefined也是可以轉(zhuǎn)化的
a + ''
"1"
null+''
"null"
undefined+''
"undefined"
var a = {key:1}
undefined
a+""
"[object Object]" //行柠逞,你NB
其他類型轉(zhuǎn)化為布爾值
五個(gè)falsy值
這里說一下,所有數(shù)據(jù)類型轉(zhuǎn)化成布爾值景馁,其中只有5個(gè)會被轉(zhuǎn)化為false:
0
NaN
-
''
(空字符串) null
undefined
方法
方法一:Boolean(原始數(shù)據(jù))
Boolean(a)
true
Boolean(0)
false
Boolean(NaN)
false
Boolean('')
false
Boolean(null)
false
Boolean(undefined)
false
Boolean({})
true // 突出2:object啥也沒有也是true
方法二:0遄场!原始數(shù)據(jù)
(合住!是取反的意思)
!!1
true
!!null
false
// 其他的就不舉例了
其他類型轉(zhuǎn)化成數(shù)值(這個(gè)不突出绰精,主要是有NaN背鍋)
方法
方法一:Number(原始值)
var a = {key :'1'}
typeof a
"object"
Number(a)
NaN
Number('1')
1
Number('1sss')
NaN //這個(gè)方法只要字符串中有非數(shù)字的就返回NaN
Number(true)
1 //后面從儲存的角度來解釋
Number(false)
0 //后面從儲存的角度來解釋
Number(null)
0
Number(undefined)
NaN
方法二:parseInt(原始值,'進(jìn)制數(shù)')
--注意是十進(jìn)制時(shí)可以不寫透葛,系統(tǒng)默認(rèn)是存在的
這個(gè)方法和上面不同之處在于這個(gè)命令只取整笨使,除了數(shù)值和能轉(zhuǎn)化的字符串,其他的全是NaN僚害。另外,他能提取字符串前面的數(shù)字硫椰,具體用法見parseInt
parseInt('1sss')
1 //
方法三:parseFloat('原始值')
,具體用法見parseFloat
方法四(簡單寫法):原始數(shù)據(jù) - 0
方法五(更簡單寫法):+ 原始數(shù)據(jù)
true -0
1
'a' -0
NaN
+ true
1
+'a'
NaN
(symbol轉(zhuǎn)化我們就不談及,至于怎么轉(zhuǎn)化成null或者undefined萨蚕,請把你的需求寫在評論區(qū)靶草,讓大家一起來深入探討)
那么其他數(shù)據(jù)類型轉(zhuǎn)化成對象怎么做?
答案是:obeject(原始值)
var a =1
typeof(a)
"number"
b = Object(a)
typeof b
"object" //給值一個(gè)對象包裹器
討論(開始閑扯)
現(xiàn)在我們來回顧一下上面所有的轉(zhuǎn)化過程岳遥,只從數(shù)據(jù)的展現(xiàn)形式什么也看不出來奕翔。這時(shí)候我們只能從數(shù)據(jù)的儲存方式來看看。
簡單的存儲模型
JS有兩個(gè)儲存數(shù)據(jù)的區(qū)域(Stack和Heap)浩蓉,基本數(shù)據(jù)類型幾乎全部存放在Stack里面派继,如此我們假設(shè)一個(gè)簡單模型 stack里面保存數(shù)據(jù)都是用哈希表的方式保存(key:value
)帮坚,Heap保存數(shù)據(jù)的方式是堆(無序存放,只有地址)互艾。--比實(shí)際情況簡單便于理解
模型中基本類型數(shù)據(jù)的儲存方式(注意所有的數(shù)據(jù)都是用二進(jìn)制表示)
布爾值(boolean
)true
保存的時(shí)候就是1
试和,false
就是0
;
字符串(String
)把引號內(nèi)部的內(nèi)容用utf-16保存纫普,單雙引號就是單雙引號阅悍,前后各一個(gè)表示開頭結(jié)尾(真實(shí)情況并非如此);
數(shù)值(number
)用二進(jìn)制表示昨稼;
null:null
用二進(jìn)制表示节视;
undefined:undefined
用二進(jìn)制表示;
以上全部保存在Stack里面模型中復(fù)雜類型數(shù)據(jù)(object)的儲存方式(突出3:存的地方有兩個(gè))
object保存在Stack里面的是object Object(二進(jìn)制表示) +地址(二進(jìn)制表示)
假栓,Heap保存的是這個(gè)地址引用的數(shù)據(jù)寻行,也就是object里面的內(nèi)容。String等命令只在Stack里面尋找數(shù)據(jù)匾荆。
推演
基本類型之間轉(zhuǎn)化在儲存的角度上的事件(簡單模型中)
那么所有的基本類型的轉(zhuǎn)化拌蜘,只需要把他原始的數(shù)據(jù)修改或者全部刪除然后寫入,讀取的時(shí)候把要讀取的部分取出來就可以了牙丽。object在上文中的突出點(diǎn)的解讀
突出1:為什么字符串轉(zhuǎn)化是"[object Object]"
var a = {}
undefined
typeof a
"object"
a.toString()
"[object Object]"
看看我們的對象在Stack里面是怎么保存的:object Object(二進(jìn)制表示) +地址(二進(jìn)制表示)
简卧。字符串沒有解讀地址而已,而每一個(gè)對象都是這個(gè)格式保存著烤芦。這個(gè)模型也就解釋了突出2:空的對象轉(zhuǎn)成布爾值也是true
举娩,因?yàn)槔锩鎸懼?code>object Object,不是5個(gè)Falsy值构罗,返回true
铜涉。至于地址有沒有,對不起遂唧,布爾值無法解讀芙代。
- 綜上看,實(shí)際上object和其他數(shù)據(jù)類型轉(zhuǎn)化這涉及到了儲存數(shù)據(jù)方式的變化
拓展--深拷貝和淺拷貝
深拷貝就是我變你也不變蠢箩,淺拷貝就是我變你也變
用上面這個(gè)簡單模型可以很好地解決深淺拷貝的問題
例如
var a = {name:'a'}
var b = a
b.name = 'b'
// a = ?
模型分析:
- Stack存儲著哈希表
a:object Object 地址1
- Heap里面存儲著堆數(shù)據(jù):
地址1{name:'a'}
- b = a 使又增加一個(gè)哈希表
b:object Object 地址1
链蕊,這時(shí)b變量也是引用地址1 -
b.name
(這是object的命令)調(diào)用地址1里面的屬性name,讓name:'a'
變成name:'b'
谬泌,此時(shí)Heap里的地址1變成地址1{name:'b'}
- 然后問a,a的stack并沒有變化
a:object Object 地址1
逻谦,而引用的地址1數(shù)據(jù)是地址1{name:'b'}
掌实,所以a = 'b'
。這就是淺拷貝邦马,你變我也變贱鼻。變的原因在于地址里的內(nèi)容變了宴卖。
再舉一個(gè)深拷貝的例子
var a = 1
var b = a
b = 2
// a = ?
模型分析:
- Stack存儲著哈希表
a:1
-
b = a
使Stack中又多出一個(gè)哈希表b:1
-
b = 2
使Stack中b的value由1變成2,哈希表為b:2
- 問a是多少邻悬,哈希表
a:1
并沒有變化症昏,所以a=1
。這就是深拷貝父丰,你變我不變肝谭。
由這兩個(gè)例子可以看出,只涉及Stack的數(shù)據(jù)變化并不會發(fā)生淺拷貝蛾扇,所以基本類型的拷貝全是深拷貝
文末要謝謝方方老師精彩的知識講解攘烛,這篇文章中大部分知識借鑒方方老師的知識,另外因?yàn)閮?nèi)存圖是方方老師原創(chuàng)镀首,而且簡書不能加動(dòng)態(tài)圖坟漱,所以這里使用了簡單模型代替,如有錯(cuò)誤歡迎指正更哄。