วิธีตรวจสอบว่าตัวแปรเป็นอาร์เรย์หรือไม่


102

วิธีการข้ามเบราว์เซอร์มาตรฐาน de-facto ที่ดีที่สุดคืออะไรในการพิจารณาว่าตัวแปรใน JavaScript เป็นอาร์เรย์หรือไม่

การค้นหาเว็บมีคำแนะนำที่แตกต่างกันบางคำดีและค่อนข้างไม่ถูกต้อง

ตัวอย่างเช่นต่อไปนี้เป็นแนวทางพื้นฐาน:

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

อย่างไรก็ตามโปรดสังเกตว่าจะเกิดอะไรขึ้นหากอาร์เรย์ว่างเปล่าหรือ obj ไม่ใช่อาร์เรย์ แต่ใช้คุณสมบัติความยาวเป็นต้น

ดังนั้นการนำไปใช้งานใดที่ดีที่สุดในแง่ของการทำงานจริงเป็นข้ามเบราว์เซอร์และยังทำงานได้อย่างมีประสิทธิภาพ


5
ผลตอบแทนนี้จะไม่เป็นจริงกับสตริงหรือไม่?
James Hugard

ตัวอย่างที่ให้มาไม่ได้มีไว้เพื่อตอบคำถามเองเป็นเพียงตัวอย่างของวิธีการแก้ปัญหาเท่านั้นซึ่งมักจะล้มเหลวในกรณีพิเศษ (เช่นกรณีนี้ด้วยเหตุนี้ "อย่างไรก็ตามโปรดทราบ ... ")
stpe

@ เจมส์: ในเบราว์เซอร์ส่วนใหญ่ (ยกเว้น IE) สตริงจะเหมือนอาร์เรย์ (เช่นสามารถเข้าถึงผ่านดัชนีตัวเลขได้)
Christoph

1
ไม่อยากจะเชื่อเลยว่านี่เป็นเรื่องยากที่จะทำ ...
Claudiu

คำตอบ:


162

การตรวจสอบประเภทของวัตถุใน JS ทำได้ผ่านทางinstanceofเช่น

obj instanceof Array

สิ่งนี้จะใช้ไม่ได้หากวัตถุถูกส่งข้ามขอบเขตเฟรมเนื่องจากแต่ละเฟรมมีArrayวัตถุของตัวเอง คุณสามารถแก้ปัญหานี้ได้โดยตรวจสอบคุณสมบัติ[[Class]] ภายในของออบเจ็กต์ ในการรับมันให้ใช้Object.prototype.toString()(สิ่งนี้รับประกันว่าจะทำงานโดย ECMA-262):

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

วิธีการทั้งสองจะใช้ได้เฉพาะกับอาร์เรย์จริงเท่านั้นไม่ใช่วัตถุที่มีลักษณะคล้ายอาร์เรย์เช่นรายการargumentsวัตถุหรือโหนด เนื่องจากวัตถุที่มีลักษณะคล้ายอาร์เรย์ทั้งหมดต้องมีlengthคุณสมบัติเป็นตัวเลขฉันจึงตรวจสอบสิ่งเหล่านี้ดังนี้:

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

โปรดทราบว่าสตริงจะผ่านการตรวจสอบนี้ซึ่งอาจนำไปสู่ปัญหาเนื่องจาก IE ไม่อนุญาตให้เข้าถึงอักขระของสตริงด้วยดัชนี ดังนั้นคุณอาจต้องการที่จะเปลี่ยนtypeof obj !== 'undefined'เพื่อtypeof obj === 'object'ที่จะไม่รวมวิทยาการและวัตถุโฮสต์ที่มีชนิดที่แตกต่างจาก'object' alltogether สิ่งนี้จะยังคงปล่อยให้สตริงออบเจ็กต์ผ่านซึ่งจะต้องถูกแยกออกด้วยตนเอง

ในกรณีส่วนใหญ่สิ่งที่คุณอยากรู้คือคุณสามารถทำซ้ำบนวัตถุผ่านดัชนีตัวเลขได้หรือไม่ ดังนั้นจึงควรตรวจสอบว่าวัตถุมีคุณสมบัติที่ตั้งชื่อ0แทนหรือไม่ซึ่งสามารถทำได้ผ่านการตรวจสอบอย่างใดอย่างหนึ่งต่อไปนี้:

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

