在 Javascript 中,如何判斷一個變數是否是陣列?
最好的方式是用 ES5 提供的 Array.isArray()
方法(畢竟原生的才是最屌的):
1 2 |
var a = [0, 1, 2]; console.log(Array.isArray(a)); // true |
但是鑑於低版本 IE 不支援 ES5,如需相容,需要想想別的辦法。
typeof
?
我們都知道,陣列是特殊的物件,所以陣列的 typeof 結果也是 object,而因為 null 的結果也是 object,所以如需用 typeof 運算子來判斷陣列,需要這麼寫:
1 2 3 |
var a = [0, 1, 2]; // 是 object 同時排除 null、排除純物件 console.log(typeof a === 'object' & a !== null && Object.prototype.toString.call(a) !== '[object Object]'); // true |
instanceof
?
來回憶下 instanceof 運算子的使用方式。a instanceof b,如果返回 true,表示 a 是 b 的一個例項。那麼如果 a instanceof Array 返回 true,是不是就說明 a 是 陣列型別呢?跟 instanceof 師出同門的還有 constructor,是否同樣可以判斷呢?
1 2 3 |
var a = [0, 1, 2]; console.log(a instanceof Array); // true 就是陣列? console.log(a.constructor === Array); // true 陣列? |
答案是否定的,需要注意巢狀 frame 的情況。
index.htm 程式碼:
1 2 3 4 5 |
window.onload = function() { var a = window.frames[0].a; console.log(a instanceof Array); // false console.log(a.constructor === Array); // false }; |
a.htm 程式碼:
1 |
window.a = [1, 2, 3]; |
我們看到 index.htm 程式碼中,變數 a 確實是一個陣列,但是 a instanceof Array 的結果卻是 false。
這是因為每個 frame 都有一套自己的執行環境,跨 frame 例項化的物件彼此不共享原型鏈。如果列印 a instanceof window.frames[0].Array,那麼結果就是 true 了。
特性嗅探?
1 2 3 4 5 |
var a = [0, 1, 2]; if (a.sort) { // 是陣列 } |
也不靠譜,萬一某個物件正好有值為 sort 的 key 呢?
1 2 3 4 5 6 |
var a = {sort: 'me'}; if (a.sort) { // 陣列? // 其實我真的不是陣列 } |
正確的姿勢是使用 Object.prototype.toString() 判斷:
1 2 |
var a = [0, 1, 2]; console.log(Object.prototype.toString.call(a) === '[object Array]'); // true |
事實上,這也是一些類庫進行陣列(甚至其他型別)判斷的主流方式。
比如在 jQuery 中進行陣列判斷的相關程式碼(PS:摘自 jQuery 1.10.1,最近版本的 jQuery 只保留了 Array.isArray(),沒有對不支援 ES5 的瀏覽器做相容):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
isArray: Array.isArray || function( obj ) { return jQuery.type(obj) === "array"; }, type: function( obj ) { if ( obj == null ) { return String( obj ); } return typeof obj === "object" || typeof obj === "function" ? class2type[ core_toString.call(obj) ] || "object" : typeof obj; }, jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); |
程式碼很清晰,如支援原生的 Array.isArray(),則直接判斷,不支援的話呼叫 toString() 進行判斷。同時可以看到很多其他型別變數的判斷也是基於 toString() 方法。當然這裡說的 toString() 均是 Object 原型鏈上的 toString() 方法。
1 2 3 4 5 6 7 8 9 10 |
console.log(Object.prototype.toString.call(10)); // [object Number] console.log(Object.prototype.toString.call('hello')); // [object String] console.log(Object.prototype.toString.call(true)); // [object Boolean] console.log(Object.prototype.toString.call([])); // [object Array] console.log(Object.prototype.toString.call({})); // [object Object] console.log(Object.prototype.toString.call(function(){})); // [object Function] console.log(Object.prototype.toString.call(/a/g)); // [object RegExp] console.log(Object.prototype.toString.call(null)); // [object Null] console.log(Object.prototype.toString.call(undefined)); // [object Undefined] console.log(Object.prototype.toString.call(new Date())); // [object Date] |
Object.prototype.toString() 為何能返回這樣型別的字串?
ECMA-262:
1 2 3 4 |
Object.prototype.toString( ) When the toString method is called, the following steps are taken: 1. Get the [[Class]] property of this object. 2. Compute a string value by concatenating the three strings “[object “, Result (1), and “]”. 3. Return Result (2) |
上面的規範定義了 Object.prototype.toString 的行為:首先,取得物件的一個內部屬性[[Class]],然後依據這個屬性,返回一個類似於 “[object Array]” 的字串作為結果([[]]用來表示語言內部用到的、外部不可直接訪問的屬性,稱為 “內部屬性”)。利用這個方法,再配合 call,我們可以取得任何物件的內部屬性 [[Class]],然後把型別檢測轉化為字串比較,以達到我們的目的。還是先來看看在 ECMA 標準中 Array 的描述吧:
1 2 |
new Array([ item0[, item1 [,…]]]) The [[Class]] property of the newly constructed object is set to “Array”. |
所以 Javascript 中判斷陣列的函式可以這樣寫:
1 2 3 |
function isArray(a) { Array.isArray ? Array.isArray(a) : Object.prototype.toString.call(a) === '[object Array]'; } |
Read More: