เหตุใด Objects จึงไม่สามารถทำซ้ำได้ใน JavaScript


87

เหตุใดวัตถุจึงไม่สามารถทำซ้ำได้ตามค่าเริ่มต้น

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

คำสั่งเช่น ES6 for...ofน่าใช้สำหรับวัตถุโดยค่าเริ่มต้น เนื่องจากคุณลักษณะเหล่านี้มีให้เฉพาะสำหรับ "อ็อบเจ็กต์ที่ทำซ้ำได้" แบบพิเศษซึ่งไม่รวมอ{}อบเจ็กต์เราจึงต้องผ่านห่วงเพื่อให้สามารถใช้งานกับวัตถุที่เราต้องการใช้

คำสั่ง for ... of สร้าง loop วนซ้ำบนวัตถุที่ทำซ้ำได้ (รวมถึง Array, Map, Set, วัตถุอาร์กิวเมนต์และอื่น ๆ ) ...

ตัวอย่างเช่นการใช้ฟังก์ชันเครื่องกำเนิด ES6 :

var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};

function* entries(obj) {
   for (let key of Object.keys(obj)) {
     yield [key, obj[key]];
   }
}

for (let [key, value] of entries(example)) {
  console.log(key);
  console.log(value);
  for (let [key, value] of entries(value)) {
    console.log(key);
    console.log(value);
  }
}

ข้างต้นบันทึกข้อมูลอย่างถูกต้องตามลำดับที่ฉันคาดหวังเมื่อฉันเรียกใช้โค้ดใน Firefox (ซึ่งรองรับES6 ):

ผลลัพธ์ของการแฮ็กสำหรับ ... จาก

โดยค่าเริ่มต้น{}วัตถุจะไม่สามารถทำซ้ำได้ แต่ทำไม? ข้อเสียจะมีมากกว่าประโยชน์ที่เป็นไปได้ของวัตถุที่สามารถทำซ้ำได้หรือไม่? อะไรคือปัญหาที่เกี่ยวข้องกับสิ่งนี้?

นอกจากนี้เนื่องจาก{}วัตถุที่มีความแตกต่างจาก "อาร์เรย์เหมือน" คอลเลกชันและ "วัตถุ iterable" เช่นNodeList, HtmlCollectionและargumentsพวกเขาจะไม่สามารถแปลงเป็นอาร์เรย์

ตัวอย่างเช่น:

var argumentsArray = Array.prototype.slice.call(arguments);

หรือใช้กับเมธอด Array:

Array.prototype.forEach.call(nodeList, function (element) {}).

นอกเหนือจากคำถามที่ฉันมีข้างต้นฉันชอบที่จะเห็นตัวอย่างการทำงานเกี่ยวกับวิธีสร้าง{}วัตถุให้เป็นรูปแบบซ้ำโดยเฉพาะจากผู้ที่กล่าวถึงไฟล์[Symbol.iterator]. นี้จะช่วยให้ใหม่เหล่านี้{}"วัตถุ iterable" for...ofงบการใช้งานเช่น นอกจากนี้ฉันสงสัยว่าการทำให้ออบเจ็กต์ทำซ้ำได้ทำให้สามารถแปลงเป็น Arrays ได้หรือไม่

ฉันลองใช้รหัสด้านล่าง แต่ได้รับไฟล์TypeError: can't convert undefined to object.

var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};

// I want to be able to use "for...of" for the "example" object.
// I also want to be able to convert the "example" object into an Array.
example[Symbol.iterator] = function* (obj) {
   for (let key of Object.keys(obj)) {
     yield [key, obj[key]];
   }
};

for (let [key, value] of example) { console.log(value); } // error
console.log([...example]); // error

1
สิ่งที่มีSymbol.iteratorคุณสมบัติเป็นสิ่งที่ทำซ้ำได้ ดังนั้นคุณต้องใช้คุณสมบัตินั้น คำอธิบายที่เป็นไปได้อย่างหนึ่งสำหรับสาเหตุที่วัตถุไม่สามารถทำซ้ำได้อาจเป็นได้ว่าสิ่งนี้บ่งบอกถึงทุกสิ่งที่ทำซ้ำได้เนื่องจากทุกอย่างเป็นวัตถุ (ยกเว้นวัตถุดั้งเดิมแน่นอน) อย่างไรก็ตามการวนซ้ำบนฟังก์ชันหรืออ็อบเจกต์นิพจน์ทั่วไปหมายความว่าอย่างไร
Felix Kling