การส่งไปยังออบเจ็กต์เป็นสิ่งที่จำเป็นในการทำงานอย่างถูกต้องสำหรับไพรมารีเหมือนอาร์เรย์ (เช่นสตริง)

นี่คือรหัสสำหรับการตรวจสอบอาร์เรย์ JS ที่มีประสิทธิภาพ:

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

และทำซ้ำได้ (เช่นไม่ว่างเปล่า) วัตถุคล้ายอาร์เรย์:

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
บทสรุปที่ยอดเยี่ยมของการตรวจสอบอาร์เรย์ js ที่ทันสมัย
Nosredna

ใน MS JS 5.6 (IE6?) ตัวดำเนินการ "instanceof" รั่วไหลของหน่วยความจำจำนวนมากเมื่อทำงานกับวัตถุ COM (ActiveXObject) ยังไม่ได้ตรวจสอบ JS 5.7 หรือ JS 5.8 แต่อาจยังคงเป็นจริง
James Hugard

1
@ เจมส์: น่าสนใจ - ฉันไม่รู้เรื่องการรั่วไหลนี้ อย่างไรก็ตามมีวิธีแก้ไขที่ง่าย: ใน IE มีเพียงวัตถุ JS ดั้งเดิมเท่านั้นที่มีhasOwnPropertyวิธีการดังนั้นเพียงแค่นำหน้าของคุณinstanceofด้วยobj.hasOwnProperty && ; ยังเป็นปัญหากับ IE7 หรือไม่ การทดสอบง่ายๆของฉันผ่านตัวจัดการงานแนะนำว่าหน่วยความจำได้รับการเรียกคืนหลังจากลดขนาดเบราว์เซอร์ ...
คริสตอฟ

@Christoph - ไม่แน่ใจเกี่ยวกับ IE7 แต่ IIRC ไม่อยู่ในรายการแก้ไขข้อบกพร่องสำหรับ JS 5.7 หรือ 5.8 เราโฮสต์เอ็นจิน JS พื้นฐานที่ฝั่งเซิร์ฟเวอร์ในบริการดังนั้นการย่อ UI จึงไม่สามารถใช้ได้
James Hugard

1
@TravisJ: ดูECMA-262 5.1 ส่วน 15.2.4.2 ; ชื่อชั้นเรียนภายในเป็นไปตามแบบแผนตัวพิมพ์ใหญ่ - ดูหัวข้อ 8.6.2
คริสตอฟ

43

การมาถึงของ ECMAScript 5th Edition ทำให้เรามีวิธีทดสอบที่แน่นอนที่สุดว่าตัวแปรคืออาร์เรย์Array.isArray () :

Array.isArray([]); // true

แม้ว่าคำตอบที่ยอมรับที่นี่จะใช้ได้กับเฟรมและหน้าต่างสำหรับเบราว์เซอร์ส่วนใหญ่ แต่ก็ไม่ได้ใช้กับ Internet Explorer 7 และต่ำกว่าเนื่องจากการObject.prototype.toStringเรียกอาร์เรย์จากหน้าต่างอื่นจะส่งกลับ[object Object]ไม่ใช่[object Array]ไม่ได้ดูเหมือนว่า IE 9 จะถดถอยพฤติกรรมนี้ด้วย (ดูการแก้ไขที่อัปเดตด้านล่าง)

หากคุณต้องการโซลูชันที่ใช้ได้กับทุกเบราว์เซอร์คุณสามารถใช้:

(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;
        }
    }
})();

มันไม่ได้แตกทั้งหมด แต่จะมี แต่ใครบางคนที่พยายามอย่างหนักที่จะทำลายมัน ใช้งานได้กับปัญหาใน IE7 และต่ำกว่าและ IE9 ข้อบกพร่องยังคงมีอยู่ใน IE 10 PP2แต่อาจได้รับการแก้ไขก่อนปล่อย

ปล. หากคุณไม่แน่ใจเกี่ยวกับวิธีแก้ปัญหาฉันขอแนะนำให้คุณทดสอบกับเนื้อหาในใจของคุณและ / หรืออ่านบทความในบล็อก มีวิธีแก้ปัญหาอื่น ๆ ที่เป็นไปได้หากคุณไม่สบายใจในการใช้การรวบรวมตามเงื่อนไข


