เป็นไปได้หรือไม่ที่จะเปลี่ยนค่าของอาเรย์เมื่อทำการ foreach ใน javascript


300

ตัวอย่าง:

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

arr.forEach(function(part){
  part = "four";
  return "four";
})

alert(arr);

อาร์เรย์ยังคงอยู่กับค่าดั้งเดิมมีวิธีใดบ้างที่จะมีการเขียนการเข้าถึงองค์ประกอบของอาร์เรย์จากฟังก์ชันวนซ้ำ


1
ที่เกี่ยวข้อง: stackoverflow.com/q/6081868/632951
Pacerier

ลองใช้แผนที่ ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ...... ):x=[2,3,4]; x=x.map(n=>n*2); // [4,6,8]
Justin

คำตอบ:


468

การเรียกกลับถูกส่งผ่านองค์ประกอบดัชนีและอาร์เรย์เอง

arr.forEach(function(part, index, theArray) {
  theArray[index] = "hello world";
});

แก้ไข - ตามที่ระบุไว้ในความคิดเห็น.forEach()ฟังก์ชั่นสามารถใช้อาร์กิวเมนต์ที่สองซึ่งจะใช้เป็นค่าของthisในการโทรกลับไปที่โทรกลับแต่ละครั้ง:

arr.forEach(function(part, index) {
  this[index] = "hello world";
}, arr); // use arr as this

ตัวอย่างที่สองarrนั้นแสดงให้เห็นว่าตัวเองกำลังตั้งค่าเช่นเดียวกับthisในการโทรกลับหนึ่งอาจคิดว่าอาร์เรย์ที่เกี่ยวข้องในการ.forEach()โทรอาจเป็นค่าเริ่มต้นของthisแต่ด้วยเหตุผลใดก็ตามมันไม่ได้; thisจะเป็นundefinedถ้าไม่ได้ระบุอาร์กิวเมนต์ที่สองไว้

(หมายเหตุ: สิ่งข้างต้นเกี่ยวกับthisใช้ไม่ได้ถ้าโทรกลับเป็น=>ฟังก์ชั่นเพราะthisไม่เคยถูกผูกไว้กับสิ่งใดเมื่อฟังก์ชั่นดังกล่าวถูกเรียก)

นอกจากนี้สิ่งสำคัญที่ต้องจำไว้ก็คือมียูทิลิตี้ที่คล้ายคลึงกันทั้งหมดในตระกูล Array และมีคำถามมากมายที่ปรากฏใน Stackoverflow เกี่ยวกับฟังก์ชั่นหนึ่งหรืออีกอันที่ทางออกที่ดีที่สุดคือการเลือกเครื่องมือที่แตกต่างกัน คุณได้:

  • forEach สำหรับการทำสิ่งที่มีหรือกับทุกรายการในอาร์เรย์
  • filter สำหรับการสร้างอาร์เรย์ใหม่ที่มีเฉพาะรายการที่ผ่านการรับรอง
  • map สำหรับการสร้างอาร์เรย์แบบหนึ่งต่อหนึ่งโดยการเปลี่ยนอาร์เรย์ที่มีอยู่
  • some เพื่อตรวจสอบว่าอย่างน้อยหนึ่งองค์ประกอบในอาเรย์เหมาะกับคำอธิบายบางอย่าง;
  • everyเพื่อตรวจสอบว่ารายการทั้งหมดในอาร์เรย์ตรงกับคำอธิบายหรือไม่
  • find เพื่อค้นหาค่าในอาร์เรย์

และอื่น ๆ ลิงค์ MDN


34
ขอบคุณ! ES6: arr.forEach ((o, i, a) => a [i] = myNewVal)
DiegoRBaquero

ทำไมpart(หรือแม้แต่oใน es6) ไม่ได้ถูกกำหนด? วิธีรับมูลค่าซ้ำ?
Daniil Mashkin

@DaniilMashkin partจะเป็นundefinedอย่างไรหากองค์ประกอบของอาร์เรย์ได้รับการกำหนดundefinedอย่างชัดเจน ช่อง "ว่างเปล่า" ของอาร์เรย์ (รายการอาร์เรย์ที่ไม่เคยกำหนดค่าใด ๆ ) จะถูกข้ามไป.forEach()และวิธีการทำซ้ำแถวอื่น ๆ ส่วนใหญ่
แหลม

2
เพื่อความสมบูรณ์.forEach()ยังใช้อาร์กิวเมนต์ที่สองthisArgซึ่งคุณสามารถใช้thisในการโทรกลับ หมายเหตุ: นี่เป็นอาร์กิวเมนต์ที่.forEachไม่ใช่อาร์กิวเมนต์ของการเรียกกลับ
Moha อูฐยิ่งใหญ่

3
ในการใช้thisอาร์กิวเมนต์ส่งเป็นอาร์กิวเมนต์ที่สอง.forEach()คุณจะต้องส่งผ่านฟังก์ชันการโทรกลับโดยใช้ไวยากรณ์function()เนื่องจากการใช้ฟังก์ชันลูกศรของ ES6 () => {}ไม่ได้ผูกบริบท
jpenna

131

Let 's ลองเพื่อให้มันง่ายและหารือเกี่ยวกับวิธีการที่จะทำงานจริง มันจะทำอย่างไรกับประเภทตัวแปรและพารามิเตอร์ฟังก์ชั่น

นี่คือรหัสของคุณที่เรากำลังพูดถึง:

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

arr.forEach(function(part) {
  part = "four";
  return "four";
})

alert(arr);

ก่อนอื่นนี่คือที่ที่คุณควรอ่านเกี่ยวกับ Array.prototype.forEach ():

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

ประการที่สองมาพูดคุยสั้น ๆ เกี่ยวกับประเภทของค่าใน JavaScript

Primitives (undefined, null, String, Boolean, Number) จัดเก็บค่าจริง

อดีต: var x = 5;

ประเภทการอ้างอิง (วัตถุที่กำหนดเอง) เก็บตำแหน่งหน่วยความจำของวัตถุ

อดีต: var xObj = { x : 5 };

และประการที่สามฟังก์ชันพารามิเตอร์ทำงานอย่างไร

ในฟังก์ชั่นพารามิเตอร์ที่มักจะผ่านค่า

เนื่องจากarrเป็นอาร์เรย์ของ Strings จึงเป็นอาร์เรย์ของวัตถุดั้งเดิมซึ่งหมายความว่าพวกเขาจะถูกเก็บไว้ตามค่า

ดังนั้นสำหรับรหัสของคุณข้างต้นนี้หมายถึงว่าแต่ละเวลา forEach () iterates, partมีค่าเท่ากับค่าเช่นเดียวกับarr[index], แต่ไม่วัตถุเดียวกัน

part = "four";จะเปลี่ยนpartตัวแปร แต่จะทิ้งไว้arrคนเดียว

รหัสต่อไปนี้จะเปลี่ยนค่าที่คุณต้องการ:

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

arr.forEach(function(part, index) {
  arr[index] = "four";
});

alert(arr);

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

var arr = [{ num : "one" }, { num : "two"}, { num : "three"}];

arr.forEach(function(part, index) {
  // part and arr[index] point to the same object
  // so changing the object that part points to changes the object that arr[index] points to

  part.num = "four";
});

alert(arr[0].num);
alert(arr[1].num);
alert(arr[2].num);

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

var arr = [{ num : "one" }, { num : "two"}, { num : "three"}];

arr.forEach(function(part, index) {
  // the following will not change the object that arr[index] points to because part now points at a new object
  part = 5;
});

alert(arr[0].num);
alert(arr[1].num);
alert(arr[2].num);

2
แน่นอนคำอธิบายที่ดี! มันจะดีกว่าที่จะขยายคำอธิบายที่วิธีการทำซ้ำอื่น ๆ สำหรับ ... ของ, แผนที่, ... ใหม่สำหรับ ... ของงานในลักษณะที่คล้ายกันมากกว่า forEach สำหรับกรณีดังกล่าวยกเว้นว่า " ดัชนี "หายไปสำหรับการเปลี่ยนแปลงในที่สุดอาร์เรย์ดั้งเดิม (เช่นเดียวกับ forEach) แผนที่ ES5 นั้นดูคล้ายกับ forEach บางทีความเป็นไปได้ที่จะคืนค่าจากแผนที่ทำให้สิ่งต่าง ๆ ชัดเจนขึ้นจากมุมมองทางไวยากรณ์มากกว่าการใช้ดัชนี
Christophe Vidal

1
คำอธิบายที่ดี ขอบคุณ. หลังจากทั้งหมดฉันไม่สามารถรับข้อความนี้: In functions, parameters are always passed by value. แล้วตัวอย่างที่สองล่ะ?
อเล็กซ์

@ 7 ด้านคำสั่งนั้นอธิบายว่าพารามิเตอร์ของฟังก์ชันจะถูกส่งผ่านโดยค่าเสมอ ดังนั้นสำหรับดึกดำบรรพ์มันจะเป็นค่าที่คะแนนดั้งเดิมชี้ไป สำหรับวัตถุมันจะเป็นตำแหน่งที่วัตถุนั้นชี้ไป หน้า w3schools นี้มีคำอธิบายที่ดี ดูส่วนอาร์กิวเมนต์จะมีการผ่านค่าและวัตถุที่จะผ่านอ้างอิง
เดฟ

ในฟังก์ชั่นพารามิเตอร์จะถูกส่งโดยค่าเสมอ บสก ขอบคุณ. +1
Radarbob

92

อาร์เรย์: [1, 2, 3, 4]
ผลลัพธ์:["foo1", "foo2", "foo3", "foo4"]

Array.prototype.map() เก็บอาร์เรย์เดิม

const originalArr = ["Iron", "Super", "Ant", "Aqua"];
const modifiedArr = originalArr.map(name => `${name}man`);

console.log( "Original: %s", originalArr );
console.log( "Modified: %s", modifiedArr );

Array.prototype.forEach() แทนที่อาร์เรย์เดิม

const originalArr = ["Iron", "Super", "Ant", "Aqua"];
originalArr.forEach((name, index) => originalArr[index] = `${name}man`);

console.log( "Overridden: %s", originalArr );


3
"Array.prototype.map () แก้ไขอาเรย์ดั้งเดิมด้วยการกำหนดตัวแปร arr" .map () ไม่เคยแก้ไขอาเรย์ดั้งเดิมมันจะสร้างอาเรย์ตัวใหม่ การกำหนดตัวแปรใหม่จะไม่เปลี่ยนวัตถุต้นฉบับ
vadkou

1
let arr1 = ["1", 2, 3, 4]; arr1.map(function(v) { return "foo"+ v; }); console.log( arr ); Array.prototype.map () ไม่เคยแก้ไขอาเรย์ดั้งเดิม forEach กลายพันธุ์
Anupam Maurya

@AnupamMaurya นั่นไม่จริงเลย mapสามารถกลายพันธุ์อาเรย์ของมันและforEachโดยทั่วไปไม่ได้
user4642212

@SebastianSimon ขอบคุณสำหรับการตอบกลับ ทั้งหมดที่ฉันต้องการเพิ่มสำหรับแต่ละแทนที่ข้อมูล แต่แผนที่ไม่สามารถสร้างสำเนาใหม่ได้
Anupam Maurya

14

Javascript นั้นผ่านไปมาตามตัวอักษรซึ่งหมายความว่าpartเป็นสำเนาของค่าในอาเรย์

หากต้องการเปลี่ยนค่าให้เข้าถึงอาร์เรย์ในลูปของคุณ

arr[index] = 'new value';


ขึ้นอยู่กับประเภทของการpartคัดลอก - แม้ว่าคุณจะถูกต้องว่าตัวแปรไม่ใช่ตัวชี้ แต่เป็นค่า
Bergi

8
การพูดว่า "JavaScript คือการผ่านค่า" เป็นลักษณะทั่วไปรวม มีประเภทการอ้างอิงใน JavaScript ประเภทค่าถูกส่งผ่านตามค่า
Alex Turpin

9
ไม่ใช่การวางหลักเกณฑ์ทั่วไป คำสั่งที่ถูกต้องและสมบูรณ์ Javascript ผ่านการอ้างอิงตามค่า Javascript นั้นผ่านค่าเสมอ
hvgotcodes

1
@Bergi: ไม่ไม่สำคัญเลย ค่าทั้งหมดจะถูกคัดลอกในที่ได้รับมอบหมาย - ค่าเดียวใน JavaScript เป็นพื้นฐานและการอ้างอิง ทั้งดั้งเดิมและการอ้างอิงจะถูกคัดลอกเมื่อได้รับมอบหมาย
newacct

6
@Bergi เพียงเพราะภาษามีสิ่งที่เรียกว่าการอ้างอิงไม่ได้หมายความว่ามันใช้ pass by reference การอ้างอิงสามารถผ่าน (และ) ผ่านค่าได้เช่นค่าของการอ้างอิงจะถูกคัดลอกและการคัดลอกนั้นจะใช้เป็นอาร์กิวเมนต์
hvgotcodes


4

นี่คือคำตอบที่คล้ายกันโดยใช้=>ฟังก์ชั่นสไตล์:

var data = [1,2,3,4];
data.forEach( (item, i, self) => self[i] = item + 10 );

ให้ผลลัพธ์:

[11,12,13,14]

selfพารามิเตอร์ไม่จำเป็นอย่างเคร่งครัดกับฟังก์ชั่นสไตล์ลูกศรเพื่อ

data.forEach( (item,i) => data[i] = item + 10);

ยังใช้งานได้


3

