Cách phát hiện một biến có phải là một mảng hay không


101

Phương pháp trình duyệt chéo tiêu chuẩn de-facto tốt nhất để xác định xem một biến trong JavaScript có phải là một mảng hay không?

Tìm kiếm trên web có một số gợi ý khác nhau, một số tốt và một số không hợp lệ.

Ví dụ, sau đây là một cách tiếp cận cơ bản:

function isArray(obj) {
    return (obj && obj.length);
}

Tuy nhiên, hãy lưu ý điều gì sẽ xảy ra nếu mảng trống, hoặc obj thực sự không phải là một mảng nhưng thực hiện thuộc tính độ dài, v.v.

Vậy cách triển khai nào là tốt nhất trong điều kiện hoạt động thực sự, trên nhiều trình duyệt và vẫn hoạt động hiệu quả?


4
Điều này sẽ không trả về true trên một chuỗi?
James Hugard

Ví dụ được đưa ra không phải để trả lời câu hỏi mà chỉ là một ví dụ về cách tiếp cận một giải pháp - điều này thường thất bại trong những trường hợp đặc biệt (như câu này, do đó là "Tuy nhiên, lưu ý ...").
stpe

@James: trong hầu hết các trình duyệt (loại trừ IE), các chuỗi có dạng mảng (tức là có thể truy cập thông qua các chỉ mục số)
Christoph

1
không thể' tin rằng đây là rất khó khăn để làm ...
Claudiu

Câu trả lời:


160

Kiểm tra kiểu của các đối tượng trong JS được thực hiện thông qua instanceof, tức là

obj instanceof Array

Điều này sẽ không hoạt động nếu đối tượng được vượt qua các ranh giới khung vì mỗi khung có Arrayđối tượng riêng . Bạn có thể giải quyết vấn đề này bằng cách kiểm tra thuộc tính [[Class]] nội bộ của đối tượng. Để có được nó, hãy sử dụng Object.prototype.toString()(điều này được đảm bảo hoạt động bởi ECMA-262):

Object.prototype.toString.call(obj) === '[object Array]'

Cả hai phương thức sẽ chỉ hoạt động cho các mảng thực tế chứ không phải các đối tượng dạng mảng như argumentsdanh sách đối tượng hoặc nút. Vì tất cả các đối tượng giống mảng phải có thuộc tính số length, tôi sẽ kiểm tra những điều này như sau:

typeof obj !== 'undefined' && obj !== null && typeof obj.length === 'number'

Xin lưu ý rằng các chuỗi sẽ vượt qua kiểm tra này, điều này có thể dẫn đến sự cố vì IE không cho phép truy cập vào các ký tự của chuỗi theo chỉ mục. Do đó, bạn có thể muốn thay đổi typeof obj !== 'undefined'thành typeof obj === 'object'để loại trừ các đối tượng gốc và đối tượng lưu trữ có các loại khác biệt với tất cả 'object'. Điều này sẽ vẫn cho phép các đối tượng chuỗi đi qua, điều này sẽ phải được loại trừ theo cách thủ công.

Trong hầu hết các trường hợp, điều bạn thực sự muốn biết là liệu bạn có thể lặp lại đối tượng thông qua các chỉ mục số hay không. Do đó, bạn nên kiểm tra xem đối tượng có thuộc tính được đặt tên 0thay thế hay không, có thể được thực hiện thông qua một trong các kiểm tra sau:

typeof obj[0] !== 'undefined' // false negative for `obj[0] = undefined`
obj.hasOwnProperty('0') // exclude array-likes with inherited entries
'0' in Object(obj) // include array-likes with inherited entries

Việc ép kiểu thành đối tượng là cần thiết để hoạt động chính xác đối với các nguyên mẫu giống như mảng (tức là chuỗi).

Đây là mã để kiểm tra mạnh mẽ các mảng JS:

function isArray(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
}

và các đối tượng giống mảng có thể lặp lại (tức là không rỗng):

function isNonEmptyArrayLike(obj) {
    try { // don't bother with `typeof` - just access `length` and `catch`
        return obj.length > 0 && '0' in Object(obj);
    }
    catch(e) {
        return false;
    }
}

7
Tóm tắt tuyệt vời về tính năng hiện đại trong kiểm tra mảng js.
Nosredna

Kể từ MS JS 5.6 (IE6?), Toán tử "instanceof" bị rò rỉ rất nhiều bộ nhớ khi chạy đối tượng COM (ActiveXObject). Chưa kiểm tra JS 5.7 hoặc JS 5.8, nhưng điều này có thể vẫn đúng.
James Hugard

1
@James: thú vị - Tôi không biết về vụ rò rỉ này; Dù sao, có một cách khắc phục dễ dàng: trong IE, chỉ các đối tượng JS gốc mới có một hasOwnPropertyphương thức, vì vậy chỉ cần thêm tiền tố của bạn instanceofvới obj.hasOwnProperty && ; Ngoài ra, đây vẫn là một vấn đề với IE7? các bài kiểm tra đơn giản của tôi thông qua trình quản lý tác vụ cho thấy rằng bộ nhớ đã được lấy lại sau khi thu nhỏ trình duyệt ...
Christoph

@Christoph - Không chắc chắn về IE7, nhưng IIRC điều này không có trong danh sách các bản sửa lỗi cho JS 5.7 hoặc 5.8. Chúng tôi lưu trữ công cụ JS cơ bản ở phía máy chủ trong một dịch vụ, do đó, việc giảm thiểu giao diện người dùng không được áp dụng.
James Hugard

1
@TravisJ: xem ECMA-262 5.1, mục 15.2.4.2 ; tên lớp nội bộ theo quy ước chữ hoa - xem phần 8.6.2
Christoph

42

Sự xuất hiện của ECMAScript 5 Edition cung cấp cho chúng tôi phương pháp kiểm tra chắc chắn nhất nếu một biến là một mảng, Array.isArray () :

Array.isArray([]); // true

Mặc dù câu trả lời được chấp nhận ở đây sẽ hoạt động trên các khung và cửa sổ cho hầu hết các trình duyệt, nhưng nó không dành cho Internet Explorer 7 trở xuống , vì Object.prototype.toStringđược gọi trên một mảng từ một cửa sổ khác sẽ trả về [object Object], không [object Array]. IE 9 dường như cũng đã khắc phục hiện tượng này (xem bản sửa lỗi cập nhật bên dưới).

Nếu bạn muốn một giải pháp hoạt động trên tất cả các trình duyệt, bạn có thể sử dụng:

(function () {
    var toString = Object.prototype.toString,
        strArray = Array.toString(),
        jscript  = /*@cc_on @_jscript_version @*/ +0;

    // jscript will be 0 for browsers other than IE
    if (!jscript) {
        Array.isArray = Array.isArray || function (obj) {
            return toString.call(obj) == "[object Array]";
        }
    }
    else {
        Array.isArray = function (obj) {
            return "constructor" in obj && String(obj.constructor) == strArray;
        }
    }
})();

Nó không hoàn toàn không thể phá vỡ, nhưng nó sẽ chỉ bị phá vỡ bởi ai đó cố gắng phá vỡ nó. Nó giải quyết các vấn đề trong IE7 trở xuống và IE9. Lỗi vẫn tồn tại trong IE 10 PP2 , nhưng nó có thể được sửa trước khi phát hành.

Tái bút, nếu bạn không chắc chắn về giải pháp thì tôi khuyên bạn nên kiểm tra nội dung của nó và / hoặc đọc bài đăng trên blog. Có những giải pháp tiềm năng khác ở đó nếu bạn không thoải mái khi sử dụng biên dịch có điều kiện.


Câu trả lời được chấp nhận làm tốt công việc trong IE8 +, nhưng không phải trong IE6,7
user123444555621

@ Pumbaa80: Bạn nói đúng :-) IE8 đã khắc phục sự cố cho Object.prototype.toString của chính nó, nhưng thử nghiệm một mảng được tạo trong chế độ tài liệu IE8 được chuyển sang chế độ tài liệu IE7 trở xuống, sự cố vẫn tiếp diễn. Tôi chỉ thử nghiệm kịch bản này chứ không phải ngược lại. Tôi đã chỉnh sửa để chỉ áp dụng bản sửa lỗi này cho IE7 trở xuống.
Andy E

WAAAAAAA, tôi ghét IE. Tôi hoàn toàn quên mất các "chế độ tài liệu", hoặc "chế độ schizo", như tôi sẽ gọi chúng.
user123444555621

Mặc dù các ý tưởng tuyệt vời và có vẻ tốt, nhưng điều này không phù hợp với tôi trong IE9 với cửa sổ bật lên. Nó trả về false cho các mảng được tạo bởi trình mở ... Có giải pháp tương thích IE9 không? (Vấn đề dường như là IE9 implemnets Array.isArray chính nó, mà trả về false đối với trường hợp nhất định, khi nó không nên.
Steffen Heil

@Steffen: thú vị, vì vậy việc triển khai gốc của isArraykhông trả về true từ các mảng được tạo trong các chế độ tài liệu khác? Tôi sẽ phải xem xét điều này khi tôi nhận được một thời gian, nhưng tôi nghĩ rằng điều tốt nhất để làm là tập tin lỗi trên Connect để nó có thể được cố định trong IE 10
Andy E

8

Crockford có hai câu trả lời trên trang 106 của "Những phần hay". Cái đầu tiên kiểm tra hàm tạo, nhưng sẽ cho âm tính giả trên các khung hoặc cửa sổ khác nhau. Đây là thứ hai:

if (my_value && typeof my_value === 'object' &&
        typeof my_value.length === 'number' &&
        !(my_value.propertyIsEnumerable('length')) {
    // my_value is truly an array!
}

Crockford chỉ ra rằng phiên bản này sẽ xác định argumentsmảng là một mảng, mặc dù nó không có bất kỳ phương thức mảng nào.

Cuộc thảo luận thú vị của anh ấy về vấn đề này bắt đầu từ trang 105.

Có một cuộc thảo luận thú vị hơn nữa (sau Phần hay) ở đây bao gồm đề xuất này:

var isArray = function (o) {
    return (o instanceof Array) ||
        (Object.prototype.toString.apply(o) === '[object Array]');
};

Tất cả các cuộc thảo luận khiến tôi không bao giờ muốn biết liệu một cái gì đó có phải là một mảng hay không.


1
điều này sẽ phá vỡ trong IE đối với các đối tượng chuỗi và loại trừ các chuỗi nguyên thủy, giống như mảng ngoại trừ trong IE; kiểm tra [[Lớp]] tốt hơn nếu bạn muốn các mảng thực tế; nếu bạn muốn các đối tượng thích mảng, việc kiểm tra imo có quá hạn chế
Christoph

@ Christoph - Tôi đã thêm một chút nữa qua một bản chỉnh sửa. Chủ đề hấp dẫn.
Nosredna

2

jQuery triển khai một hàm isArray, điều này gợi ý cách tốt nhất để làm điều này là

function isArray( obj ) {
    return toString.call(obj) === "[object Array]";
}

(đoạn trích được lấy từ jQuery v1.3.2 - được điều chỉnh một chút để phù hợp với ngữ cảnh)


Chúng trả về false trên IE (# 2968). (Từ nguồn jquery)
razzed

1
Đó là lời nhận xét trong nguồn jQuery dường như đề cập đến chức năng isFunction, không isArray
Mario Menger

4
bạn nên sử dụng Object.prototype.toString()- điều đó ít có khả năng bị hỏng hơn
Christoph

2

Ăn trộm từ guru John Resig và jquery:

function isArray(array) {
    if ( toString.call(array) === "[object Array]") {
        return true;
    } else if ( typeof array.length === "number" ) {
        return true;
    }
    return false;
}

2
Các thử nghiệm thứ hai sẽ trở lại đúng đối với một chuỗi cũng như: typeof "abc" .length === "number" // true
Daniel Vandersluis

2
Thêm vào đó, tôi đoán bạn không bao giờ nên đặt tên kiểu hardcode, như "number". Hãy thử so sánh nó với con số thực tế thay vào đó, như typeof (obj) == typeof (42)
ohnoes

5
@mtod: tại sao loại tên không nên được mã hóa cứng? sau khi tất cả, các giá trị trả về typeofđược tiêu chuẩn hóa?
Christoph

1
@ohnoes giải thích cho mình
Pacerier

1

Bạn sẽ làm gì với giá trị sau khi bạn quyết định nó là một mảng?

Ví dụ: nếu bạn định liệt kê các giá trị được chứa nếu nó trông giống như một mảng HOẶC nếu nó là một đối tượng đang được sử dụng làm bảng băm, thì đoạn mã sau sẽ đạt được những gì bạn muốn (mã này dừng lại khi hàm đóng trả về bất kỳ thứ gì khác hơn "undefined". Lưu ý rằng nó KHÔNG lặp lại qua các vùng chứa hoặc bảng liệt kê COM; đó là một bài tập cho người đọc):

function iteratei( o, closure )
{
    if( o != null && o.hasOwnProperty )
    {
        for( var ix in seq )
        {
            var ret = closure.call( this, ix, o[ix] );
            if( undefined !== ret )
                return ret;
        }
    }
    return undefined;
}

(Lưu ý: kiểm tra "o! = Null" cho cả null và undefined)

Ví dụ về việc sử dụng:

// Find first element who's value equals "what" in an array
var b = iteratei( ["who", "what", "when" "where"],
    function( ix, v )
    {
        return v == "what" ? true : undefined;
    });

// Iterate over only this objects' properties, not the prototypes'
function iterateiOwnProperties( o, closure )
{
    return iteratei( o, function(ix,v)
    {
        if( o.hasOwnProperty(ix) )
        {
            return closure.call( this, ix, o[ix] );
        }
    })
}

mặc dù câu trả lời có thể là thú vị, nó không thực sự có bất cứ điều gì để làm với các câu hỏi (và btw: iterating trên mảng qua for..inlà xấu [tm])
Christoph

@Christoph - Chắc chắn rồi. Phải có một số lý do để quyết định xem một cái gì đó có phải là Mảng hay không: bởi vì bạn muốn làm điều gì đó với các giá trị. Những điều điển hình nhất (ít nhất là trong mã của tôi) là ánh xạ, lọc, tìm kiếm hoặc biến đổi dữ liệu trong mảng. Hàm trên thực hiện chính xác điều đó: nếu thứ được truyền có các phần tử có thể được lặp lại, thì nó sẽ lặp lại chúng. Nếu không, thì nó không làm gì cả và trả về không xác định một cách vô hại.
James Hugard

@Christoph - Tại sao lặp lại các mảng với for..in không tốt [tm]? Bạn sẽ lặp lại các mảng và / hoặc bảng băm (đối tượng) bằng cách nào khác?
James Hugard

1
@James: for..inlặp qua các thuộc tính có thể liệt kê của các đối tượng; bạn không nên sử dụng nó với mảng vì: (1) nó chậm; (2) nó không được đảm bảo để duy trì trật tự; (3) nó sẽ bao gồm bất kỳ bộ thuộc tính nào do người dùng xác định trong đối tượng hoặc bất kỳ nguyên mẫu nào của nó vì ES3 không bao gồm bất kỳ cách nào để đặt thuộc tính DontEnum; có thể có những vấn đề khác đã làm tôi suy nghĩ
Christoph

1
@Christoph - Mặt khác, việc sử dụng for (;;) sẽ không hoạt động đúng với các mảng thưa thớt và nó sẽ không lặp lại các thuộc tính của đối tượng. # 3 được coi là hình thức xấu cho Object do lý do bạn đề cập. Mặt khác, bạn rất đúng về hiệu suất: for..in chậm hơn ~ 36 lần so với for (;;) trên mảng phần tử 1M. Chà. Thật không may, không áp dụng cho trường hợp sử dụng chính của chúng tôi, đó là lặp lại các thuộc tính đối tượng (hashtables).
James Hugard

1

Nếu bạn đang làm việc này trong CouchDB (SpiderMonkey) thì hãy sử dụng

Array.isArray(array)

như array.constructor === Arrayhoặc array instanceof Arraykhông hoạt động. Sử dụng array.toString() === "[object Array]"có hiệu quả nhưng có vẻ khá khó so sánh.


Điều này hoạt động trong Node.js và cả trong các trình duyệt, không chỉ CouchDB: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Karl Wilbur


0

Trên w3school có một ví dụ nên khá chuẩn.

Để kiểm tra xem một biến có phải là một mảng hay không, họ sử dụng một cái gì đó tương tự như thế này

function arrayCheck(obj) { 
    return obj && (obj.constructor==Array);
}

đã thử nghiệm trên Chrome, Firefox, Safari, ie7


sử dụng constructorđể kiểm tra kiểu là imo quá giòn; sử dụng một trong những lựa chọn thay thế được đề xuất thay thế
Christoph

Tại sao bạn nghĩ vậy? Về độ giòn?
Kamarey

@Kamarey: constructorlà một thuộc tính DontEnum thông thường của đối tượng nguyên mẫu; điều này có thể không phải là vấn đề đối với các kiểu tích hợp miễn là không ai làm điều gì ngu ngốc, nhưng đối với các kiểu do người dùng định nghĩa thì có thể dễ dàng; lời khuyên của tôi: luôn sử dụng instanceof, kiểm tra chuỗi nguyên mẫu và không dựa vào các thuộc tính có thể bị ghi đè tùy ý
Christoph

1
Cảm ơn, tìm thấy lời giải thích khác ở đây: thinkweb2.com/projects/prototype/...
Kamarey

Điều này không đáng tin cậy, vì bản thân đối tượng Array có thể bị ghi đè bằng một đối tượng tùy chỉnh.
Josh Stodola

-2

Bạn có thể tìm thấy một trong những phiên bản được nghiên cứu và thảo luận tốt nhất của hàm này trên trang PHPJS . Bạn có thể liên kết đến các gói hoặc bạn có thể vào trực tiếp chức năng . Tôi thực sự khuyên bạn nên sử dụng trang web cho các hàm PHP tương đương được xây dựng tốt trong JavaScript.


-2

Không đủ tham chiếu bằng các hàm tạo. Đôi khi chúng có các tham chiếu khác nhau của hàm tạo. Vì vậy, tôi sử dụng các biểu diễn chuỗi của chúng.

function isArray(o) {
    return o.constructor.toString() === [].constructor.toString();
}

bị lừa bởi{constructor:{toString:function(){ return "function Array() { [native code] }"; }}}
Bergi

-4

Thay thế Array.isArray(obj)bởiobj.constructor==Array

mẫu :

Array('44','55').constructor==Array trả về true (IE8 / Chrome)

'55'.constructor==Array trả về false (IE8 / Chrome)


3
Tại sao bạn lại thay thế để sửa chức năng bằng một chức năng khủng khiếp?
Bergi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.