คำตอบที่ยอมรับใช้งานได้ดีใน IE8 + แต่ไม่ใช่ใน IE6,7
user123444555621

@ Pumbaa80: คุณพูดถูก :-) IE8 แก้ไขปัญหาสำหรับ Object.prototype.toString ของตัวเอง แต่การทดสอบอาร์เรย์ที่สร้างในโหมดเอกสาร IE8 ที่ส่งผ่านไปยังโหมดเอกสาร IE7 หรือต่ำกว่าปัญหายังคงมีอยู่ ฉันได้ทดสอบสถานการณ์นี้เท่านั้นไม่ใช่วิธีอื่น ฉันได้แก้ไขเพื่อใช้การแก้ไขนี้กับ IE7 และต่ำกว่าเท่านั้น
Andy E

WAAAAAAA ฉันเกลียด IE ฉันลืมไปเลยเกี่ยวกับ "โหมดเอกสาร" หรือ "โหมด schizo" ที่ฉันจะเรียกมัน
user123444555621

แม้ว่าไอเดียจะยอดเยี่ยมและดูดี แต่ก็ใช้ไม่ได้กับฉันใน IE9 ที่มีหน้าต่างป๊อปอัป มันส่งคืนเท็จสำหรับอาร์เรย์ที่สร้างโดยตัวเปิด ... มีโซลูชันที่เข้ากันได้กับ IE9 หรือไม่? (ปัญหาน่าจะเป็นที่ IE9 ใช้ Array.isArray เองซึ่งส่งคืนเท็จสำหรับกรณีที่ระบุเมื่อไม่ควร
Steffen Heil

@Steffen: น่าสนใจดังนั้นการใช้งานเนทีฟisArrayจึงไม่ส่งคืนจริงจากอาร์เรย์ที่สร้างในโหมดเอกสารอื่น ๆ ? ฉันจะต้องตรวจสอบสิ่งนี้เมื่อมีเวลา แต่ฉันคิดว่าสิ่งที่ดีที่สุดที่ต้องทำคือส่งข้อบกพร่องใน Connect เพื่อให้สามารถแก้ไขได้ใน IE 10
Andy E

8

คร็อกฟอร์ดมีคำตอบสองข้อในหน้า 106 ของ "ส่วนที่ดี" ตัวแรกตรวจสอบตัวสร้าง แต่จะให้เชิงลบที่ผิดพลาดในเฟรมหรือหน้าต่างต่างๆ นี่คือวินาที:

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

Crockford ชี้ให้เห็นว่าเวอร์ชันนี้จะระบุไฟล์ argumentsอาร์เรย์ว่าเป็นอาร์เรย์แม้ว่าจะไม่มีวิธีอาร์เรย์ก็ตาม

การอภิปรายปัญหาที่น่าสนใจของเขาเริ่มต้นในหน้า 105

มีการอภิปรายที่น่าสนใจเพิ่มเติม (post-Good Parts) ที่นี่ซึ่งรวมถึงข้อเสนอนี้:

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

การสนทนาทั้งหมดทำให้ฉันไม่อยากรู้ว่าบางอย่างเป็นอาร์เรย์หรือไม่


1
สิ่งนี้จะทำลายใน IE สำหรับอ็อบเจ็กต์สตริงและไม่รวมสตริงไพรมารีซึ่งเหมือนอาร์เรย์ยกเว้นใน IE การตรวจสอบ [[Class]] จะดีกว่าถ้าคุณต้องการอาร์เรย์จริง หากคุณต้องการวัตถุที่ชอบอาร์เรย์การตรวจสอบก็เข้มงวดเกินไป
Christoph

@ Christoph - ฉันเพิ่มอีกเล็กน้อยผ่านการแก้ไข หัวข้อที่น่าสนใจ
Nosredna

2

jQuery ใช้ฟังก์ชัน isArray ซึ่งแนะนำวิธีที่ดีที่สุดในการดำเนินการนี้คือ

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

(ตัวอย่างที่นำมาจาก jQuery v1.3.2 - ปรับเล็กน้อยเพื่อให้เข้าใจผิดบริบท)


พวกเขาส่งคืนเท็จบน IE (# 2968) (จากแหล่ง jquery)
razzed

1
ความคิดเห็นนั้นในแหล่ง jQuery ดูเหมือนจะอ้างถึงฟังก์ชัน isFunction ไม่ใช่ isArray
Mario Menger

4
คุณควรใช้Object.prototype.toString()- มีโอกาสน้อยที่จะพัง
Christoph

2

ขโมยมาจากปรมาจารย์ John Resig และ jquery:

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

2
การทดสอบครั้งที่สองจะคืนค่าจริงสำหรับสตริงเช่นกัน: typeof "abc" .length === "number" // true
Daniel Vandersluis

2
นอกจากนี้ฉันเดาว่าคุณไม่ควรใช้ชื่อประเภทฮาร์ดโค้ดเช่น "หมายเลข" ลองเปรียบเทียบกับจำนวนจริงแทนเช่น typeof (obj) == typeof (42)
ohnoes

5
@mtod: ทำไมไม่ควรพิมพ์ชื่อแบบฮาร์ดโค้ด? ท้ายที่สุดค่าส่งคืนของtypeofเป็นมาตรฐาน?
Christoph

1
@ohnoes อธิบายตัวเอง
Pacerier

1

คุณจะทำอย่างไรกับค่าเมื่อคุณตัดสินใจว่าเป็นอาร์เรย์

ตัวอย่างเช่นหากคุณตั้งใจจะแจกแจงค่าที่มีอยู่หากดูเหมือนอาร์เรย์หรือถ้าเป็นวัตถุที่ใช้เป็นตารางแฮชโค้ดต่อไปนี้จะได้รับสิ่งที่คุณต้องการ (โค้ดนี้จะหยุดเมื่อฟังก์ชันการปิดส่งคืนค่าอื่น ๆ กว่า "ไม่ได้กำหนด" โปรดทราบว่าจะไม่วนซ้ำบนคอนเทนเนอร์ COM หรือการแจงนับ แต่เป็นแบบฝึกหัดสำหรับผู้อ่าน):

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;
}

(หมายเหตุ: การทดสอบ "o! = null" สำหรับค่าว่างและไม่ได้กำหนด)

ตัวอย่างการใช้งาน:

// 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] );
        }
    })
}

แม้ว่าคำตอบอาจจะน่าสนใจ แต่ก็ไม่ได้เกี่ยวข้องอะไรกับคำถามเลย (และ btw: การวนซ้ำอาร์เรย์ผ่านfor..inไม่ดี [tm])
คริสตอฟ

@ คริสตอฟ - แน่นอนว่ามันเป็นอย่างนั้น ต้องมีเหตุผลบางอย่างในการตัดสินใจว่าบางสิ่งบางอย่างเป็น Array หรือไม่: เพราะคุณต้องการทำบางสิ่งกับค่า สิ่งที่พบบ่อยที่สุด (อย่างน้อยที่สุดในรหัสของฉัน) คือการแมปกรองค้นหาหรือแปลงข้อมูลในอาร์เรย์ ฟังก์ชั่นข้างต้นทำอย่างนั้น: ถ้าสิ่งที่ส่งผ่านมีองค์ประกอบที่สามารถวนซ้ำได้มันจะวนซ้ำทับพวกมัน ถ้าไม่เช่นนั้นก็จะไม่ทำอะไรเลยและส่งกลับโดยไม่ได้กำหนด
James Hugard

@ คริสตอฟ - เหตุใดจึงวนซ้ำอาร์เรย์ด้วย for..in bad [tm]? คุณจะทำซ้ำบนอาร์เรย์และ / หรือแฮชแท็ก (วัตถุ) ได้อย่างไร
James Hugard

1
@ เจมส์: ทำfor..inซ้ำคุณสมบัติของวัตถุที่แจกแจงได้ คุณไม่ควรใช้กับอาร์เรย์เนื่องจาก: (1) มันช้า (2) ไม่รับประกันว่าจะรักษาความสงบเรียบร้อย (3) จะรวมคุณสมบัติที่ผู้ใช้กำหนดไว้ในวัตถุหรือต้นแบบใด ๆ เนื่องจาก ES3 ไม่รวมวิธีการตั้งค่าแอตทริบิวต์ DontEnum อาจมีปัญหาอื่น ๆ ที่ทำให้ฉันคิดมาก
Christoph