7
คำถามที่แท้จริงของคุณที่นี่คืออะไร? เหตุใด ECMA จึงตัดสินใจ
Steve Bennett

3
เนื่องจากออบเจ็กต์ไม่มีลำดับการรับประกันคุณสมบัติของมันฉันจึงสงสัยว่าสิ่งนั้นผิดไปจากคำจำกัดความของการทำซ้ำซึ่งคุณคาดว่าจะมีลำดับที่คาดเดาได้หรือไม่?
jfriend00

2
เพื่อให้ได้คำตอบที่เชื่อถือได้สำหรับ "ทำไม" คุณควรถามที่esdiscuss.org
Felix Kling

1
@FelixKling - โพสต์นั้นเกี่ยวกับ ES6 หรือเปล่า? คุณควรแก้ไขเพื่อบอกว่าคุณกำลังพูดถึงเวอร์ชันใดเนื่องจาก "ECMAScript เวอร์ชันที่กำลังจะมาถึง" ทำงานได้ไม่ดีนักเมื่อเวลาผ่านไป
jfriend00

คำตอบ:


42

ฉันจะลองดู โปรดทราบว่าฉันไม่ได้มีส่วนเกี่ยวข้องกับ ECMA และไม่สามารถมองเห็นกระบวนการตัดสินใจของพวกเขาได้ดังนั้นฉันจึงไม่สามารถบอกได้อย่างชัดเจนว่าทำไมพวกเขาถึงมีหรือไม่ได้ทำอะไรเลย อย่างไรก็ตามฉันจะตั้งสมมติฐานและถ่ายให้ดีที่สุด

1. ทำไมต้องเพิ่มfor...ofโครงสร้างตั้งแต่แรก?

JavaScript มีfor...inโครงสร้างที่สามารถใช้เพื่อวนซ้ำคุณสมบัติของวัตถุได้ อย่างไรก็ตามมันไม่ใช่ forEach loop จริงๆเนื่องจากมันแจกแจงคุณสมบัติทั้งหมดบนวัตถุและมีแนวโน้มที่จะคาดเดาได้ในกรณีง่ายๆเท่านั้น

มันแบ่งออกเป็นกรณีที่ซับซ้อนมากขึ้น (รวมถึงอาร์เรย์ซึ่งการใช้งานมีแนวโน้มที่จะถูกกีดกันหรือทำให้สับสนโดยการป้องกันที่จำเป็นสำหรับการใช้for...inกับอาร์เรย์อย่างถูกต้อง ) คุณสามารถหลีกเลี่ยงสิ่งนั้นได้โดยใช้hasOwnProperty(เหนือสิ่งอื่นใด) แต่มันค่อนข้างอึดอัดและไม่ดี

ดังนั้นสมมติฐานของฉันคือการfor...ofสร้างจะถูกเพิ่มเพื่อแก้ไขข้อบกพร่องที่เกี่ยวข้องกับfor...inโครงสร้างและให้ประโยชน์และความยืดหยุ่นมากขึ้นเมื่อทำซ้ำสิ่งต่างๆ ผู้คนมักจะมองfor...inว่าเป็นforEachวงรอบที่สามารถนำไปใช้กับคอลเลกชันใดก็ได้และสร้างผลลัพธ์ที่ดีในบริบทที่เป็นไปได้ แต่นั่นไม่ใช่สิ่งที่เกิด for...ofแก้ไขห่วงว่า

ฉันยังคิดว่าสิ่งสำคัญสำหรับรหัส ES5 ที่มีอยู่ในการทำงานภายใต้ ES6 และให้ผลลัพธ์เช่นเดียวกับที่ทำภายใต้ ES5 ดังนั้นจึงไม่สามารถทำการเปลี่ยนแปลงที่ผิดปกติได้เช่นกับพฤติกรรมของfor...inโครงสร้าง

2. for...ofทำงานอย่างไร?

เอกสารอ้างอิงจะเป็นประโยชน์สำหรับส่วนนี้ โดยเฉพาะอย่างยิ่งวัตถุจะได้รับการพิจารณาiterableหากกำหนดSymbol.iteratorคุณสมบัติ

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

วิธีนี้มีประโยชน์เนื่องจากให้การทำซ้ำของคุณเองอย่างตรงไปตรงมา ฉันอาจพูดได้ว่าแนวทางนี้อาจนำเสนอประเด็นที่เป็นประโยชน์เนื่องจากการพึ่งพาการกำหนดคุณสมบัติที่ก่อนหน้านี้ไม่มีเลยยกเว้นจากสิ่งที่ฉันสามารถบอกได้ว่าไม่ใช่กรณีนี้เนื่องจากคุณสมบัติใหม่จะถูกละเว้นเป็นหลักเว้นแต่คุณจะจงใจไปหามัน (เช่น จะไม่ปรากฏในfor...inลูปเป็นคีย์ ฯลฯ ) นั่นไม่ใช่กรณี

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

3. เหตุใดวัตถุจึงไม่ถูกiterableใช้for...ofโดยค่าเริ่มต้น

ฉันเดาว่านี่เป็นการรวมกันของ:

  1. การสร้างออบเจ็กต์ทั้งหมดiterableตามค่าเริ่มต้นอาจถูกพิจารณาว่าไม่สามารถยอมรับได้เนื่องจากเป็นการเพิ่มคุณสมบัติที่ก่อนหน้านี้ไม่มีเลยหรือเนื่องจากอ็อบเจ็กต์ไม่ใช่คอลเล็กชัน (จำเป็น) ดังที่เฟลิกซ์กล่าวว่า "การวนซ้ำบนฟังก์ชันหรือวัตถุนิพจน์ทั่วไปหมายความว่าอย่างไร"
  2. ออบเจ็กต์ธรรมดาสามารถทำซ้ำได้แล้วโดยใช้for...inและยังไม่ชัดเจนว่าการใช้งานตัววนซ้ำในตัวจะทำอะไรได้แตกต่าง / ดีกว่าfor...inพฤติกรรมที่มีอยู่ ดังนั้นแม้ว่า # 1 เป็นความผิดและการเพิ่มสถานที่ได้รับการยอมรับก็อาจจะไม่ได้รับการมองว่าเป็นประโยชน์
  3. ผู้ใช้ที่ต้องการสร้างออบเจ็กต์iterableสามารถทำได้อย่างง่ายดายโดยกำหนดSymbol.iteratorคุณสมบัติ
  4. ข้อมูลจำเพาะ ES6 ยังมีประเภทแผนที่ซึ่งเป็น iterableค่าเริ่มต้นและมีข้อดีเล็กน้อยอื่น ๆ นอกเหนือจากการใช้วัตถุธรรมดาเป็นไฟล์Map.

มีตัวอย่างให้สำหรับ # 3 ในเอกสารอ้างอิง:

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

for (var value of myIterable) {
    console.log(value);
}

เนื่องจากสามารถสร้างวัตถุได้อย่างง่ายดายทำให้สามารถทำiterableซ้ำได้โดยใช้for...inและอาจไม่มีข้อตกลงที่ชัดเจนเกี่ยวกับสิ่งที่ตัววนซ้ำออบเจ็กต์เริ่มต้นควรทำ (หากสิ่งที่หมายถึงแตกต่างจากสิ่งที่for...inทำ) ดูเหมือนว่าสมเหตุสมผล เพียงพอที่วัตถุไม่ได้ถูกสร้างขึ้นiterableตามค่าเริ่มต้น

โปรดทราบว่าโค้ดตัวอย่างของคุณสามารถเขียนใหม่ได้โดยใช้for...in:

for (let levelOneKey in object) {
    console.log(levelOneKey);         //  "example"
    console.log(object[levelOneKey]); // {"random":"nest","another":"thing"}

    var levelTwoObj = object[levelOneKey];
    for (let levelTwoKey in levelTwoObj ) {
        console.log(levelTwoKey);   // "random"
        console.log(levelTwoObj[levelTwoKey]); // "nest"
    }
}

... หรือคุณสามารถสร้างวัตถุของคุณiterableในแบบที่คุณต้องการโดยทำสิ่งต่อไปนี้ (หรือคุณสามารถสร้างวัตถุทั้งหมดiterableโดยกำหนดให้Object.prototype[Symbol.iterator]แทน):

obj = { 
    a: '1', 
    b: { something: 'else' }, 
    c: 4, 
    d: { nested: { nestedAgain: true }}
};

obj[Symbol.iterator] = function() {
    var keys = [];
    var ref = this;
    for (var key in this) {
        //note:  can do hasOwnProperty() here, etc.
        keys.push(key);
    }

    return {
        next: function() {
            if (this._keys && this._obj && this._index < this._keys.length) {
                var key = this._keys[this._index];
                this._index++;
                return { key: key, value: this._obj[key], done: false };
            } else {
                return { done: true };
            }
        },
        _index: 0,
        _keys: keys,
        _obj: ref
    };
};

คุณสามารถเล่นได้ที่นี่ (ใน Chrome ตามสัญญาเช่า): http://jsfiddle.net/rncr3ppz/5/

แก้ไข

และเพื่อตอบสนองต่อคำถามที่อัปเดตของคุณใช่คุณสามารถแปลงiterableอาร์เรย์เป็นอาร์เรย์โดยใช้ตัวดำเนินการกระจายใน ES6

อย่างไรก็ตามดูเหมือนว่าจะยังใช้งานไม่ได้ใน Chrome หรืออย่างน้อยฉันก็ไม่สามารถใช้งานได้ใน jsFiddle ของฉัน ในทางทฤษฎีมันควรจะง่ายเหมือน:

var array = [...myIterable];

ทำไมไม่ลองทำobj[Symbol.iterator] = obj[Symbol.enumerate]ในตัวอย่างสุดท้ายของคุณล่ะ
Bergi

@Bergi - เพราะฉันไม่เห็นสิ่งนั้นในเอกสาร (และฉันไม่เห็นคุณสมบัตินั้นที่อธิบายไว้ที่นี่ ) แม้ว่าอาร์กิวเมนต์หนึ่งที่ช่วยในการกำหนดตัววนซ้ำอย่างชัดเจนก็คือทำให้ง่ายต่อการบังคับใช้คำสั่งการวนซ้ำเฉพาะ แต่ก็เป็นสิ่งที่จำเป็น หากลำดับการทำซ้ำไม่สำคัญ (หรือถ้าคำสั่งเริ่มต้นนั้นใช้ได้) และทางลัดบรรทัดเดียวใช้งานได้มีเหตุผลเพียงเล็กน้อยที่จะไม่ใช้แนวทางที่รัดกุมกว่านี้
aroth

อ๊ะ[[enumerate]]ไม่ใช่สัญลักษณ์ที่รู้จักกันดี (@@ enumerate) แต่เป็นวิธีการภายใน ฉันจะต้องเป็นobj[Symbol.iterator] = function(){ return Reflect.enumerate(this) }
Bergi

การคาดเดาทั้งหมดนี้ใช้ประโยชน์อะไรได้บ้างเมื่อมีการบันทึกกระบวนการสนทนาที่แท้จริงไว้เป็นอย่างดี เป็นเรื่องแปลกมากที่คุณจะพูดว่า "ดังนั้นสมมติฐานของฉันก็คือว่า for ... of construction กำลังถูกเพิ่มเพื่อแก้ไขข้อบกพร่องที่เกี่ยวข้องกับ for ... in construction" ไม่ได้มันถูกเพิ่มเข้ามาเพื่อรองรับวิธีการทั่วไปในการวนซ้ำสิ่งใด ๆ และเป็นส่วนหนึ่งของชุดคุณสมบัติใหม่ที่หลากหลายซึ่งรวมถึงการเล่นซ้ำตัวเองเครื่องกำเนิดไฟฟ้าแผนที่และชุดต่างๆ แทบจะไม่ได้หมายความว่าเป็นการทดแทนหรืออัปเกรดfor...inซึ่งมีจุดประสงค์ที่แตกต่างกัน - เพื่อทำซ้ำในคุณสมบัติของวัตถุ

2
จุดที่ดีที่เน้นย้ำอีกครั้งว่าไม่ใช่ทุกวัตถุที่จะสะสม วัตถุถูกใช้มาเป็นเวลานานแล้วเพราะมันสะดวกมาก แต่ท้ายที่สุดแล้วพวกมันไม่ใช่คอลเลกชันจริงๆ นั่นคือสิ่งที่เรามีMapสำหรับตอนนี้
Felix Kling

9

Objectอย่าใช้โปรโตคอลการวนซ้ำใน Javascript ด้วยเหตุผลที่ดีมาก มีสองระดับที่คุณสมบัติของอ็อบเจ็กต์สามารถทำซ้ำได้ใน JavaScript:

  • ระดับโปรแกรม
  • ระดับข้อมูล

การทำซ้ำระดับโปรแกรม

เมื่อคุณวนซ้ำบนวัตถุในระดับโปรแกรมคุณจะตรวจสอบส่วนหนึ่งของโครงสร้างของโปรแกรมของคุณ มันคือการดำเนินการสะท้อนแสง ลองอธิบายคำสั่งนี้ด้วยประเภทอาร์เรย์ซึ่งโดยปกติจะวนซ้ำที่ระดับข้อมูล:

const xs = [1,2,3];
xs.f = function f() {};

for (let i in xs) console.log(xs[i]); // logs `f` as well

เราเพิ่งตรวจสอบระดับโปรแกรมของxs. เนื่องจากอาร์เรย์จัดเก็บลำดับข้อมูลเราจึงสนใจเฉพาะระดับข้อมูลเป็นประจำ for..inเห็นได้ชัดว่าไม่มีเหตุผลในการเชื่อมต่อกับอาร์เรย์และโครงสร้าง "ที่เน้นข้อมูล" อื่น ๆ ในกรณีส่วนใหญ่ นั่นคือเหตุผลที่ ES2015 เปิดตัวfor..ofและโปรโตคอลที่ทำซ้ำได้

การทำซ้ำระดับข้อมูล

นั่นหมายความว่าเราสามารถแยกแยะข้อมูลจากระดับโปรแกรมได้โดยการแยกแยะฟังก์ชันจากประเภทดั้งเดิมหรือไม่? ไม่เพราะฟังก์ชันสามารถเป็นข้อมูลใน Javascript ได้เช่นกัน:

  • Array.prototype.sort ตัวอย่างเช่นคาดว่าฟังก์ชันจะดำเนินการอัลกอริทึมการจัดเรียงบางอย่าง
  • Thunks like () => 1 + 2เป็นเพียงเครื่องห่อหุ้มที่ใช้งานได้สำหรับค่าที่ประเมินอย่างเฉื่อยชา

นอกจากค่าดั้งเดิมสามารถแสดงถึงระดับโปรแกรมได้เช่นกัน:

  • [].lengthตัวอย่างเช่นเป็นNumberแต่แสดงถึงความยาวของอาร์เรย์และเป็นของโดเมนโปรแกรม

นั่นหมายความว่าเราไม่สามารถแยกความแตกต่างระหว่างโปรแกรมและระดับข้อมูลด้วยการตรวจสอบประเภทเท่านั้น


สิ่งสำคัญคือต้องเข้าใจว่าการนำโปรโตคอลการทำซ้ำไปใช้กับออบเจ็กต์ Javascript แบบเก่าธรรมดาจะต้องอาศัยระดับข้อมูล แต่อย่างที่เราเพิ่งเห็นความแตกต่างที่น่าเชื่อถือระหว่างข้อมูลและการทำซ้ำระดับโปรแกรมนั้นไม่สามารถทำได้

ด้วยArrayความแตกต่างนี้จึงไม่สำคัญ: ทุกองค์ประกอบที่มีคีย์เหมือนจำนวนเต็มเป็นองค์ประกอบข้อมูล Objects มีคุณสมบัติเทียบเท่า: ตัวenumerableอธิบาย แต่ควรพึ่งพาสิ่งนี้จริงหรือ? ฉันเชื่อว่ามันไม่ใช่! ความหมายของตัวenumerableอธิบายพร่ามัวเกินไป

สรุป

ไม่มีวิธีที่มีความหมายในการใช้โปรโตคอลการวนซ้ำสำหรับอ็อบเจ็กต์เนื่องจากไม่ใช่ทุกอ็อบเจ็กต์ที่เป็นคอลเล็กชัน

หากคุณสมบัติของอ็อบเจ็กต์สามารถทำซ้ำได้ตามค่าเริ่มต้นระดับโปรแกรมและข้อมูลจะผสมกัน เนื่องจากทุกประเภทคอมโพสิตใน Javascript จะขึ้นอยู่กับอ็อบเจ็กต์ธรรมดาสิ่งนี้จะใช้สำหรับArrayและMapเช่นกัน

for..in, Object.keys, Reflect.ownKeysฯลฯ สามารถนำมาใช้สำหรับทั้งสะท้อนและข้อมูลย้ำความแตกต่างที่ชัดเจนเป็นไปไม่ได้อย่างสม่ำเสมอ หากคุณไม่ระวังคุณจะจบลงอย่างรวดเร็วด้วยการเขียนโปรแกรมเมตาและการอ้างอิงแปลก ๆ Mapชนิดข้อมูลนามธรรมอย่างมีประสิทธิภาพจบลงมหันต์ของโปรแกรมและข้อมูลระดับ ฉันเชื่อว่าMapเป็นความสำเร็จที่สำคัญที่สุดใน ES2015 แม้ว่าPromises จะน่าตื่นเต้นกว่ามากก็ตาม


3
+1 ฉันคิดว่า "ไม่มีวิธีที่มีความหมายในการใช้โปรโตคอลการวนซ้ำสำหรับออบเจ็กต์เพราะไม่ใช่ทุกวัตถุที่เป็นคอลเล็กชัน" สรุปได้
Charlie Schliesser

1
ฉันไม่คิดว่านั่นเป็นการโต้เถียงที่ดี ถ้าวัตถุของคุณไม่ใช่คอลเล็กชันทำไมคุณถึงพยายามวนซ้ำ ไม่สำคัญว่าวัตถุทุกชิ้นจะไม่ใช่คอลเล็กชันเพราะคุณจะไม่พยายามทำซ้ำมากกว่าวัตถุที่ไม่ใช่
BT

จริงๆแล้วทุกออบเจ็กต์คือคอลเล็กชันและไม่ได้ขึ้นอยู่กับภาษาที่จะตัดสินว่าคอลเล็กชันนั้นสอดคล้องกันหรือไม่ อาร์เรย์และแผนที่ยังสามารถรวบรวมค่าที่ไม่เกี่ยวข้องกันได้ ประเด็นก็คือคุณสามารถทำซ้ำบนคีย์ของวัตถุใด ๆ โดยไม่คำนึงถึงการใช้งานดังนั้นคุณจึงอยู่ห่างจากการทำซ้ำค่าของพวกเขาเพียงขั้นตอนเดียว หากคุณกำลังพูดถึงภาษาที่พิมพ์ค่าอาร์เรย์ (หรือคอลเล็กชันอื่น ๆ ) แบบคงที่คุณสามารถพูดถึงข้อ จำกัด ดังกล่าวได้ แต่ไม่ใช่ JavaScript
Manngo

ข้อโต้แย้งที่ว่าวัตถุทุกชิ้นไม่ใช่คอลเล็กชันไม่สมเหตุสมผล คุณสมมติว่าตัววนซ้ำมีจุดประสงค์เดียวเท่านั้น (เพื่อวนซ้ำคอลเลกชัน) ตัววนซ้ำเริ่มต้นบนวัตถุจะเป็นตัววนซ้ำคุณสมบัติของวัตถุไม่ว่าคุณสมบัติเหล่านั้นจะเป็นตัวแทนของอะไร (ไม่ว่าจะเป็นคอลเลกชันหรืออย่างอื่น) ดังที่ Manngo กล่าวไว้หากวัตถุของคุณไม่ได้เป็นตัวแทนของคอลเลกชันก็ขึ้นอยู่กับโปรแกรมเมอร์ที่จะไม่ปฏิบัติเหมือนคอลเล็กชัน บางทีพวกเขาอาจต้องการทำซ้ำคุณสมบัติบนวัตถุสำหรับเอาต์พุตการดีบักบางอย่าง? ยังมีเหตุผลอื่น ๆ อีกมากมายนอกเหนือจากคอลเล็กชัน
jfriend00

8

ฉันเดาว่าคำถามน่าจะเป็น "เหตุใดจึงไม่มีการทำซ้ำวัตถุในตัว

การเพิ่มความสามารถในการทำซ้ำให้กับออบเจ็กต์เองอาจส่งผลที่ไม่คาดคิดได้และไม่ไม่มีวิธีใดที่จะรับประกันคำสั่งซื้อได้ แต่การเขียนตัววนซ้ำนั้นง่ายพอ ๆ

