ECMAScirpt 變量有兩種不一樣的數據類型:基本類型,引用類型。也有其餘的叫法,好比原始類型和對象類型等。面試
JavaScript 有七種內置類型:
• 空值(null)
• 未定義(undefined)
• 布爾值(boolean)
• 數字(number)
• 字符串(string)
• 對象(object)
• 符號(symbol, ES6 中新增) 數組
除對象以外,其餘統稱爲「基本類型」。 對象稱爲「引用類型」。函數
咱們能夠用 typeof 運算符來查看值的類型,它返回的是類型的字符串值。有意思的是,這七種類型和它們的字符串值並不一一對應: spa
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 對它的處理有問題:
prototype
typeof null === "object"; // true
按照類型的定義,這裏正確的返回結果應該是 "null"。但這個 bug 由來已久,在 JavaScript 中已經存在了將近二十年,也許永遠也不會修復,由於這牽涉到太多的 Web 系統,「修復」它會產生更多的bug,令許多系統沒法正常工做。 scala
因此在平常開發中,爲了更好的檢測null類型,咱們須要使用複合條件來檢測 null 值的類型: 指針
var a = null; (!a && typeof a === "object"); // true
null 是基本類型中惟一的一個「假值」類型, typeof對它的返回值爲 "object"。code
這裏還有一張狀況,看下面:對象
typeof function a(){ /* .. */ } === "function"; // true
或許這裏你會說js的內置類型沒有function類型呀,爲何這裏typeof會返回"function"。在介紹裏面,function類型其實是 object 的一個「子類型」。具體來講,函數是「可調用對象」,它有一個內部屬性 [[Call]],該屬性使其能夠被調用。 blog
函數不只是對象,還能夠擁有屬性。例如:
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呢?
其實方法有不少,這裏羅列幾種給你們。
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 只能用來判斷兩個對象是否屬於原型鏈的關係, 而不能獲取對象的具體類型。
最強利器!!
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([ ])
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"
開頭曾說過,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) 老是經過值複製的方式來賦值 / 傳遞,包括null、 undefined、字符串、數字、布爾和 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 。
總之請謹記:咱們沒法自行決定使用值複製仍是引用複製,一切由值的類型來決定。