ข้อผิดพลาด JSLint 'body of for in ควรถูกรวมอยู่ใน if statement' หมายถึงอะไร?


242

ฉันใช้JSLintกับไฟล์ JavaScript ของฉัน มันโยนข้อผิดพลาด:

for( ind in evtListeners ) {

ปัญหาที่บรรทัดที่ 41 อักขระ 9: เนื้อหาของ a for in ควรถูกห่อด้วยคำสั่ง if เพื่อกรองคุณสมบัติที่ไม่ต้องการออกจากต้นแบบ

สิ่งนี้หมายความว่า?


5
โดยค่าเริ่มต้น 'ใน' จะวนซ้ำคุณสมบัติที่สืบทอดมาเช่นกัน โดยปกติร่างกายจะถูกห่อif (evtListeners.hasOwnProperty(ind))เพื่อ จำกัด การประมวลผลเฉพาะคุณสมบัติ (ไม่สืบทอด) เท่านั้น อย่างไรก็ตามในบางกรณีคุณต้องการทำซ้ำกับคุณสมบัติทั้งหมดรวมถึงคุณสมบัติที่สืบทอดมา ในกรณีนั้น JSLint จะบังคับให้คุณตัดส่วนของลูปในคำสั่ง if เพื่อตัดสินใจว่าคุณสมบัติใดที่คุณต้องการจริงๆ สิ่งนี้จะใช้งานได้และทำให้ JSlint มีความสุข: if (evtListeners[ind] !== undefined)
xorcus

1
คำตอบส่วนใหญ่ล้าสมัยแล้ว โซลูชันที่อัปเดตมีอยู่ที่stackoverflow.com/a/10167931/3138375
eli-bd

คำตอบ:


430

ก่อนอื่นอย่าใช้การfor inวนซ้ำเพื่อระบุค่าในอาร์เรย์ ไม่เคย for(var i = 0; i<arr.length; i++)ใช้ดีเก่า

เหตุผลที่อยู่เบื้องหลังนี้คือต่อไปนี้: วัตถุใน JavaScript prototypeแต่ละคนมีข้อมูลพิเศษที่เรียกว่า ทุกสิ่งที่คุณเพิ่มลงในเขตข้อมูลนั้นจะสามารถเข้าถึงได้บนวัตถุทุกประเภทนั้น สมมติว่าคุณต้องการให้อาร์เรย์ทั้งหมดมีฟังก์ชั่นใหม่ที่เรียกfilter_0ว่าจะกรองเลขศูนย์ออก

Array.prototype.filter_0 = function() {
    var res = [];
    for (var i = 0; i < this.length; i++) {
        if (this[i] != 0) {
            res.push(this[i]);
        }
    }
    return res;
};

console.log([0, 5, 0, 3, 0, 1, 0].filter_0());
//prints [5,3,1]

นี่เป็นวิธีมาตรฐานในการขยายวัตถุและเพิ่มวิธีการใหม่ ห้องสมุดจำนวนมากทำเช่นนี้ อย่างไรก็ตามเรามาดูวิธีการfor inทำงานตอนนี้:

var listeners = ["a", "b", "c"];
for (o in listeners) {
    console.log(o);
}
//prints:
//  0
//  1
//  2
//  filter_0

คุณเห็นไหม? ทันใดนั้นคิดว่า filter_0 เป็นดัชนีอาเรย์อีกตัวหนึ่ง แน่นอนมันไม่ได้เป็นดัชนีที่เป็นตัวเลขจริงๆ แต่จะfor inแจกแจงผ่านฟิลด์วัตถุไม่ใช่แค่ดัชนีที่เป็นตัวเลข ตอนนี้เรากำลังแจงนับผ่านดัชนีและ ตัวเลขทุกfilter_0ตัว แต่filter_0ไม่ได้เป็นเขตข้อมูลของวัตถุอาร์เรย์ใด ๆ ทุกวัตถุอาร์เรย์มีคุณสมบัตินี้ในขณะนี้

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

for (o in listeners) {
    if (listeners.hasOwnProperty(o)) {
       console.log(o);
    }
}
 //prints:
 //  0
 //  1
 //  2

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

var listeners = ["a", "b", "c"];
listeners.happy = "Happy debugging";

for (o in listeners) {
    if (listeners.hasOwnProperty(o)) {
       console.log(o);
    }
}

 //prints:
 //  0
 //  1
 //  2
 //  happy

43
คุณไม่ควรใช้for inเพื่อย้ำผ่านอาร์เรย์เพราะภาษาไม่ได้การันตีลำดับที่for inจะแจกแจงอาร์เรย์ มันอาจไม่อยู่ในลำดับที่เป็นตัวเลข นอกจากนี้หากคุณใช้ `สำหรับ (i = 0; ฉัน <array.length ผม ++) สร้างสไตล์ที่คุณสามารถแน่ใจว่าคุณเพียงคุณสมบัติวนดัชนีตัวเลขในการสั่งซื้อและไม่มีตัวอักษรและตัวเลข
Breton

ขอบคุณ! ฉันจะบันทึกสิ่งนี้ไว้เป็นข้อมูลอ้างอิง
nyuszika7h

ฉันพบว่าตัวเองดูคำตอบนี้อีกครั้งเพราะฉันมั่นใจว่า JSLint นี้แตก ฉันมีรหัสที่คร่าวๆ: สำหรับ (o ในผู้ฟัง) {ถ้า (Listeners.hasOwnProperty (i)) {console.log (o); }} ปัญหาคือว่าฉันมีข้อผิดพลาดฉันจะเปลี่ยนชื่อตัวแปรฉันเป็น o และพลาดการอ้างอิง JSLint ฉลาดพอที่จะตรวจสอบให้แน่ใจว่าคุณกำลังตรวจสอบ hasOwnProperty สำหรับคุณสมบัติที่ถูกต้องบนวัตถุที่ถูกต้อง
drewish

12
อย่างไรก็ตามสำหรับ in นั้นสามารถปรับให้ซ้ำกับคุณสมบัติของวัตถุได้ OP ไม่เคยพูดว่า for in ถูกนำไปใช้กับ Array hasOwnProperty เป็นแนวปฏิบัติที่ดีที่สุดอย่างไรก็ตามมีหลายกรณีที่คุณไม่ต้องการตัวอย่างเช่นถ้าวัตถุขยายออกไปอีกวัตถุหนึ่งและคุณต้องการแสดงรายการทั้งวัตถุและคุณสมบัติของผู้ปกครอง
gotofritz

3
ฉันคิดว่าแทนที่จะทำให้ผู้คนกลัวจากfor-inลูป (ซึ่งยอดเยี่ยมมาก) เราควรให้ความรู้แก่พวกเขาเกี่ยวกับวิธีการทำงาน (ทำอย่างถูกต้องในคำตอบนี้) และแนะนำพวกเขาเพื่อObject.defineProperty()ให้พวกเขาสามารถขยายต้นแบบของพวกเขาได้อย่างปลอดภัย อนึ่งต้นแบบขยายวัตถุพื้นเมืองควรไม่Object.definePropertyทำได้โดยไม่ต้อง
Robert Rossmann

87

Douglas Crockford ผู้เขียน jslint ได้เขียน (และพูด) เกี่ยวกับปัญหานี้หลายครั้ง มีส่วนหนึ่งในหน้านี้ของเว็บไซต์ซึ่งครอบคลุมสิ่งนี้:

สำหรับงบ

A สำหรับคลาสของ statement ควรมีแบบฟอร์มต่อไปนี้:

for (initialization; condition; update) {
    statements
}

for (variable in object) {
    if (filter) {
        statements
    } 
}

แบบฟอร์มแรกควรใช้กับอาร์เรย์และวนซ้ำของจำนวนการวนซ้ำที่กำหนดไว้ล่วงหน้า

ควรใช้แบบฟอร์มที่สองกับวัตถุ โปรดระวังว่าสมาชิกที่เพิ่มเข้ากับต้นแบบของวัตถุจะถูกรวมไว้ในการแจงนับ ขอแนะนำให้โปรแกรมตั้งรับโดยใช้วิธีการ hasOwnProperty เพื่อแยกความแตกต่างสมาชิกที่แท้จริงของวัตถุ:

for (variable in object) {
    if (object.hasOwnProperty(variable)) {
        statements
    } 
}

คร็อคฟอร์ดยังมีซีรีย์วิดีโอเกี่ยวกับโรงละคร YUI ที่ซึ่งเขาพูดถึงเรื่องนี้ วิดีโอ / การพูดถึงชุดของ Crockford เกี่ยวกับจาวาสคริปต์ต้องดูว่าคุณจริงจังกับจาวาสคริปต์หรือไม่



8

คำตอบของ Vava อยู่ที่เครื่องหมาย หากคุณใช้ jQuery $.each()ฟังก์ชันจะดูแลสิ่งนี้ดังนั้นจึงปลอดภัยกว่าที่จะใช้

$.each(evtListeners, function(index, elem) {
    // your code
});

5
หากประสิทธิภาพเป็นสิ่งที่ควรพิจารณาฉันไม่แนะนำให้ใช้$.each(หรือ underscore.js _.each) หากคุณสามารถหลีกเลี่ยงการforวนซ้ำดิบ jsperf มีการทดสอบการเปรียบเทียบแบบเปิดหูเปิดตาที่น่าใช้งาน
nickb

3
( jsperf.com/each-vs-each-vs-for-in/3 ) นี้มีความเป็นจริงมากกว่าเพราะมันใช้ตัวกรองโปรโตพื้นฐาน
dvdrtrgn

7

@all - ทุกอย่างใน JavaScript เป็นวัตถุ () ดังนั้นคำสั่งเช่น "ใช้เฉพาะสิ่งนี้กับวัตถุ" เป็นการทำให้เข้าใจผิด นอกจากนี้ยังไม่ได้พิมพ์ JavaScript อย่างยิ่งเพื่อให้ 1 == "1" เป็นจริง (แม้ว่า 1 === "1" ไม่เป็นเช่นนั้น Crockford มีขนาดใหญ่ในเรื่องนี้) เมื่อพูดถึงแนวคิดของอาร์เรย์ใน progromatic ใน JS การพิมพ์มีความสำคัญในคำจำกัดความ

@Brenton - ไม่จำเป็นต้องเป็นเผด็จการศัพท์ "อาเรย์ร่วม", "พจนานุกรม", "แฮช", "วัตถุ" แนวคิดการเขียนโปรแกรมเหล่านี้ทั้งหมดนำไปใช้กับหนึ่งโครงสร้างใน JS เป็นคู่ของชื่อ (คีย์, ดัชนี) ที่ค่าสามารถเป็นวัตถุอื่นได้ (สตริงเป็นวัตถุด้วย)

ดังนั้น new Array()เป็นเช่นเดียวกับ[]

new Object() คล้าย ๆ กับ {}

var myarray = [];

สร้างโครงสร้างที่เป็นอาร์เรย์ที่มีข้อ จำกัด ที่ดัชนีทั้งหมด (คีย์ aka) ต้องเป็นจำนวนเต็ม นอกจากนี้ยังช่วยให้การกำหนดดัชนีใหม่โดยอัตโนมัติผ่าน. พุช ()

var myarray = ["one","two","three"];

เป็นทางเลือกที่ดีที่สุด for(initialization;condition;update){

แต่สิ่งที่เกี่ยวกับ:

var myarray = [];
myarray[100] = "foo";
myarray.push("bar");

ลองสิ่งนี้:

var myarray = [], i;
myarray[100] = "foo";
myarray.push("bar");
myarray[150] = "baz";
myarray.push("qux");
alert(myarray.length);
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}

อาจไม่ใช่การใช้งานอาร์เรย์ที่ดีที่สุด แต่เป็นเพียงภาพประกอบที่สิ่งต่าง ๆ ไม่ถูกตัดออกไปเสมอ

หากคุณรู้ว่ากุญแจของคุณและแน่นอนว่าถ้าพวกเขาไม่ใช่ตัวเลขทั้งหมดตัวเลือกอาร์เรย์เช่นเดียวกับโครงสร้างของคุณคือวัตถุ

var i, myarray= {
   "first":"john",
   "last":"doe",
   100:"foo",
   150:"baz"
};
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}

"ใช้สิ่งนี้กับวัตถุเท่านั้น" หมายความว่าอย่าใช้มันในอาร์เรย์หรือสิ่งอื่นใดที่ขยายวัตถุมิฉะนั้นเมื่อคุณชี้ให้เห็นมันจะโง่มากเพราะทุกอย่างขยายวัตถุ
Juan Mendes

'"อาเรย์แบบเชื่อมโยง", "พจนานุกรม", "แฮช", "วัตถุ" แนวคิดการเขียนโปรแกรมเหล่านี้ทั้งหมดนำไปใช้กับโครงสร้างเดียวใน JS' ครั้งที่พวกเขามีแนวคิดที่แตกต่างจากภาษาที่แตกต่างกันมีความคล้ายคลึงกันกับแต่ละอื่น ๆ แต่ถ้าคุณไปสมมติว่าพวกเขา / เหมือนกัน / และจะใช้ในทางเดียวกันเพื่อจุดประสงค์เดียวกันคุณกำลังตั้งตัวเองเพื่อทำผิดพลาดจริง ๆ ที่คุณสามารถหลีกเลี่ยงได้ด้วยการไม่รู้ภาษาน้อยลง คุณกำลังใช้งาน
Breton

2

แน่นอนมันสุดโต่งที่จะพูด

... อย่าใช้ a ในวงวนเพื่อแจกแจงอาร์เรย์ ไม่เคย ใช้ค่าเก่าสำหรับ (var i = 0; i <arr.length; i ++)

?

การเน้นส่วนในสารสกัด Douglas Crockford นั้นคุ้มค่า

... ควรใช้แบบฟอร์มที่สองกับวัตถุ ...

หากคุณต้องการอาเรย์ (aka Hashtable พจนานุกรม /) var myAssocArray = {key1: "value1", key2: "value2"...};ที่คีย์จะถูกตั้งชื่อแทนการจัดทำดัชนีตัวเลขที่คุณจะต้องดำเนินการนี้เป็นวัตถุเช่น

ในกรณีนี้myAssocArray.lengthจะเกิดขึ้นเป็นโมฆะ (เนื่องจากวัตถุนี้ไม่มีคุณสมบัติ 'ความยาว') และคุณi < myAssocArray.lengthจะไม่ทำให้คุณไปได้ไกลนัก นอกเหนือจากการอำนวยความสะดวกที่มากขึ้นฉันคาดหวังว่าอาเรย์แบบเชื่อมโยงจะเสนอข้อได้เปรียบด้านประสิทธิภาพในหลาย ๆ สถานการณ์เนื่องจากคีย์อาเรย์อาจเป็นคุณสมบัติที่มีประโยชน์ (เช่นคุณสมบัติ ID หรือชื่อสมาชิกอาเรย์ของสมาชิกอาเรย์) array ประเมินซ้ำ ๆ ว่า statement เพื่อค้นหารายการ array ที่อยู่หลัง

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


1
คุณกำลังสับสนอย่างลึกซึ้ง ไม่มีสิ่งเช่น "เชื่อมโยงอาร์เรย์" ในจาวาสคริปต์ นั่นคือแนวคิด php อย่างเคร่งครัด
Breton

เป็นความจริงที่ว่าวัตถุเหล่านี้ไม่มีlengthทรัพย์สิน แต่คุณสามารถทำอย่างอื่นได้:var myArr = []; myArr['key1'] = 'hello'; myArr['key2'] = 'world';
nyuszika7h

3
@ Nyuszika7H นั่นเป็นวิธีที่ผิด หากคุณไม่ต้องการ Array จำนวนเต็มคุณไม่ควรใช้var myArr = []มันควรอยู่var myArr = {}ใน PHP มันเหมือนกัน แต่ไม่ใช่ใน JS
Juan Mendes

"อาเรย์" ที่เชื่อมโยงกันไม่ใช่อาเรย์
Vincent McNabb


0

เพียงเพื่อเพิ่มในหัวข้อสำหรับใน / สำหรับ / $ แต่ละฉันเพิ่มกรณีทดสอบ jsperf สำหรับการใช้ $ .each vs สำหรับใน: http://jsperf.com/each-vs-for-in/2

เบราว์เซอร์ / รุ่นที่แตกต่างกันจัดการมันแตกต่างกัน แต่ดูเหมือนว่า $ .each และตรงไปตรงมาเป็นตัวเลือกที่ประหยัดที่สุด

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

หากคุณทำซ้ำอาร์เรย์เพื่อดำเนินการบางอย่างกับแต่ละคู่คีย์ในนั้นควรใช้วิธี hasOwnProperty หากคุณไม่ใช้ jQuery และใช้ $ .each ถ้าคุณใช้ jQuery

ใช้ทุกครั้งfor(i=0;i<o.length;i++)หากคุณไม่ต้องการอาเรย์แบบเชื่อมโยงแม้ว่า ... lol chrome ทำงานได้เร็วกว่า 97% ในหรือ$.each

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