2018-06-10 JS 裏的數據類型轉換

前言:之前的博客介紹了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 裏。如圖:
    JS存儲數據

  • 對象的存儲方式
    上一條說了,對象的存儲方式是在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屬性來引用對象本身。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章