js類型----你所不知道的JavaScript系列(5)


ECMAScirpt 變量有兩種不同的數據類型:基本類型,引用類型。也有其他的叫法,比如原始類型和對象類型等。

1、內置類型

JavaScript 有七種內置類型:
 • 空值(null
 • 未定義(undefined
 • 布爾值(boolean
 • 數字(number
 • 字符串(string
 • 對象(object
 • 符號(symbolES6 中新增)

除對象之外,其他統稱為“基本類型”。 對象稱為“引用類型”。

我們可以用 typeof 運算符來查看值的類型,它返回的是類型的字符串值。有意思的是,這七種類型和它們的字符串值並不一一對應: 

typeof undefined === "undefined"; // true
typeof true === "boolean"; // true
typeof 42 === "number"; // true
typeof "42" === "string"; // true
typeof { life: 42 } === "object"; // true
// ES6中新加入的類型
typeof Symbol() === "symbol"; // true

以上六種類型均有同名的字符串值與之對應。 你可能注意到 null 類型不在此列。它比較特殊typeof 對它的處理有問題:

typeof null === "object"; // true

按照類型的定義,這里正確的返回結果應該是 "null"。但這個 bug 由來已久,在 JavaScript 中已經存在了將近二十年,也許永遠也不會修復,因為這牽涉到太多的 Web 系統,“修復”它會產生更多的bug,令許多系統無法正常工作。 

所以在日常開發中,為了更好的檢測null類型,我們需要使用復合條件來檢測 null 值的類型: 

var a = null;
(!a && typeof a === "object"); // true

null 是基本類型中唯一的一個“假值”類型, typeof對它的返回值為 "object"

這里還有一張情況,看下面:

typeof function a(){ /* .. */ } === "function"; // true

或許這里你會說js的內置類型沒有function類型呀,為什么這里typeof會返回"function"。在介紹里面,function類型實際上是 object 的一個“子類型”。具體來說,函數是“可調用對象”,它有一個內部屬性 [[Call]],該屬性使其可以被調用。 

函數不僅是對象,還可以擁有屬性。例如:

function a(b,c) {
   /* .. */
}

a.length; // 2  函數對象的 length 屬性是其聲明的參數的個數

a.name;  // "a" 函數對象的 name 屬性返回函數名

再來看看數組。 JavaScript 支持數組 ,數組也是對象,它也是 object 的一個“子類型”,數組的元素按數字順序來進行索引(而非普通像對象那樣通過字符串鍵值),其 length 屬性是元素的個數。

typeof [1,2,3] === "object"; // true

我們來比較一下下面的代碼:

typeof   null;   // "object"
typeof   [];   // "object"
typeof   {};   // "object"

返回的都是"object",那這樣我們要怎么知道區分對象,數組和null呢?

其實方法有很多,這里羅列幾種給大家。

 

1、instanceof

instanceof 是用來判斷 A 是否為 B 的實例對,表達式為:A instanceof B,如果A是B的實例,則返回true,否則返回false。 在這里需要特別注意的是:instanceof檢測的是原型。其內部原理大致可以這樣理解:

instanceof (A,B) = {
    var L = A.__proto__;
    var R = B.prototype;
    if(L === R) {
        //A的內部屬性__proto__指向B的原型對象
        return true;
    }
    return false;
}

雖然上述代碼很不嚴謹也有一些問題,但是從上述過程可以看出,當 A 的 __proto__ 指向 B 的 prototype 時,就認為A就是B的實例,我們再來看幾個例子:

[] instanceof Array;  // true
{} instanceof Object;  // true
new Date() instanceof Date;  // true
 
function Person(){};
new Person() instanceof Person;
 
[] instanceof Object;  // true

細心的你可能發現了 [] instanceof Array 和 [] instanceof Object 竟然都返回 true。為什么?回想一下前面我講過的“instanceof檢測的是原型”,在剛才的例子里面就涉及到了原型和原型鏈的概念。如果對原型和原型鏈不了解的同學,可以先去看看相關知識,這個知識點是十分重要的,不管是以后工作還是面試,都是非常重要的!那么。。。現在繼續賺回來看我們的例子。從原型鏈上來說 [].__proto__ 指向 Array.prototype, 而 Array.prototype.__proto__ 又指向了Object.prototype,Object.prototype.__proto__ 指向了null,標志着原型鏈的結束。所以按照 instanceof 的判斷規則,[] 就是Object的實例。當然,類似的new Date()、new Person() 也會形成這樣一條原型鏈,例如:new Date() instanceof Object 也會返回 true。因此,instanceof 只能用來判斷兩個對象是否屬於原型鏈的關系, 而不能獲取對象的具體類型。

 

2、Object.prototype.toString.call

最強利器!!

Object.prototype.toString.call('') ;   // "[object String]"
Object.prototype.toString.call(1) ;    // "[object Number]"
Object.prototype.toString.call(true) ; // "[object Boolean]"
Object.prototype.toString.call(undefined) ; // "[object Undefined]"
Object.prototype.toString.call(null) ; // "[object Null]"
Object.prototype.toString.call(new Function()) ; // "[object Function]"
Object.prototype.toString.call(new Date()) ; // [object Date]"
Object.prototype.toString.call([]) ; // "[object Array]"
Object.prototype.toString.call({}) ; //"[object Object]"

基本上所有對象的類型都可以通過這個方法獲取到。也可以簡寫成 toString.call ,例如 toString.call({ }),toString.call([ ])

 

 

2、值和類型

 JavaScript 中的變量是沒有類型的, 只有值才有。變量可以隨時持有任何類型的值。   ----《你所不知道的JavaScript(中)》P6

JavaScript 不做“類型強制”,也就是說,JavaScript 擁有動態類型,這意味着相同的變量可用作不同的類型。語言引擎不要求變量總是持有與其初始值同類型的值。一個變量可以現在被賦值為字符串類型值,隨后又被賦值為數字類型值。

42 的類型為 number,並且無法更改。而 "42" 的類型為 string。數字 42 可以通過強制類型轉換(coercion)為字符串 "42" 。

在對變量執行 typeof 操作時,得到的結果並不是該變量的類型,而是該變量持有的值的類型,因為 JavaScript 中的變量沒有類型

var a = 42;
typeof a; // "number"

a = true; typeof a; // "boolean"

 

 

3、值和引用

開頭曾說過,ECMAScirpt 變量有兩種不同的數據類型:基本類型,引用類型。為什么有這兩種區分呢?我們先來看下面的例子:

var a = 2;
var b = a; 
b++;
a; // 2
b; // 3

var c = [1,2,3]; var d = c; d.push( 4 ); c; // [1,2,3,4] d; // [1,2,3,4]

在JavaScript中,簡單值(即標量基本類型值, scalar primitive總是通過值復制的方式來賦值 / 傳遞,包括nullundefined、字符串、數字、布爾和 ES6 中的 symbol。 復合值(compound value)——對象(包括數組和封裝對象,參見第 3 章)和函數,則
通過引用復制的方式來賦值 / 傳遞。
上例中 2 是一個標量基本類型值,所以變量 a 持有該值的一個復本, b 持有它的另一個復
本。 b 更改時, a 的值保持不變。c d 則分別指向同一個復合值 [1,2,3] 的兩個不同引用。請注意, c d 僅僅是指向值[1,2,3],並非持有。所以它們更改的是同一個值(如調用 .push(4))。隨后它們都指向更改后的新值 [1,2,3,4]。 

 

由於引用指向的是值本身而非變量,所以一個引用無法更改另一個引用的指向。 

var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// 然后
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]

代碼前兩句使得a、b都是對復合值 [1,2,3] 的兩個不同引用,所以代碼第三第四句輸出的結果就都是值[1,2,3]。代碼第五句b=[4,5,6] 的執行使得變量b重新指向了另一個復合值,而不會修改的原先的復合值,也不會不影響 a 指向值 [1,2,3],所以a輸出的值依舊是復合值[1,2,3]。 

有了上面的例子之后,再來看看下面一段代碼,

function foo(x) {
   x.push( 4 );
   x; // [1,2,3,4]
   // 然后
   x = [4,5,6];
   x.push( 7 );
   x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[1,2,3,4],不是[4,5,6,7]

我們向函數傳遞 a 的時候,實際是將引用 a 的一個復本賦值給 x,而 a 仍然指向 [1,2,3]在函數中我們可以通過引用 x 來更改數組的值(push(4) 之后變為 [1,2,3,4])。但 x =[4,5,6] 並不影響 a 的指向,所以 a 仍然指向 [1,2,3,4]。 

我們不能通過引用 x 來更改引用 a 的指向,只能更改 a x 共同指向的值 。如果要將 a 的值變為 [4,5,6,7],必須更改 x 指向的數組,而不是為 x 賦值一個新的數組。

function foo(x) {
   x.push( 4 );
   x; // [1,2,3,4]
   // 然后
   x.length = 0; // 清空數組
   x.push( 4, 5, 6, 7 );
   x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[4,5,6,7],不是[1,2,3,4]

x.length = 0 x.push(4,5,6,7) 並沒有創建一個新的數組,而是更改了當前的數組。於是 a 指向的值變成了 [4,5,6,7]。 

關於引用類型的比較和基本類型的比較,我們來看下面的代碼:

//基礎類型的比較
var a = 1;
var b = 1;

a===b;   // true


//引用類型比較
var c = [1];
var d = c;
var e = [1];

c===d;   // true
c===e;   // false

基礎類型的比較是值的比較,只要值相等,就返回 true,而引用類型的比較是兩個引用的比較,上面例子中c、d都是對復合值 [1] 的應用,他們都指向同一個值,所以返回 true ,雖然 e 所指的值也是 [1],但是這個 [1] 和 c 所指的 [1] 不一定,c和e是兩個不同的引用,就像C++里面所說的指針,雖然兩個指針所指的內存區域存放的值一樣,但是這兩個指針所指的地址不一樣,在機器看來這就是兩個完全不同的變量,所以在比較的時候就返回 false 。

 

總之請謹記:我們無法自行決定使用值復制還是引用復制,一切由值的類型來決定。

 

 




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM