Phương thức javascript forEach có công dụng gì (bản đồ đó không thể làm được)?


90

Sự khác biệt duy nhất tôi thấy trong map và foreach maplà trả về một mảng và forEachkhông. Tuy nhiên, tôi thậm chí không hiểu dòng cuối cùng của forEachphương thức " func.call(scope, this[i], i, this);". Ví dụ: không phải " this" và " scope" đề cập đến cùng một đối tượng và không phải là this[i]iđề cập đến giá trị hiện tại trong vòng lặp?

Tôi nhận thấy trên một bài đăng khác có người nói "Sử dụng forEachkhi bạn muốn làm điều gì đó trên cơ sở từng yếu tố của danh sách. Bạn có thể thêm những thứ vào trang chẳng hạn. Về cơ bản, nó rất tốt khi bạn muốn" tác dụng phụ ". Tôi không biết tác dụng phụ là gì.

Array.prototype.map = function(fnc) {
    var a = new Array(this.length);
    for (var i = 0; i < this.length; i++) {
        a[i] = fnc(this[i]);
    }
    return a;
}

Array.prototype.forEach = function(func, scope) { 
    scope = scope || this; 
    for (var i = 0, l = this.length; i < l; i++) {
        func.call(scope, this[i], i, this); 
    } 
}

Cuối cùng, có bất kỳ cách sử dụng thực sự nào cho các phương thức này trong javascript không (vì chúng tôi không cập nhật cơ sở dữ liệu) ngoài việc thao tác các số như thế này:

alert([1,2,3,4].map(function(x){ return x + 1})); //this is the only example I ever see of map in javascript.

Cảm ơn vì đã trả lời.


Làm thế nào người ta có thể tìm thấy định nghĩa của một hàm về JavaScript gốc, giống như bạn đã làm với mapforEach? Tất cả những gì tôi nhận được từ Google là thông số kỹ thuật và hướng dẫn sử dụng.
WoodrowShigeru

Xem thêm ngôn ngữ-bất khả tri Có sự khác biệt giữa foreach và map không?
Bergi

Câu trả lời:


94

Sự khác biệt cơ bản giữa mapforEachtrong ví dụ của bạn là forEachhoạt động trên các phần tử mảng ban đầu, trong khi kết mapquả trả về một cách rõ ràng là một mảng mới.

Với việc forEachbạn đang thực hiện một số hành động với - và tùy chọn thay đổi - từng phần tử trong mảng ban đầu. Các forEachphương pháp chạy các chức năng mà bạn cung cấp cho mỗi phần tử, nhưng trả về không có gì ( undefined). Mặt khác, duyệt mapqua mảng, áp dụng một hàm cho từng phần tử và đưa ra kết quả là một mảng mới .

"Tác dụng phụ" forEachlà mảng ban đầu bị thay đổi. "Không có tác dụng phụ" có mapnghĩa là, trong cách sử dụng thành ngữ, các phần tử mảng ban đầu không bị thay đổi; mảng mới là một ánh xạ 1-1 của từng phần tử trong mảng ban đầu - biến đổi ánh xạ là hàm được cung cấp của bạn.

Thực tế là không có cơ sở dữ liệu liên quan không có nghĩa là bạn sẽ không phải thao tác trên các cấu trúc dữ liệu, xét cho cùng, đây là một trong những bản chất của lập trình bằng bất kỳ ngôn ngữ nào. Đối với câu hỏi cuối cùng của bạn, mảng của bạn không chỉ có thể chứa số mà còn chứa các đối tượng, chuỗi, hàm, v.v.


4
lưu ý từ tương lai: câu trả lời này thực sự là vô nghĩa; .mapcó thể làm mọi thứ .forEachcó thể làm (bao gồm cả việc sửa đổi các phần tử), nó chỉ trả về một danh sách mới được xây dựng từ hàm vòng lặp.
Travis Webb

6
Trả lời từ quá khứ: Tôi kính trọng không đồng ý. .map()tạo một mảng mới và không thay đổi mảng ban đầu. Bạn thực sự có thể sửa đổi mảng mà chính nó đang được ánh xạ, nhưng điều đó ít nhất sẽ không phải là thành ngữ, nếu không muốn nói là vô nghĩa. .forEach(), trong khi tương tự, áp dụng chức năng của nó cho từng phần tử, nhưng luôn trả về undefined. Bất chấp tất cả những điều trên, OP cũng đang hỏi về các chức năng cụ thể của riêng mình được thêm vào nguyên mẫu mảng; không có ES5 trở lại đây trong quá khứ.
Ken Redler

4
Vẫn không có gì forEachmà bạn không thể làm với map. Đơn giản là bạn có thể không quan tâm đến giá trị trả về từ đó mapvà bạn có forEach. Sự khác biệt là nó siêu không hiệu quả khi sử dụng mapcho các nhiệm vụ mà bạn không muốn tạo một mảng mới dựa trên kết quả. Vì vậy, đối với những người, bạn sử dụng forEach.
poke

2
@poke: .. và tương tự, bạn có thể làm bất cứ điều gì bạn cần với một forvòng lặp cũ đơn giản . Tôi không đồng ý rằng có sự khác biệt nào đó về "hiệu quả", nhưng tôi cũng không nghĩ rằng có ai tranh luận rằng một thứ có một số phép thuật mà người kia không thể đạt được bằng cách nào đó. Mặc dù trên thực tế, có một điều mapkhông thể làm: không trả về một mảng. Có giá trị trong có JS sống theo sự mong đợi từ các ngôn ngữ khác như hành vi của yêu thích chức năng như map, reduce, filter, vv Ví dụ, bạn có thể thực hiện reduceRightbằng khối xây dựng tương tự, nhưng tại sao không chỉ cần sử dụng reduceRight?
Ken Redler

1
@ChinotoVokro, ví dụ của bạn đang thay đổi rõ ràng mảng ban đầu bằng cách gán b.val = b.val**2 bên trong đối tượng trả về . Đó là điều chính xác mà chúng tôi đang nói thực sự là có thể nhưng khó hiểu và không thành ngữ. Thông thường bạn chỉ cần trả về giá trị mới, không phải cũng gán nó vào mảng ban đầu:a=[{val:1},{val:2},{val:3},{val:4}]; a.map((b)=>{b.val**2}); JSON.stringify(a);
Ken Redler

66

Sự khác biệt chính giữa hai phương pháp là khái niệm và phong cách: Bạn sử dụng forEachkhi bạn muốn làm điều gì đó với hoặc với từng phần tử của một mảng (làm "với" là những gì bài đăng bạn trích dẫn có nghĩa là "tác dụng phụ", tôi nghĩ) , trong khi bạn sử dụng mapkhi muốn sao chép và biến đổi từng phần tử của một mảng (mà không thay đổi bản gốc).

Bởi vì cả hai mapforEachgọi một hàm trên mỗi mục trong một mảng và hàm đó do người dùng định nghĩa, nên hầu như bạn không thể làm gì với cái này và không làm được với cái kia. Mặc dù xấu xí, có thể sử dụng mapđể sửa đổi một mảng tại chỗ và / hoặc làm điều gì đó với các phần tử mảng:

var a = [{ val: 1 }, { val: 2 }, { val: 3 }];
a.map(function(el) {
    el.val++; // modify element in-place
    alert(el.val); // do something with each element
});
// a now contains [{ val: 2 }, { val: 3 }, { val: 4 }]

nhưng rõ ràng hơn và rõ ràng hơn nhiều so với mục đích sử dụng của bạn forEach:

var a = [{ val: 1 }, { val: 2 }, { val: 3 }];
a.forEach(function(el) { 
    el.val++;
    alert(el.val);
});

Đặc biệt nếu, như thường lệ trong thế giới thực, ellà một biến hữu ích mà con người có thể đọc được:

cats.forEach(function(cat) { 
    cat.meow(); // nicer than cats[x].meow()
});

Theo cách tương tự, bạn có thể dễ dàng sử dụng forEachđể tạo một mảng mới:

var a = [1,2,3],
    b = [];
a.forEach(function(el) { 
    b.push(el+1); 
});
// b is now [2,3,4], a is unchanged

nhưng nó sạch hơn để sử dụng map:

var a = [1,2,3],
    b = a.map(function(el) { 
        return el+1; 
    });

Cũng xin lưu ý rằng, vì maptạo một mảng mới, nó có thể phải chịu ít nhất một số lần truy cập hiệu suất / bộ nhớ khi tất cả những gì bạn cần là lặp lại, đặc biệt đối với các mảng lớn - hãy xem http://jsperf.com/map-foreach

Về lý do tại sao bạn muốn sử dụng các hàm này, chúng hữu ích bất cứ khi nào bạn cần thực hiện thao tác mảng trong javascript, điều này (ngay cả khi chúng ta chỉ nói về javascript trong môi trường trình duyệt) khá thường xuyên, hầu như bất cứ lúc nào bạn đang truy cập một mảng mà bạn không viết ra bằng tay trong mã của mình. Bạn có thể đang xử lý một mảng các phần tử DOM trên trang hoặc dữ liệu được lấy từ một yêu cầu AJAX hoặc dữ liệu do người dùng nhập vào biểu mẫu. Một ví dụ phổ biến mà tôi gặp phải là lấy dữ liệu từ một API bên ngoài, nơi bạn có thể muốn sử dụng mapđể chuyển đổi dữ liệu sang định dạng bạn muốn và sau đó sử dụng forEachđể lặp qua mảng mới của bạn để hiển thị nó cho người dùng của bạn.


tôi thấy mọi người luôn sử dụng .map()để sửa đổi các phần tử nội tuyến. Tôi đã theo ấn tượng đó là lợi ích chính của việc sử dụng .maphơn.forEach
chovy

1
@chovy Tôi tin rằng bạn nên chọn dựa trên việc bạn có muốn tạo một mảng kết quả hay không. .mapcó thể sửa đổi các phần tử, có, nhưng nó sẽ tạo ra một mảng mà bạn không cần. Bạn cũng nên xem xét như thế nào lựa chọn một hay khác cho phép người đọc đoán hay không, bạn có tác dụng phụ
leewz

16

Câu trả lời được bình chọn (từ Ken Redler) là sai lệch.

Một tác dụng phụ trong phương tiện khoa học máy tính mà một tài sản của một hàm / phương pháp làm thay đổi trạng thái toàn cầu [wiki] . Theo một nghĩa hẹp nào đó, điều này cũng có thể bao gồm việc đọc từ trạng thái toàn cục, thay vì từ các đối số. Trong lập trình bắt buộc hoặc OO, các tác dụng phụ thường xuất hiện. Và bạn có thể đang tận dụng nó mà không nhận ra.

Sự khác biệt đáng kể giữa forEachmapmapphân bổ bộ nhớ và lưu trữ giá trị trả về, trong khi forEachném nó đi. Xem thông số kỹ thuật của emca để biết thêm thông tin.

Đối với lý do tại sao mọi người nói forEachđược sử dụng khi bạn muốn có một tác dụng phụ là giá trị trả về forEachluôn là undefined. Nếu nó không có tác dụng phụ (không thay đổi trạng thái toàn cục), thì hàm này chỉ lãng phí thời gian của cpu. Trình biên dịch tối ưu hóa sẽ loại bỏ khối mã này và thay thế nó bằng giá trị cuối cùng ( undefined).

Nhân tiện, cần lưu ý rằng JavaScript không có hạn chế về các tác dụng phụ. Bạn vẫn có thể sửa đổi mảng ban đầu bên trong map.

var a = [1,2,3]; //original
var b = a.map( function(x,i){a[i] = 2*x; return x+1} );
console.log("modified=%j\nnew array=%j",a,b);
// output:
// modified=[2,4,6]
// new array=[2,3,4]

1
Câu hỏi này cùng với các câu trả lời và nhận xét của nó luôn thú vị để quay lại vài năm một lần. Cách bản đồ được triển khai trong JS, về mặt phân bổ bộ nhớ, là một góc độ tốt khác.
Ken Redler

6

Đây là một câu hỏi đẹp với một câu trả lời bất ngờ.

Sau đây là dựa trên mô tả chính thức củaArray.prototype.map() .

forEach()có thể làm được điều đó map()không thể. Đó là, map()là một tập hợp siêu nghiêm ngặt của forEach().

Mặc dù map()thường được sử dụng để tạo một mảng mới, nó cũng có thể được sử dụng để thay đổi mảng hiện tại. Ví dụ sau đây minh họa điều này:

var a = [0, 1, 2, 3, 4], mapped = null;
mapped = a.map(function (x) { a[x] = x*x*x; return x*x; });
console.log(mapped); // logs [0, 1, 4, 9, 16]  As expected, these are squares.
console.log(a); // logs [0, 1, 8, 27, 64] These are cubes of the original array!!

Trong ví dụ trên, ađã được thiết lập thuận tiện như vậy a[i] === icho i < a.length. Mặc dù vậy, nó thể hiện sức mạnh map(), và đặc biệt là khả năng thay đổi mảng mà nó được gọi.

Lưu ý1:
Mô tả chính thức ngụ ý rằng map()thậm chí có thể thay đổi độ dài của mảng mà nó được gọi! Tuy nhiên, tôi không thể thấy (một lý do chính đáng) để làm điều này.

Lưu ý 2:
Mặc dù map()bản đồ là một siêu tập hợp forEach(), forEach()vẫn nên được sử dụng khi người ta muốn thay đổi một mảng nhất định. Điều này làm cho ý định của bạn rõ ràng.

Hy vọng điều này sẽ giúp.


8
Thực ra có 1 điều mà forEach có thể làm mà map không làm được - đó là không trả về một mảng.
Antti Haapala,

1
Bạn cũng có thể sử dụng số thứ ba với chức năng lập bản đồ để biến những mảng mục tiêu, thay vì biến scoped:mapped = a.map(function (x, i, arr) { arr[i] = x * x * x; return x * x; });.
pdoherty926

