กำลังตรวจสอบว่าสามารถทำซ้ำได้หรือไม่


104

ในเอกสาร MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

for...ofสร้างอธิบายไว้เพื่อให้สามารถย้ำกว่า "iterable วัตถุ" แต่มีวิธีที่ดีในการตัดสินใจว่าวัตถุนั้นทำซ้ำได้หรือไม่?

ฉันได้พยายามค้นหาคุณสมบัติทั่วไปสำหรับอาร์เรย์ตัวทำซ้ำและตัวกำเนิด แต่ไม่สามารถทำได้

นอกเหนือจากการทำfor ... ofใน try block และการตรวจสอบข้อผิดพลาดประเภทมีวิธีที่ชัดเจนในการทำเช่นนี้หรือไม่?


แน่นอนในฐานะผู้เขียนคุณรู้ว่าวัตถุของคุณสามารถทำซ้ำได้หรือไม่?
andrewb

5
วัตถุถูกส่งผ่านเป็นอาร์กิวเมนต์ฉันไม่แน่ใจ
simonzack

1
ทำไมไม่ทดสอบ typeof ของอาร์กิวเมนต์
James Bruckner

2
@ andrew-buchan, James Bruckner: การตรวจสอบประเภทอาจได้ผล แต่ถ้าคุณอ่านเอกสาร MDN คุณจะสังเกตเห็นว่า "เหมือนอาร์เรย์" ฉันไม่รู้ว่ามันหมายความว่าอย่างไรดังนั้นคำถามนี้
simonzack

1
wiki.ecmascript.org/doku.php?id=harmony:iteratorsระบุว่า " ออบเจ็กต์สามารถทำซ้ำได้หากมีiterator()วิธีการ " อย่างไรก็ตามเนื่องจากนี่เป็นเพียงแบบร่างการตรวจสอบจึงอาจขึ้นอยู่กับการดำเนินการ คุณใช้สภาพแวดล้อมแบบใด
Bergi

คำตอบ:


142

วิธีที่เหมาะสมในการตรวจสอบความสามารถในการทำซ้ำมีดังนี้:

function isIterable(obj) {
  // checks for null and undefined
  if (obj == null) {
    return false;
  }
  return typeof obj[Symbol.iterator] === 'function';
}

เหตุใดจึงใช้งานได้ (โปรโตคอลเชิงลึกซ้ำได้): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

เนื่องจากเรากำลังพูดถึงเรื่อง for..of ฉันถือว่าเราอยู่ในแนวความคิด ES6

นอกจากนี้อย่าแปลกใจที่ฟังก์ชันนี้จะส่งคืนtrueif objเป็นสตริงเนื่องจากสตริงจะวนซ้ำบนอักขระของพวกเขา


14
หรือ, Symbol.iterator in Object(obj).

14
มีข้อยกเว้น (อย่างน้อย) หนึ่งข้อในการใช้ตัวดำเนินการ 'in': string สตริงสามารถทำซ้ำได้ (ในรูปของ for..of) แต่คุณไม่สามารถใช้ 'in' กับมันได้ ถ้าไม่ใช่สำหรับสิ่งนี้ฉันต้องการใช้ 'in' ซึ่งดูดีกว่า
Tomas Kulich

มันไม่ควรreturn typeof obj[Symbol.iterator] === 'function'? "เพื่อให้สามารถทำซ้ำได้อ็อบเจ็กต์ต้องใช้เมธอด @@ iterator" โดยระบุวิธีการ
callum

ไม่มีความหมายที่เหมาะสมสำหรับ obj [Symbol.iterator] เป็นอย่างอื่นนอกจากฟังก์ชัน (ไม่ได้กำหนดหรือ) หากใครใส่ตัวอย่าง String ไว้ตรงนั้นมันเป็นสิ่งที่ไม่ดีและ IMO จะดีถ้ารหัสล้มเหลวโดยเร็วที่สุด
Tomas Kulich

จะได้typeof Object(obj)[Symbol.iterator] === 'function'ผลในทุกกรณี?
Craig Gidney

26

