前言:之前的博客介紹了JS中的各種數據類型,那麼可不可以把已經確定的數據類型轉換成其他的數據類型呢?本文就將介紹一些方法達成此目的,另外還會引申的簡單介紹一下JS中的內存管理、深拷貝與淺拷貝等相關知識。
1、如何將其他的數據類型轉換成String字符串類型?
注:下面介紹的三種轉換成字符串的方法都不適用於對象,所得結果都是”[object Object]”
- toSring()
toString方法適用於number類型和boolean類型,如:
true.toString(); // "true"
(11).toString(); // "11"
但是對於null和undefined,使用這種方法會報錯,如:
undefined.toString(); // Cannot read property 'toString' of undefined
null.toString(); // Cannot read property 'toString' of null
對於object,toSring方法結果不正確,結果永遠是”[object Object]”
var obj = {}
obj.toString()
==> 結果爲 "[object Object]"
String()
該方法適用於所有數據類型(除了對象,結果同toString()
)
String(obj); // "[object Object]"
String(11); // "11"
String(true); // "true"
String(undefined); // "undefined"
+ ''
即使用+運算符加上空字符串(同樣對object無效)
注:上面介紹的toString()以及String()方法是比較常規的方法,而這種不常規的操作,纔是程序員的日常。這種方法的原理是:‘+’ 運算符只能相加相同的數據類型,如果兩邊的數據類型不同,他會優先將其轉換成字符串來相加。因此就有一個很常見的坑:1+'1'
的結果是多少?有些經驗尚淺的程序員會以爲結果是2,但很明顯,這句話是按照字符串相加的規則來計算的,所以結果爲"11"
使用此方法轉換成字符串的例子如下:
true+""; // "true"
undefined+""; // "undefined"
obj+""; // "[object Object]"
11+""; // "11"
2、如何將其他的數據類型轉換成Number數值類型?
Number()
使用Number函數,可以將任意類型的值轉化成數值。Number函數將字符串轉爲數值,要比parseInt函數嚴格很多。基本上,只要有一個字符無法轉成數值,整個字符串就會被轉爲NaN。
示例如下:
// 數值:轉換後還是原來的值
Number(324) // 324
// 字符串:如果可以被解析爲數值,則轉換爲相應的數值
Number('324') // 324
// 字符串:如果不可以被解析爲數值,返回 NaN
Number('324abc') // NaN
// 空字符串轉爲0
Number('') // 0
// 布爾值:true 轉成 1,false 轉成 0
Number(true) // 1
Number(false) // 0
// undefined:轉成 NaN
Number(undefined) // NaN
// null:轉成0
Number(null) // 0
parseInt()
parseInt方法用於將字符串轉爲整數。
如:parseInt('123') // 123
如果字符串頭部有空格,空格會被自動去除。parseInt(' 81') // 81
如果parseInt的參數不是字符串,則會先轉爲字符串再轉換。
parseInt(1.23) // 1
// 等同於
parseInt('1.23') // 1
字符串轉爲整數的時候,是一個個字符依次轉換,如果遇到不能轉爲數字的字符,就不再進行下去,返回已經轉好的部分,如:
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15
如果字符串的第一個字符不能轉化爲數字(後面跟着數字的正負號除外),返回NaN。
parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') // NaN
parseInt('+1') // 1
注:parseInt()方法默認轉換成十進制,不過需要注意的是,如果參數本身就是number類型,且是0x開頭(16進制),或0o開頭(八進制),0b開頭(二進制),0開頭且後面的數字沒有8和9(視爲八進制),那麼parseInt方法會將其以相應的進制轉換成十進制展示出來。另外,如果參數是0x開頭的字符串,也會以16進制解析,如parseInt('0x10') // 16
因此,爲了防止意外解析成其他進制,建議添加第二個參數按照特定進制解析:如:
parseInt('0x16'); // 22
parseInt('0x16',10) ; // 0
parseFloat()
parseFloat方法用於將一個字符串轉爲浮點數。parseFloat('3.14') // 3.14
如果字符串符合科學計數法,則會進行相應的轉換。
parseFloat('314e-2') // 3.14
parseFloat('0.0314E+2') // 3.14
如果字符串包含不能轉爲浮點數的字符,則不再進行往後轉換,返回已經轉好的部分。如:parseFloat('3.14more non-digit characters') // 3.14
parseFloat方法會自動過濾字符串前導的空格。如:parseFloat('\t\v\r12.34\n ') // 12.34
如果參數不是字符串,或者字符串的第一個字符不能轉化爲浮點數,則返回NaN。
parseFloat([]) // NaN
parseFloat('FF2') // NaN
parseFloat('') // NaN 注意,parseFloat會將空字符串轉爲NaN。
parseFloat的轉換結果不同於Number函數的地方如下:
parseFloat(true) // NaN
Number(true) // 1
parseFloat(null) // NaN
Number(null) // 0
parseFloat('') // NaN
Number('') // 0
parseFloat('123.45#') // 123.45
Number('123.45#') // NaN
- 程序員愛用的非常規方法:字符串 - 0
如:'22' - 0
- 更加非常規的方法: + 字符串,這裏的+並不是取正值的意思,負數一樣可行
如:+ '22'
、+ '-011' ; // -11
注意:上面介紹的使用運算符的轉換方法,字符串不能有除了數字外的其他字符(正負號,表示進制的標識除外),字符串中,0b、0x、0o開頭會以對應表示的進制解析,如果是’011’,則會解析成十進制而不是二進制,小數同樣可以用這兩種方法進行轉換:
'011'-0 // 11
'0b11'-0 // 3
'0o11'-0 // 9
'0x11'-0 // 17
'0x11.1'-0 // NaN
'011.1'-0 // 11.1
3、如何將將其他數據類型轉換爲Boolean布爾類型?
- 常規方法:
Boolean()
,如:
Boolean("ss"); // true
Boolean({}); // true
- 程序員喜歡用的非常規方法 ,雙重取反:
!! x
,如:
!!"ss" ; // true
!!NaN ; // false
!!{} ; // true
- 五個falsy值,即轉換成Boolean後爲false的值:
0 、 NaN 、 null 、 undefined 、‘’(空字符串)
4、關於JS中的數據在內存中的存儲方式
內存
什麼是內存呢,舉個例子:你買一個8G 的內存條,操作系統開機即佔用 512MB,Chrome 打開即佔用 1G 內存, Chrome 各每個網頁分配一定數量的內存, 這些內存要分給頁面渲染器、網絡模塊、瀏覽器外殼和 JS 引擎(V8引擎)。
JS 引擎將內存分爲代碼區和數據區, 我們只研究數據區。 數據區分爲 Stack(棧內存) 和 Heap(堆內存)。 簡單類型的數據(如Number,string等)直接存在 Stack 裏, 複雜類型的數據(object對象)是把 Heap 地址存在 Stack 裏。如圖:
對象的存儲方式
上一條說了,對象的存儲方式是在stack內存存儲一個地址,形成對對象的引用,地址指向heap內存的某個位置,這樣才能達到可以隨時爲對象添加或刪除內容的目的。所以有句話這麼說,object是對象的引用。它在stack內存存的是地址,而不像其他數據類型直接把內容存在stack內存。內存圖
因此,這裏就要引入“內存圖”的概念,方便解決一些複雜問題,具體的內存圖樣式和應用會在第六部分的面試題中體現。垃圾回收
JS中的垃圾回收機制:如果一個對象沒有被引用,他就是垃圾,將會被回收。
內存泄漏就和垃圾回收機制有一定的聯繫:由於瀏覽器的一些bug,使得本應被被標記爲垃圾的數據沒有被標記,而這些垃圾數據佔用的內存將永遠被佔用,哪怕你把當前頁面關掉都不會被釋放,除非直接關掉整個瀏覽器。(IE6就有此類bug)
5、深拷貝與淺拷貝
- 深拷貝:
先舉個例子:
var a = 1;
var b = a;
b = 2 ;
那麼經過了上述操作,很明顯a的值仍然還是1。
即 b 變而不影響 a,就叫深拷貝(簡單數據類型的賦值都是深拷貝)
- 淺拷貝
同樣舉個栗子先:
var a = {x:1} ;
var b = a ;
b.x = 2;
經過了上述操作,明顯,a.x也變成了2.
即 b 變而導致了a變,這就是淺拷貝。
- 那麼有人就問了,難道對象就不能實現深拷貝了嗎?
當然……不是,對象的深拷貝的基本原理大概就是在b = a
的這個環節,讓 a所引用的對象複製一份,然後重新存在heap內存中的另一個位置,然後b再引用新生成的對象的地址,這樣就實現了對象的深拷貝,不過具體代碼很複雜,這裏暫且不表。
6、幾道面試題目
- 題目一:
var a = 1
var b = a
b = 2
請問 a 顯示是幾?
內存圖解決思路:
- 題目二:
var a = {name: 'a'}
var b = a
b = {name: 'b'}
請問現在 a.name 是多少?
- 題目三:
var a = {name: 'a'}
var b = a
b.name = 'b'
請問現在 a.name 是多少?
- 題目四:
var a = {name: 'a'}
var b = a
b = null
請問現在 a 是什麼?
- 題目五:
var a = {n:1};
var b = a;
a.x = a = {n:2};
alert(a.x);
alert(b.x);
這道題最坑的一點在這句話a.x = a = {n:2};
,很顯然如果在日常工作中這麼寫代碼遲早得被打死,但是既然作爲題目被寫出來了,那咱們還是先仔細分析一下這句話:
賦值是從右到左的,但不要被繞暈了, 其實很簡單,從運算符優先級來考慮
a.x = a = {n:2};
.運算優先於=賦值運算,因此此處賦值可理解爲
聲明a對象中的x屬性,用於賦值,此時b指向a,同時擁有未賦值的x屬性
對a對象賦值,此時變量名a改變指向到對象{n:2}
對步驟1中x屬性,也即a原指向對象的x屬性,也即b指向對象的x屬性賦值
賦值結果:
a => {n: 2}
b => {n: 1, x: {n: 2 } }
然後老辦法,我們畫個內存圖分析一下吧:
7、JS中對象的循環引用
什麼是對象的循環引用呢?舉個例子先:
var obj = {name:'enoch'} ;
obj.self = obj;
obj.self.self.self.name; // "enoch"
上述代碼就可以實現對象的循環引用,原理是:爲對象添加一個屬性,屬性值就是對象自己(在內存中就是對象引用的地址),從而達到可以通過引用該對象的self屬性來引用對象本身。