TL; DR
Nhưng còn nhiều điều để khám phá, đọc tiếp ...
JavaScript có ngữ nghĩa mạnh mẽ để lặp qua các mảng và các đối tượng giống như mảng. Tôi đã chia câu trả lời thành hai phần: Tùy chọn cho mảng chính hãng và tùy chọn cho những thứ giống như mảng , chẳng hạn như arguments
đối tượng, các đối tượng lặp khác (ES2015 +), bộ sưu tập DOM, v.v.
Tôi sẽ nhanh chóng lưu ý rằng bạn có thể sử dụng các tùy chọn ES2015 ngay bây giờ , ngay cả trên các công cụ ES5, bằng cách chuyển mã ES2015 sang ES5. Tìm kiếm "dịch mã ES2015" / "Chuyển mã ES6" để biết thêm ...
Được rồi, hãy nhìn vào các lựa chọn của chúng tôi:
Đối với mảng thực tế
Bạn có ba tùy chọn trong ECMAScript 5 ("ES5"), phiên bản được hỗ trợ rộng rãi nhất vào lúc này và hai tùy chọn khác được thêm vào trong ECMAScript 2015 ("ES2015", "ES6"):
- Sử dụng
forEach
và liên quan (ES5 +)
- Sử dụng một
for
vòng lặp đơn giản
- Sử dụng
for-in
đúng
- Sử dụng
for-of
(sử dụng một trình vòng lặp ngầm) (ES2015 +)
- Sử dụng một trình vòng lặp rõ ràng (ES2015 +)
Chi tiết:
1. Sử dụng forEach
và liên quan
Trong bất kỳ môi trường hiện đại mơ hồ nào (vì vậy, không phải IE8) nơi bạn có quyền truy cập vào các Array
tính năng được thêm bởi ES5 (trực tiếp hoặc sử dụng polyfill), bạn có thể sử dụng forEach
( spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
chấp nhận chức năng gọi lại và, tùy chọn, một giá trị sẽ sử dụng như this
khi gọi cuộc gọi lại đó (không được sử dụng ở trên). Gọi lại được gọi cho mỗi mục trong mảng, theo thứ tự, bỏ qua các mục không tồn tại trong các mảng thưa thớt. Mặc dù tôi chỉ sử dụng một đối số ở trên, cuộc gọi lại được gọi với ba: Giá trị của mỗi mục nhập, chỉ mục của mục nhập đó và tham chiếu đến mảng bạn đang lặp lại (trong trường hợp chức năng của bạn không có sẵn ).
Trừ khi bạn hỗ trợ các trình duyệt lỗi thời như IE8 (mà NetApps chỉ chiếm hơn 4% thị phần vào thời điểm viết bài này vào tháng 9 năm 2016), bạn có thể vui vẻ sử dụng forEach
trong một trang web có mục đích chung mà không cần sử dụng . Nếu bạn cần hỗ trợ các trình duyệt lỗi thời, việc làm mờ / polyfilling forEach
dễ dàng thực hiện (tìm kiếm "es5 shim" cho một số tùy chọn).
forEach
có lợi ích là bạn không phải khai báo các biến chỉ mục và giá trị trong phạm vi chứa, vì chúng được cung cấp dưới dạng đối số cho hàm lặp và do đó chỉ nằm trong phạm vi lặp.
Nếu bạn lo lắng về chi phí thời gian chạy của việc thực hiện một cuộc gọi hàm cho mỗi mục nhập mảng, thì đừng; chi tiết .
Ngoài ra, forEach
là chức năng "lặp qua tất cả", nhưng ES5 đã định nghĩa một số chức năng "hữu ích khác theo cách của bạn thông qua mảng và thực hiện mọi thứ", bao gồm:
every
(dừng lặp lại lần đầu tiên khi gọi lại trở lại false
hoặc một cái gì đó falsey)
some
(dừng lặp lại lần đầu tiên khi gọi lại trở lại true
hoặc một cái gì đó trung thực)
filter
(tạo một mảng mới bao gồm các phần tử trong đó hàm bộ lọc trả về true
và bỏ qua các phần tử mà nó trả về false
)
map
(tạo một mảng mới từ các giá trị được trả về bởi hàm gọi lại)
reduce
(xây dựng một giá trị bằng cách gọi lại cuộc gọi lại, chuyển các giá trị trước đó; xem thông số kỹ thuật để biết chi tiết; hữu ích cho việc tóm tắt nội dung của một mảng và nhiều thứ khác)
reduceRight
(thích reduce
, nhưng hoạt động theo thứ tự giảm dần chứ không phải tăng dần)
2. Sử dụng một for
vòng lặp đơn giản
Đôi khi những cách cũ là tốt nhất:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Nếu chiều dài của mảng sẽ không thay đổi trong vòng lặp, và đó là trong mã hiệu suất nhạy cảm (không), một phiên bản hơi phức tạp hơn grabbing chiều dài lên phía trước có thể là một nhỏ nhanh hơn chút:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
Và / hoặc đếm ngược:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Nhưng với các công cụ JavaScript hiện đại, hiếm khi bạn cần tìm ra chút nước ép cuối cùng.
Trong ES2015 trở lên, bạn có thể đặt biến chỉ số và giá trị cục bộ thành for
vòng lặp:
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
//console.log(index); // would cause "ReferenceError: index is not defined"
//console.log(value); // would cause "ReferenceError: value is not defined"
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
try {
console.log(index);
} catch (e) {
console.error(e); // "ReferenceError: index is not defined"
}
try {
console.log(value);
} catch (e) {
console.error(e); // "ReferenceError: value is not defined"
}
Và khi bạn làm điều đó, không chỉ value
mà còn index
được tạo lại cho mỗi lần lặp vòng lặp, nghĩa là các bao đóng được tạo trong thân vòng lặp giữ một tham chiếu đến index
(và value
) được tạo cho lần lặp cụ thể đó:
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
Nếu bạn có năm div, bạn sẽ nhận được "Chỉ số là: 0" nếu bạn nhấp vào lần đầu tiên và "Chỉ mục là: 4" nếu bạn nhấp vào lần cuối. Điều này không hoạt động nếu bạn sử dụng var
thay vì let
.
3. Sử dụng for-in
đúng
Bạn sẽ nhận được những người bảo bạn sử dụng for-in
, nhưng đó không phải for-in
là để làm gì . for-in
các vòng lặp thông qua các thuộc tính vô số của một đối tượng , không phải là các chỉ mục của một mảng. Đơn hàng không được đảm bảo , ngay cả trong ES2015 (ES6). ES2015 + không xác định một để tài sản đối tượng (thông qua [[OwnPropertyKeys]]
, [[Enumerate]]
và những điều mà sử dụng chúng như thế Object.getOwnPropertyKeys
), nhưng nó không xác định rằng for-in
sẽ đi theo thứ tự đó; ES2020 đã làm, mặc dù. (Chi tiết trong câu trả lời khác này .)
Các trường hợp sử dụng thực sự duy nhất cho for-in
một mảng là:
- Đó là một mảng thưa thớt với những khoảng trống lớn trong đó, hoặc
- Bạn đang sử dụng các thuộc tính không phải là phần tử và bạn muốn đưa chúng vào vòng lặp
Chỉ nhìn vào ví dụ đầu tiên đó: Bạn có thể sử dụng for-in
để truy cập các phần tử mảng thưa thớt đó nếu bạn sử dụng các biện pháp bảo vệ phù hợp:
// `a` is a sparse array
var key;
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (key in a) {
if (a.hasOwnProperty(key) && // These checks are
/^0$|^[1-9]\d*$/.test(key) && // explained
key <= 4294967294 // below
) {
console.log(a[key]);
}
}
Lưu ý ba kiểm tra:
Rằng đối tượng có thuộc tính riêng của nó theo tên đó (không phải đối tượng mà nó thừa hưởng từ nguyên mẫu của nó) và
Khóa đó là tất cả các chữ số thập phân (ví dụ: dạng chuỗi thông thường, không phải ký hiệu khoa học) và
Giá trị của khóa khi bị ép buộc thành một số là <= 2 ^ 32 - 2 (là 4.294.967.294). Con số đó đến từ đâu? Đó là một phần định nghĩa của một chỉ mục mảng trong đặc tả . Các số khác (không phải số nguyên, số âm, số lớn hơn 2 ^ 32 - 2) không phải là chỉ mục mảng. Lý do là 2 ^ 32 - 2 là vì nó làm cho giá trị chỉ số lớn nhất thấp hơn 2 ^ 32 - 1 , đó là giá trị tối đa mà một mảng length
có thể có. (Ví dụ: độ dài của một mảng phù hợp với số nguyên không dấu 32 bit.) (Đạo cụ cho RobG để chỉ ra trong một nhận xét trên bài đăng trên blog của tôi rằng thử nghiệm trước đây của tôi không hoàn toàn đúng.)
Bạn sẽ không làm điều đó trong mã nội tuyến, tất nhiên. Bạn sẽ viết một hàm tiện ích. Có lẽ:
// Utility function for antiquated environments without `forEach`
var hasOwn = Object.prototype.hasOwnProperty;
var rexNum = /^0$|^[1-9]\d*$/;
function sparseEach(array, callback, thisArg) {
var index;
for (var key in array) {
index = +key;
if (hasOwn.call(a, key) &&
rexNum.test(key) &&
index <= 4294967294
) {
callback.call(thisArg, array[key], index, array);
}
}
}
var a = [];
a[5] = "five";
a[10] = "ten";
a[100000] = "one hundred thousand";
a.b = "bee";
sparseEach(a, function(value, index) {
console.log("Value at " + index + " is " + value);
});
4. Sử dụng for-of
(sử dụng một trình vòng lặp ngầm) (ES2015 +)
ES2015 đã thêm các trình vòng lặp vào JavaScript. Cách dễ nhất để sử dụng các trình vòng lặp là for-of
câu lệnh mới . Nó trông như thế này:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Dưới vỏ bọc, điều đó nhận được một trình vòng lặp từ mảng và lặp qua nó, nhận các giá trị từ nó. Điều này không có vấn đề gì khi sử dụng for-in
, bởi vì nó sử dụng một trình vòng lặp được xác định bởi đối tượng (mảng) và các mảng xác định rằng các trình vòng lặp của chúng lặp qua các mục nhập của chúng (không phải thuộc tính của chúng). Không giống như for-in
trong ES5, thứ tự mà các mục được truy cập là thứ tự số của các chỉ mục của chúng.
5. Sử dụng một trình vòng lặp rõ ràng (ES2015 +)
Đôi khi, bạn có thể muốn sử dụng một trình vòng lặp rõ ràng . Bạn cũng có thể làm điều đó, mặc dù nó còn cục mịch hơn nhiều for-of
. Nó trông như thế này:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
Iterator là một đối tượng khớp với định nghĩa của Iterator trong đặc tả. next
Phương thức của nó trả về một đối tượng kết quả mới mỗi lần bạn gọi nó. Đối tượng kết quả có một thuộc tính, done
cho chúng ta biết liệu nó đã được thực hiện chưa và một thuộc tính value
có giá trị cho lần lặp đó. ( done
là tùy chọn nếu có false
, value
là tùy chọn nếu có undefined
.)
Ý nghĩa của value
thay đổi tùy thuộc vào iterator; mảng hỗ trợ (ít nhất) ba hàm trả về các vòng lặp:
values()
: Đây là cái tôi đã sử dụng ở trên. Nó trả về một iterator trong đó mỗi value
là sự xâm nhập mảng cho lặp đó ( "a"
, "b"
và "c"
trong ví dụ trước đó).
keys()
: Trả về một trình vòng lặp trong đó mỗi lần lặp value
là chìa khóa cho lần lặp đó (vì vậy đối với phần a
trên của chúng tôi , đó sẽ là "0"
, sau đó "1"
, sau đó "2"
).
entries()
: Trả về một iterator trong đó mỗi value
là một mảng trong biểu mẫu [key, value]
cho lần lặp đó.
Đối với các đối tượng giống như mảng
Ngoài các mảng thực, còn có các đối tượng giống như mảng có length
thuộc tính và thuộc tính với tên số: NodeList
thể hiện, arguments
đối tượng, v.v ... Làm thế nào để chúng ta lặp qua nội dung của chúng?
Sử dụng bất kỳ tùy chọn nào ở trên cho mảng
Ít nhất một số, và có thể là hầu hết hoặc thậm chí là tất cả các cách tiếp cận mảng ở trên thường áp dụng tốt như nhau cho các đối tượng giống như mảng:
Sử dụng forEach
và liên quan (ES5 +)
Các hàm khác nhau trên Array.prototype
là "chung chung có chủ ý" và thường có thể được sử dụng trên các đối tượng giống như mảng thông qua Function#call
hoặc Function#apply
. (Xem Caveat cho các đối tượng được cung cấp bởi máy chủ ở cuối câu trả lời này, nhưng đó là một vấn đề hiếm gặp.)
Giả sử bạn muốn sử dụng forEach
trên một Node
's childNodes
tài sản. Bạn sẽ làm điều này:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Nếu bạn sẽ làm điều đó rất nhiều, bạn có thể muốn lấy một bản sao của tham chiếu hàm vào một biến để sử dụng lại, ví dụ:
// (This is all presumably in some scoping function)
var forEach = Array.prototype.forEach;
// Then later...
forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Sử dụng một for
vòng lặp đơn giản
Rõ ràng, một for
vòng lặp đơn giản áp dụng cho các đối tượng giống như mảng.
Sử dụng for-in
đúng
for-in
với các biện pháp bảo vệ tương tự như với một mảng cũng sẽ hoạt động với các đối tượng giống như mảng; cảnh báo cho các đối tượng cung cấp máy chủ trên # 1 ở trên có thể được áp dụng.
Sử dụng for-of
(sử dụng một trình vòng lặp ngầm) (ES2015 +)
for-of
sử dụng trình vòng lặp được cung cấp bởi đối tượng (nếu có). Điều đó bao gồm các đối tượng cung cấp máy chủ. Chẳng hạn, đặc tả cho NodeList
từ querySelectorAll
đã được cập nhật để hỗ trợ phép lặp. Thông số kỹ thuật cho HTMLCollection
từ getElementsByTagName
không.
Sử dụng một trình vòng lặp rõ ràng (ES2015 +)
Xem # 4.
Tạo một mảng thực sự
Đôi khi, bạn có thể muốn chuyển đổi một đối tượng giống như mảng thành một mảng thực sự. Làm điều đó thật dễ dàng đáng ngạc nhiên:
Sử dụng slice
phương pháp của mảng
Chúng ta có thể sử dụng slice
phương thức của mảng, giống như các phương thức khác được đề cập ở trên là "chung chung có chủ ý" và do đó có thể được sử dụng với các đối tượng giống như mảng, như sau:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Vì vậy, ví dụ, nếu chúng ta muốn chuyển đổi một NodeList
mảng thành một mảng thực sự, chúng ta có thể làm điều này:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Xem Caveat cho các đối tượng cung cấp máy chủ dưới đây. Cụ thể, lưu ý rằng điều này sẽ thất bại trong IE8 trở về trước, điều này không cho phép bạn sử dụng các đối tượng do máy chủ cung cấp như this
thế.
Sử dụng cú pháp lây lan ( ...
)
Cũng có thể sử dụng cú pháp lây lan của ES2015 với các công cụ JavaScript hỗ trợ tính năng này. Giống như for-of
, điều này sử dụng trình vòng lặp được cung cấp bởi đối tượng (xem # 4 trong phần trước):
var trueArray = [...iterableObject];
Vì vậy, ví dụ, nếu chúng ta muốn chuyển đổi một NodeList
mảng thành một mảng thực sự, với cú pháp trải rộng, điều này trở nên khá ngắn gọn:
var divs = [...document.querySelectorAll("div")];
Sử dụng Array.from
Array.from
(đặc tả) | (MDN) (ES2015 +, nhưng dễ dàng được điền đầy đủ) tạo một mảng từ một đối tượng giống như mảng, trước tiên tùy ý chuyển các mục qua chức năng ánh xạ. Vì thế:
var divs = Array.from(document.querySelectorAll("div"));
Hoặc nếu bạn muốn lấy một mảng tên thẻ của các thành phần với một lớp nhất định, bạn sẽ sử dụng chức năng ánh xạ:
// Arrow function (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standard function (since `Array.from` can be shimmed):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Hãy cẩn thận cho các đối tượng cung cấp máy chủ
Nếu bạn sử dụng các Array.prototype
hàm với các đối tượng giống như mảng do máy chủ cung cấp (danh sách DOM và các thứ khác do trình duyệt cung cấp thay vì công cụ JavaScript), bạn cần chắc chắn kiểm tra trong môi trường đích của mình để đảm bảo đối tượng được cung cấp máy chủ hoạt động đúng . Hầu hết đều cư xử đúng mực (bây giờ), nhưng điều quan trọng là phải kiểm tra. Lý do là hầu hết các Array.prototype
phương thức bạn có thể muốn sử dụng đều dựa vào đối tượng do máy chủ cung cấp để trả lời trung thực cho [[HasProperty]]
hoạt động trừu tượng . Khi viết bài này, các trình duyệt thực hiện rất tốt điều này, nhưng thông số 5.1 đã cho phép khả năng một đối tượng cung cấp máy chủ có thể không trung thực. Đó là trong §8.6.2 , một số đoạn bên dưới bảng lớn gần đầu phần đó), trong đó có đoạn:
Các đối tượng máy chủ có thể thực hiện các phương thức nội bộ này theo bất kỳ cách nào trừ khi được quy định khác; ví dụ, một khả năng là [[Get]]
và [[Put]]
đối với một đối tượng máy chủ cụ thể thực sự tìm nạp và lưu trữ các giá trị thuộc tính nhưng [[HasProperty]]
luôn tạo ra sai .
(Tôi không thể tìm thấy sự nói dài giòng tương đương trong ES2015 spec, nhưng đó là ràng buộc để vẫn là trường hợp.) Một lần nữa, như vậy văn bản phổ biến host-cung cấp mảng giống như các đối tượng trong trình duyệt hiện đại [ NodeList
trường hợp, ví dụ] làm tay cầm [[HasProperty]]
chính xác, nhưng điều quan trọng là phải kiểm tra.)
forEach
và không chỉfor
. như đã nói, trong c # thì hơi khác một chút và điều đó làm tôi bối rối :)