@ pdoherty926 true, nhưng như vậy có thể a.forEach(function(x, i, arr) { ..., một lần nữa, là một cách đúng đắn hơn về mặt khái niệm khi bạn có ý định thay đổi từng mục gốc thay vì các giá trị bản đồ từ mảng này sang mảng khác
conny

@conny: Đồng ý. Ngoài ra, vui lòng xem câu trả lời này , cho một câu hỏi tương tự.
Sumukh Barve

3

Bạn có thể sử dụng mapnhư thể nó đã từng forEach.

Tuy nhiên, nó sẽ làm được nhiều hơn những gì nó phải làm.

scopecó thể là một đối tượng tùy ý; nó không nhất thiết phải như vậy this.

Đối với việc liệu có sử dụng thực sự cho mapforEach, cũng như để hỏi nếu có sử dụng thực sự cho forhoặc whilevòng lặp.


Nhưng tại sao cả "scope" và "this" đều được gọi ở đây: func.call (scope, this [i], i, this); Phạm vi không phải là một tham số ngang bằng với đối tượng hiện tại, là "this"?
JohnMerlino

Không, nó thể bằng đối tượng hiện tại. Bản thân đối tượng được truyền làm tham số thứ ba cho mảng. scope = scope || thiscó nghĩa là "if scopeis falsy (undefined, null, false, v.v.), hãy đặt phạm vi thành thisthay thế và tiếp tục".
wombleton

Bạn có thể liên kết tôi với một ví dụ khi nó không bằng cái này không?
JohnMerlino

developer.mozilla.org/en/Core_JavaScript_1.5_Reference/... có một thuộc "In nội dung của một mảng với một phương pháp đối tượng"
wombleton

1

Trong khi tất cả các câu hỏi trước đều đúng, tôi chắc chắn sẽ phân biệt khác. Việc sử dụng mapforEachcó thể bao hàm ý định.

Tôi thích sử dụng mapkhi tôi chỉ đơn giản là chuyển đổi dữ liệu hiện có theo một cách nào đó (nhưng muốn đảm bảo dữ liệu gốc không thay đổi.

Tôi thích sử dụng forEachkhi tôi đang sửa đổi bộ sưu tập tại chỗ.

Ví dụ,

var b = [{ val: 1 }, { val: 2 }, { val: 3 }];
var c = b.map(function(el) {
    return { val: el.val + 1 }; // modify element in-place
});
console.log(b);
//  [{ val: 1 }, { val: 2 }, { val: 3 }]
console.log(c);
//  [{ val: 3 }, { val: 4 }, { val: 5 }]

Quy tắc chung của tôi là đảm bảo khi bạn mapluôn tạo một số đối tượng / giá trị mới để trả về cho từng phần tử của danh sách nguồn và trả lại nó thay vì chỉ thực hiện một số thao tác trên mỗi phần tử.

Trừ khi bạn có nhu cầu thực sự để sửa đổi danh sách hiện có, nếu không thì việc sửa đổi nó tại chỗ sẽ không thực sự hợp lý và phù hợp hơn với các kiểu lập trình chức năng / bất biến.


1

TL; Câu trả lời DR -

bản đồ luôn trả về một mảng khác.

forEach thì không. Nó là vào bạn để quyết định những gì nó làm. Trả về một mảng nếu bạn muốn hoặc làm điều gì đó khác nếu bạn không muốn.

Tính linh hoạt là mong muốn là một số tình huống nhất định. Nếu nó không phù hợp với những gì bạn đang giải quyết thì hãy sử dụng bản đồ.


0

Một lần nữa, tôi cảm thấy mình giống như một người làm nghề giải mã thêm câu trả lời cho câu hỏi đã được hỏi cách đây 5 năm (rất vui vì có hoạt động khá gần đây, vì vậy tôi không phải là người duy nhất làm phiền người chết :).

Những người khác đã đăng về câu hỏi chính của bạn về sự khác biệt giữa các chức năng. Nhưng đối với ...

Có bất kỳ cách sử dụng thực sự nào cho các phương thức này trong javascript không (vì chúng tôi không cập nhật cơ sở dữ liệu) ngoài việc thao tác các số như thế này:

... thật buồn cười bạn nên hỏi. Mới hôm nay, tôi đã viết một đoạn mã gán một số giá trị từ một biểu thức chính quy cho nhiều biến bằng cách sử dụng bản đồ để chuyển đổi. Nó được sử dụng để chuyển đổi một cấu trúc dựa trên văn bản rất phức tạp thành dữ liệu có thể hình dung được ... nhưng để đơn giản hơn, tôi sẽ đưa ra một ví dụ bằng cách sử dụng chuỗi ngày, bởi vì chúng có lẽ quen thuộc hơn với mọi người (mặc dù, nếu vấn đề của tôi thực sự là với ngày , thay vì map, tôi sẽ sử dụng Date-object, đối tượng này sẽ tự thực hiện công việc một cách xuất sắc).

const DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})Z$/;
const TEST_STRING = '2016-01-04T03:20:00.000Z';

var [
    iYear,
    iMonth,
    iDay,
    iHour,
    iMinute,
    iSecond,
    iMillisecond
    ] = DATE_REGEXP
        // We take our regular expression and...
        .exec(TEST_STRING)
        // ...execute it against our string (resulting in an array of matches)...
        .slice(1)
        // ...drop the 0th element from those (which is the "full string match")...
        .map(value => parseInt(value, 10));
        // ...and map the rest of the values to integers...

// ...which we now have as individual variables at our perusal
console.debug('RESULT =>', iYear, iMonth, iDay, iHour, iMinute, iSecond, iMillisecond);

Vì vậy, ... trong khi đây chỉ là một ví dụ - và chỉ thực hiện một chuyển đổi rất cơ bản cho dữ liệu (chỉ để làm ví dụ) ... thực hiện điều này mà không có bản đồ sẽ là một nhiệm vụ tẻ nhạt hơn nhiều.

Cấp, nó được viết bằng một phiên bản của JavaScript mà tôi không nghĩ quá nhiều trình duyệt hỗ trợ chưa (ít nhất là đầy đủ) nhưng - chúng tôi đang nhận được ở đó. Nếu tôi cần chạy nó trong trình duyệt, tôi tin rằng nó sẽ diễn ra tốt đẹp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.