function* iterate_object(o) {
    var keys = Object.keys(o);
    for (var i=0; i<keys.length; i++) {
        yield [keys[i], o[keys[i]]];
    }
}

แล้ว

for (var [key, val] of iterate_object({a: 1, b: 2})) {
    console.log(key, val);
}

a 1
b 2

1
ขอบคุณ torazaburo ฉันได้แก้ไขคำถามของฉันแล้ว ฉันชอบที่จะดูตัวอย่างโดยใช้[Symbol.iterator]และหากคุณสามารถขยายผลที่ไม่ได้ตั้งใจเหล่านั้น
boombox

4

คุณสามารถทำให้วัตถุทั้งหมดทำซ้ำได้ทั่วโลก:

Object.defineProperty(Object.prototype, Symbol.iterator, {
    enumerable: false,
    value: function * (){
        for(let key in this){
            if(this.hasOwnProperty(key)){
                yield [key, this[key]];
            }
        }
    }
});

3
อย่าเพิ่มวิธีการทั่วโลกให้กับวัตถุดั้งเดิม นี่เป็นความคิดที่แย่มากที่จะกัดคุณและใครก็ตามที่ใช้รหัสของคุณในตูด
BT

2

นี่เป็นแนวทางล่าสุด (ซึ่งใช้ได้กับ chrome canary)

var files = {
    '/root': {type: 'directory'},
    '/root/example.txt': {type: 'file'}
};

for (let [key, {type}] of Object.entries(files)) {
    console.log(type);
}

ใช่ entriesตอนนี้เป็นวิธีการที่เป็นส่วนหนึ่งของ Object :)

แก้ไข

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

Object.prototype[Symbol.iterator] = function * () {
    for (const [key, value] of Object.entries(this)) {
        yield {key, value}; // or [key, value]
    }
};

ตอนนี้คุณสามารถทำได้

for (const {key, value:{type}} of files) {
    console.log(key, type);
}

แก้ไข 2

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

for (const {key, value:item1} of example) {
    console.log(key);
    console.log(item1);
    for (const {key, value:item2} of item1) {
        console.log(key);
        console.log(item2);
    }
}

2

ฉันยังกังวลกับคำถามนี้

จากนั้นฉันก็มีความคิดที่จะใช้Object.entries({...})มันส่งคืนArrayซึ่งเป็นไฟล์Iterableไฟล์.

นอกจากนี้ดร. Axel Rauschmayer ยังโพสต์คำตอบที่ยอดเยี่ยมเกี่ยวกับเรื่องนี้ ดูเหตุใดวัตถุธรรมดาจึงไม่สามารถทำซ้ำได้


0

ในทางเทคนิคนี่ไม่ใช่คำตอบสำหรับคำถามว่าทำไม? แต่ฉันได้ปรับคำตอบของ Jack Slocum ข้างต้นตามความคิดเห็นของ BT เป็นบางสิ่งที่สามารถใช้เพื่อทำให้ Object ซ้ำได้

var iterableProperties={
    enumerable: false,
    value: function * () {
        for(let key in this) if(this.hasOwnProperty(key)) yield this[key];
    }
};

var fruit={
    'a': 'apple',
    'b': 'banana',
    'c': 'cherry'
};
Object.defineProperty(fruit,Symbol.iterator,iterableProperties);
for(let v of fruit) console.log(v);

ไม่ค่อยสะดวกเท่าที่ควร แต่ใช้งานได้โดยเฉพาะอย่างยิ่งถ้าคุณมีวัตถุหลายชิ้น:

var instruments={
    'a': 'accordion',
    'b': 'banjo',
    'c': 'cor anglais'
};
Object.defineProperty(instruments,Symbol.iterator,iterableProperties);
for(let v of instruments) console.log(v);

และเนื่องจากทุกคนมีสิทธิ์แสดงความคิดเห็นฉันไม่เห็นว่าทำไม Objects จึงไม่สามารถทำซ้ำได้เช่นกัน หากคุณสามารถเติม polyfill ตามด้านบนหรือใช้for … inแล้วฉันไม่เห็นอาร์กิวเมนต์ง่ายๆ

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

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