【資料型別】JavaScript資料型別&聊聊Object.prototype.toString
一、資料型別
JavaScript定義了7種基本資料型別:
- Symbol (ES6)
- String
- Number
- Null
- Boolean
- Undefined
- Object
JavaScript是一種弱型別指令碼語言
,即定義變數時不需要宣告變數的型別,在程式運作過程中會自動判斷變數的型別,定義變數的方法:
- var
- let (ES6)
- const (ES6)
不新增關鍵詞var/let/const也能定義變數,此時會建立一個全域性變數,且變數不會提升
二、值型別和引用型別
除了以上型別的劃分,變數還分為值型別和引用型別,在傳遞過程中分為值傳遞和引用傳遞。值傳遞只會傳遞變數的值,引用傳遞會傳遞變數的儲存地址(指標)
var a = 1,
b = {name: 1};
var c = a; //值傳遞,a會賦值給c
console.log(c); // 1
c = 2;
console.log(a); // 1 值傳遞相當於記憶體中複製了一份,c和a沒有關係
var d = b;
d.name = 2;
console.log(b.name); // 2 引用傳遞傳遞的是地址,所以b和d指向同一記憶體地址
三、判斷資料型別的方法
我們很多時候需要對資料的型別進行判斷,然後進行相關的操作,判斷資料型別常見有以下幾種方法:
- typeof
- instanceof / constructor
- Object.prototype.toString.call()
四、typeof
呼叫typeof方法時會返回以下幾種值之一:
- symbol
- string
- number
- boolean
- undefined
- object
- function
typeof Symbol(); // symbol
typeof 'abc'; // string
typeof 123; // number
typeof NaN; // number
typeof true; // boolean
typeof undefined; // undefined
typeof {a:1}; // object
typeof [1,2,3]; // object
typeof function a(){}; // function
typeof null; // object
// typeof檢測一個未宣告的變數時會返回undefined
typeof undeclare_variable; // undefined
- typeof null => object 這是typeof 的一個bug,null並不是引用型別
Javascript語言第一版只設計了五種資料型別(物件、整數、浮點數、字串、布林值),沒考慮null,只把它當作object的一種特殊值,後來null獨立出來,作為一種單獨的資料型別,為了相容以前的程式碼就沒有改變null的型別- typeof [1,2,3] => object 除了函式返回的是function,其他引用型別返回的都是object,所以
typeof無法區分array和object
五、instanceof / constructor
instanceof和constructor都是用於例項和建構函式的對應,可以彌補typeof無法區分陣列和物件的問題
但是,instanceof只能用於非物件型別資料的判斷
[1,2,3] instanceof Array; // true
var a = {name: 1};
a instanceof Object; // true
1 instanceof Number; // false
constructor可以用於任何型別的判斷
var num = 1;
num.constructor === Number; // true
var arr = [1, 2, 3];
arr.constructor === Array; // true
但是,這兩種方法都不能用於跨iframe的情況
,例如
//pageOne中
...
<div>父頁面</div>
<iframe src="pageB.html" frameborder="0"></iframe>
<script>
//這裡定義了一個數組a
var a = [1, 2, 3];
console.log( a.constructor === Array ); // true
</script>
...
//pageTwo中
...
<div>子頁面</div>
<script>
//子頁面獲取父頁面中的值
var c = top.a;
console.log( c.constructor === Array ); // false
</script>
...
這是因為瀏覽器會把不同頁面中的Array做區分
,即pageOne和pageTwo的Array是不同的
兩個構造器,所以跨iframe的變數無法通過這兩種方法來判斷
六、Object.prototype.toString.call()
使用typeof無法區分null、array、date
instanceof無法運用在值型別區分
沒事,我們還有一種方法,Object.prototype.toString.call()
Object.prototype.toString.call()是一種比較常用,也是比較有效的方法
var a = {name: 1};
Object.prototype.toString.call(a); // [object Object]
var b = new Date();
Object.prototype.toString.call(b); // [object Date]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(undefined); // [object Undefined]
function a(){
console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
}
通過這種方式我們就可以解決常見問題中的型別判斷
那麼,為什麼Object.prototype.toString.call()可以區分資料型別?
七、探索為什麼Object.prototype.toString.call()可以區分資料型別
我們先來了解一下在呼叫Object.prototype.toString.call()的時候發生了什麼
1.在ECMA5中,呼叫Object.prototype.toString會執行以下步驟:
(1).如果值為undefined
,則返回"[object Undefiend]"
(2).如果值為null
,則返回"[object Null]"
(3).將傳遞的值呼叫ToObject
方法,並賦給O作為呼叫後的結果
(4).將O的內部屬性[[Class]]
的值賦給class
(5).結果返回字串,該字串由三個字串"[object ", class, and "]"
拼接而成
根據上面的描述我們可以看出來,主要通過物件的內建屬性
[[Class]]
來判斷
2.內建屬性[[Class]]
ECMA規範中這麼介紹[[Class]]
翻譯:本規範針對每種內建物件定義了[[Class]]內部屬性的值。物件的[[Class]]內部屬性的值可以是除了。。。之外的任何字串值。[[Class]]內部屬性的值用於內部區分不同型別的物件。 請注意,除了通過Object.prototype.toString外,本規範沒有提供任何方法讓程式訪問該值。
我們再來看規範中,在建立某種型別變數的時候,會對物件內建屬性[[Class]]
進行賦值
-
建立陣列時:
-
建立函式時:
-
建立字串時:
3.ES6中不再使用[[Class]]內建屬性進行判斷
最新規範中不再使用[[Class]]內建屬性進行判斷,在呼叫Object.prototype.toString時執行了以下步驟:
(這裡不詳細講,有興趣的可以去了解下)
綜上,大家應該瞭解了為什麼Object.prototype.toString.call()可以用來區分資料型別
一些問題
1.null和undefined不是物件,為什麼Object.prototype.toString.call()返回的值要採用[object *]的格式
這是ES5.1規範中的修正。 在ES5之前,將null或undefined傳遞給toString總是導致全域性物件被傳遞。 ES5中嚴格模式相關的更改會導致null和undefined被傳遞而不進行修改。 按照ES5的規定,將null或undefined傳遞給Object.prototype.toString會導致TypeError異常。 這個例外破壞了一些現有的程式碼,所以ES5.1修復這個規範,以防止這種情況發生。
那麼,為什麼toString返回null和undefined? 事實證明,很多現有的程式碼也期望Object.prototype.toString始終返回形式為“[object *]”的字串。 因此,ES5.1規範中決定將傳入null和undefined的值“[object Null]”和“[object Undefined]”。
2.Object.prototype.toString(‘foo’)和Object.prototype.toString.call(‘foo’)的區別
前者傳入的引數實際是Object.prototype,而Object.prototype是個物件,所以始終返回’[object Object]’,其中的’foo’會被忽略;而後者通過call,硬繫結到傳入的’foo’;所以千萬不要把call忘了