ทำไมต้อง verbose?

const isIterable = object =>
  object != null && typeof object[Symbol.iterator] === 'function'

57
Readability > Clevernesstrueผลตอบแทนเสมอ
jfmercer

27
ฮ่า ๆ ปกติฉันเป็นคนที่บ่นเรื่องโค้ดที่อ่านไม่ได้ แต่จริงๆแล้วฉันคิดว่ามันอ่านได้ค่อนข้างดี เช่นเดียวกับประโยคภาษาอังกฤษ: ถ้าอ็อบเจ็กต์ไม่เป็นโมฆะและคุณสมบัติตัววนซ้ำเชิงสัญลักษณ์เป็นฟังก์ชันก็จะทำซ้ำได้ ถ้ามันไม่ตายง่ายๆฉันก็ไม่รู้ว่าคืออะไร ...
adius

4
imo ประเด็นของ "ความสามารถในการอ่าน" คือการเข้าใจสิ่งที่เกิดขึ้นโดยไม่ต้องอ่านจริง
Dmitry Parzhitsky

2
@Alexander Mills การปรับปรุงของคุณทำให้โค้ดแย่ลง 1. เช่นเดียวกับที่ @jfmercer กล่าวReadability > Clevernessดังนั้นการย่อตัวแปรobjectเพื่อoช่วยให้ไม่มีใคร 2. สตริงที่ว่างเปล่า''เป็น iterable trueและดังนั้นจึงต้องกลับ
adius

1
เข้าใจแล้วฉันคิดว่ามีเหตุผลว่าทำไมจึงใช้!= null
Alexander Mills

22

ทางออกที่ง่ายที่สุดคือ:

function isIterable (value) {
  return Symbol.iterator in Object(value);
}

Objectจะรวมสิ่งที่ไม่ใช่วัตถุไว้ในตัวเดียวทำให้ตัวinดำเนินการสามารถทำงานได้แม้ว่าค่าดั้งเดิมจะไม่ใช่วัตถุก็ตาม nullและundefinedกลายเป็นวัตถุว่างเปล่าดังนั้นจึงไม่จำเป็นต้องมีการตรวจจับขอบเคสและสตริงจะรวมเข้ากับอ็อบเจกต์ String ซึ่งสามารถทำซ้ำได้


1
คุณใช้สัญลักษณ์ผิด มัน: Symbol.iterator.
Gil

@Gil คุณพูดถูกจริงๆโอ๊ะ! ฉันควรคัดลอกโค้ดที่ทดสอบแล้วแทนที่จะพิมพ์ลงในโพสต์โดยตรง
Domino

การสร้างออบเจ็กต์ใหม่ด้วยObjectการตรวจสอบประเภทนี้เป็นการสิ้นเปลือง
Ruben Verborgh

ฉันไม่สามารถพูดได้ว่าฉันรู้ว่าObjectฟังก์ชันถูกนำไปใช้อย่างไร แต่จะสร้างวัตถุใหม่ถ้าvalueไม่ใช่วัตถุอยู่แล้ว ผมคาดว่าการรวมกันของinและจะเป็นเครื่องมือเบราว์เซอร์บางสิ่งบางอย่างสามารถได้อย่างง่ายดายเพิ่มประสิทธิภาพการพูดแตกต่างจากObject(...) value !== undefined && value !== null && value[Symbol.iterator] && trueนอกจากนี้ยังอ่านง่ายมากซึ่งฉันสนใจ
Domino

9

ในฐานะที่เป็น sidenote ให้ระวังเกี่ยวกับความหมายของiterable ถ้าคุณมาจากภาษาอื่น ๆ ที่คุณคาดหวังบางสิ่งบางอย่างที่คุณสามารถย้ำไปกับการพูดการforห่วงคือiterable ฉันกลัวว่าไม่กรณีที่นี่ที่iterableหมายถึงสิ่งที่ใช้โพรโทคอซ้ำ