1
@Christoph - ในทางกลับกันการใช้สำหรับ (;;) จะไม่ทำงานอย่างถูกต้องสำหรับอาร์เรย์แบบกระจัดกระจายและจะไม่ทำให้คุณสมบัติของวัตถุซ้ำ # 3 ถือเป็นรูปแบบที่ไม่ดีสำหรับ Object เนื่องจากเหตุผลที่คุณกล่าวถึง ในทางกลับกันคุณพูดถูกเกี่ยวกับประสิทธิภาพ: for..in ช้ากว่า (;;) ประมาณ 36 เท่าในอาร์เรย์องค์ประกอบ 1M ว้าว. โชคไม่ดีที่ไม่สามารถใช้ได้กับกรณีการใช้งานหลักของเราซึ่งเป็นการทำซ้ำคุณสมบัติของวัตถุ (แฮชแท็ก)
James Hugard

1

หากคุณกำลังทำสิ่งนี้ใน CouchDB (SpiderMonkey) ให้ใช้ไฟล์

Array.isArray(array)

เป็นarray.constructor === Arrayหรือarray instanceof Arrayไม่ทำงาน การใช้array.toString() === "[object Array]"งานได้ผล แต่ดูเหมือนจะค่อนข้างหลบเมื่อเปรียบเทียบ


สิ่งนี้ใช้ได้ใน Node.js และในเบราว์เซอร์ด้วยไม่ใช่แค่ CouchDB: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Karl Wilbur


0

ในw3schoolมีตัวอย่างที่ค่อนข้างเป็นมาตรฐาน

เพื่อตรวจสอบว่าตัวแปรเป็นอาร์เรย์หรือไม่พวกเขาใช้สิ่งที่คล้ายกันนี้

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

ทดสอบบน Chrome, Firefox, Safari, ie7


ใช้constructorสำหรับการตรวจสอบประเภท imo เปราะเกินไป ใช้ทางเลือกที่แนะนำแทน
Christoph

ทำไมคุณคิดอย่างงั้น? เกี่ยวกับเปราะ?
Kamarey

@ Kamarey: constructorเป็นคุณสมบัติ DontEnum ปกติของวัตถุต้นแบบ นี่อาจไม่ใช่ปัญหาสำหรับประเภทในตัวตราบเท่าที่ไม่มีใครทำอะไรโง่ ๆ แต่สำหรับประเภทที่ผู้ใช้กำหนดเองก็สามารถทำได้อย่างง่ายดาย คำแนะนำของฉัน: ใช้เสมอinstanceofซึ่งตรวจสอบห่วงโซ่ต้นแบบและไม่พึ่งพาคุณสมบัติที่สามารถเขียนทับได้โดยพลการ
คริสตอฟ

1
ขอบคุณพบคำอธิบายอื่นที่นี่: thinkweb2.com/projects/prototype/…
Kamarey

สิ่งนี้ไม่น่าเชื่อถือเนื่องจากอ็อบเจ็กต์ Array สามารถเขียนทับด้วยอ็อบเจ็กต์ที่กำหนดเองได้
Josh Stodola

-2

หนึ่งที่ดีที่สุดวิจัยและกล่าวถึงรุ่นของฟังก์ชั่นนี้สามารถพบได้บนเว็บไซต์ PHPJS คุณสามารถเชื่อมโยงไปยังแพ็กเกจหรือไปที่ฟังก์ชันโดยตรงก็ได้ ฉันขอแนะนำไซต์สำหรับฟังก์ชัน PHP ที่เทียบเท่าใน JavaScript


-2

การอ้างอิงไม่เพียงพอเท่ากับตัวสร้าง บางครั้งพวกเขามีการอ้างอิงที่แตกต่างกันของตัวสร้าง ดังนั้นฉันจึงใช้การแสดงสตริงของพวกมัน

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

โดนหลอก{constructor:{toString:function(){ return "function Array() { [native code] }"; }}}
Bergi

-4

แทนที่Array.isArray(obj)โดยobj.constructor==Array

ตัวอย่าง:

Array('44','55').constructor==Array คืนค่าจริง (IE8 / Chrome)

'55'.constructor==Array ส่งคืนเท็จ (IE8 / Chrome)


3
ทำไมคุณถึงแทนที่เพื่อแก้ไขฟังก์ชันด้วยฟังก์ชันที่น่ากลัว?
Bergi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.