ฟังก์ชั่น. forEach สามารถมีฟังก์ชันการโทรกลับ (แต่ละองค์ประกอบ elementIndex) ดังนั้นโดยทั่วไปสิ่งที่คุณต้องทำคือ:

arr.forEach(function(element,index){
    arr[index] = "four";   //set the value  
});
console.log(arr); //the array has been overwritten.

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

var copy = arr.slice();

3
หากคุณต้องการที่จะทำสำเนาของอาร์เรย์ให้ใช้แทนmap() วนซ้ำข้ามอาร์เรย์ต้นทางและส่งคืนอาร์เรย์ใหม่ที่มีสำเนา [แก้ไข] ของต้นฉบับ: อาร์เรย์ต้นฉบับนั้นไม่มีการเปลี่ยนแปลง forEach()map()
Nicholas Carey

2

ด้วยวิธีการของวัตถุแบบ Array คุณสามารถปรับเปลี่ยนเนื้อหาแบบ Array ได้ แต่เมื่อเปรียบเทียบกับแบบพื้นฐานสำหรับลูปวิธีการเหล่านี้ขาดฟังก์ชั่นที่สำคัญอย่างหนึ่ง คุณไม่สามารถแก้ไขดัชนีในการรันได้

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

พิจารณารหัสนี้ที่เราย้ายรายการที่ตำแหน่งดัชนี 5 ไปยังตำแหน่งดัชนี 2 เมื่อดัชนีนับได้ถึง 5

var ar = [0,1,2,3,4,5,6,7,8,9];
ar.forEach((e,i,a) => {
i == 5 && a.splice(2,0,a.splice(i,1)[0])
console.log(i,e);
}); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 5 5 - 6 6 - 7 7 - 8 8 - 9 9

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

พิจารณารหัสนี้ที่เราย้ายรายการที่ตำแหน่งดัชนี 5 ไปยังตำแหน่งดัชนี 7 เมื่อดัชนีนับได้ถึง 5

var a = [0,1,2,3,4,5,6,7,8,9];
a.forEach((e,i,a) => {
i == 5 && a.splice(7,0,a.splice(i,1)[0])
console.log(i,e);
}); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 5 5 - 6 7 - 7 5 - 8 8 - 9 9

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

var a = [0,1,2,3,4,5,6,7,8,9];
a.forEach((e,i,a) => {
i == 5 && (a.splice(7,0,a.splice(i,1)[0]), i--);
console.log(i,e);
}); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 4 5 - 6 7 - 7 5 - 8 8 - 9 9

อย่างที่คุณเห็นเมื่อเราลดiมันจะไม่ดำเนินต่อจาก 5 แต่ 6 จากที่มันถูกทิ้ง

ดังนั้นจงเก็บเรื่องนี้ไว้ในใจ


1

ในการเพิ่มหรือลบองค์ประกอบทั้งหมดซึ่งจะเปลี่ยนแปลงดัชนีโดยการขยายคำแนะนำของ zhujy_8833 ของ slice () เพื่อทำซ้ำมากกว่าการคัดลอกเพียงแค่นับจำนวนองค์ประกอบที่คุณได้ลบหรือเพิ่มและแก้ไขดัชนีตามนั้น ตัวอย่างเช่นในการลบองค์ประกอบ:

let values = ["A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8"];
let count = 0;
values.slice().forEach((value, index) => {
    if (value === "A2" || value === "A5") {
        values.splice(index - count++, 1);
    };
});
console.log(values);

// Expected: [ 'A0', 'A1', 'A3', 'A4', 'A6', 'A7', 'A8' ]

ในการแทรกองค์ประกอบก่อน:

if (value === "A0" || value === "A6" || value === "A8") {
    values.splice(index - count--, 0, 'newVal');
};

// Expected: ['newVal', A0, 'A1', 'A2', 'A3', 'A4', 'A5', 'newVal', 'A6', 'A7', 'newVal', 'A8' ]

ในการแทรกองค์ประกอบหลังจาก:

if (value === "A0" || value === "A6" || value === "A8") {
    values.splice(index - --count, 0, 'newVal');
};

// Expected: ['A0', 'newVal', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'newVal', 'A7', 'A8', 'newVal']

ในการแทนที่องค์ประกอบ:

if (value === "A3" || value === "A4" || value === "A7") {
    values.splice(index, 1, 'newVal');
};

// Expected: [ 'A0', 'A1', 'A2', 'newVal', 'newVal', 'A5', 'A6', 'newVal', 'A8' ]

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


0

คุณสามารถลองสิ่งนี้หากคุณต้องการแทนที่

var newArray= [444,555,666];
var oldArray =[11,22,33];
oldArray.forEach((name, index) => oldArray [index] = newArray[index]);
console.log(newArray);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.