เพื่อให้สิ่งต่างๆชัดเจนขึ้นตัวอย่างทั้งหมดข้างต้นให้ส่งคืนfalseอ็อบเจ็กต์นี้{a: 1, b: 2}เนื่องจากอ็อบเจ็กต์นั้นไม่ได้ใช้โปรโตคอลการวนซ้ำ ดังนั้นคุณจะไม่สามารถทำซ้ำได้for...of แต่คุณยังสามารถทำได้ด้วยไฟล์for...in.

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

/**
 * @param variable
 * @returns {boolean}
 */
const hasIterationProtocol = variable =>
    variable !== null && Symbol.iterator in Object(variable);

คุณไม่มีเหตุผล ทำไมคุณถึงคิดว่ามันจะแตกด้วยundefined?
adius

@adius ฉันคิดว่าฉันคิดผิดว่าคุณทำobject !== nullในคำตอบของคุณ แต่คุณทำเช่นobject != nullนั้นมันไม่ได้ผิดundefinedในกรณีเฉพาะนั้น ฉันได้อัปเดตคำตอบตามนั้น
Francesco Casula

ตกลงฉันเข้าใจ Btw: รหัสของคุณไม่ถูกต้อง hasIterationProtocol('')ต้องกลับtrue! คุณจะลบรหัสของคุณได้อย่างไรและปล่อยให้ส่วนคำอธิบายที่ทำซ้ำได้ซึ่งเป็นสิ่งเดียวที่เพิ่มมูลค่าที่แท้จริง / สิ่งใหม่ในคำตอบของคุณ
adius

1
มันจะกลับมาtrueโดยการลบส่วนหนึ่งของคำตอบเก่าฉันรวมทั้งสองฟังก์ชั่นและลืมเกี่ยวกับการเปรียบเทียบที่เข้มงวด ตอนนี้ฉันตอบกลับคำตอบเดิมซึ่งใช้งานได้ดี
Francesco Casula

1
ฉันไม่เคยคิดที่จะใช้การObject(...)จับที่ดี แต่ในกรณีนั้นไม่จำเป็นต้องตรวจสอบค่าว่าง
Domino

2

ปัจจุบันตามที่ระบุไว้แล้วเพื่อทดสอบว่าobjสามารถทำได้หรือไม่เพียงแค่ทำ

obj != null && typeof obj[Symbol.iterator] === 'function' 

คำตอบในอดีต (ไม่ถูกต้องอีกต่อไป)

for..ofสร้างเป็นส่วนหนึ่งของ ECMAScript 6 ฉบับภาษาข้อมูลจำเพาะร่าง ดังนั้นจึงสามารถเปลี่ยนแปลงได้ก่อนเวอร์ชันสุดท้าย

ในแบบร่างนี้อ็อบเจ็กต์ที่ทำซ้ำได้ต้องมีฟังก์ชันiteratorเป็นคุณสมบัติ

คุณสามารถตรวจสอบว่าวัตถุสามารถทำซ้ำได้เช่นนี้หรือไม่:

function isIterable(obj){
   if(obj === undefined || obj === null){
      return false;
   }
   return obj.iterator !== undefined;
}

7
คุณสมบัติตัววนซ้ำเป็นสิ่งที่ Firefox ชั่วคราว ไม่เป็นไปตาม ES6 ดู: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Amir Arad

2

สำหรับตัววนซ้ำแบบasyncคุณควรตรวจสอบ 'Symbol.asyncIterator' แทน 'Symbol.iterator':

async function* doSomething(i) {
    yield 1;
    yield 2;
}

let obj = doSomething();

console.log(typeof obj[Symbol.iterator] === 'function');      // false
console.log(typeof obj[Symbol.asyncIterator] === 'function'); // true

1

หากคุณต้องการตรวจสอบข้อเท็จจริงว่าตัวแปรเป็นวัตถุ ( {key: value}) หรืออาร์เรย์ ( [value, value]) คุณสามารถทำได้:

const isArray = function (a) {
    return Array.isArray(a);
};

const isObject = function (o) {
    return o === Object(o) && !isArray(o) && typeof o !== 'function';
};

function isIterable(variable) {
    return isArray(variable) || isObject(variable);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.