[] .ForEach.call () làm gì trong JavaScript?


134

Tôi đã xem xét một số đoạn mã và tôi thấy nhiều phần tử gọi một hàm qua danh sách nút với forEach được áp dụng cho một mảng trống.

Ví dụ tôi có một cái gì đó như:

[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

nhưng tôi không thể hiểu làm thế nào nó hoạt động. Bất cứ ai có thể giải thích cho tôi hành vi của mảng trống trước forEach và cách thức callhoạt động?

Câu trả lời:


202

[] là một mảng.
Mảng này không được sử dụng ở tất cả.

Nó đang được đặt trên trang, bởi vì sử dụng một mảng cho phép bạn truy cập vào các nguyên mẫu mảng, như thế nào .forEach.

Điều này chỉ nhanh hơn gõ Array.prototype.forEach.call(...);

Tiếp theo, forEachlà một chức năng lấy một chức năng làm đầu vào ...

[1,2,3].forEach(function (num) { console.log(num); });

... Và đối với mỗi phần tử trong this(nơi thisgiống như mảng, trong đó nó có một lengthvà bạn có thể truy cập các phần của nó như thế nào this[1]), nó sẽ vượt qua ba điều:

  1. phần tử trong mảng
  2. chỉ số của phần tử (phần tử thứ ba sẽ vượt qua 2 )
  3. một tham chiếu đến mảng

Cuối cùng, .calllà một nguyên mẫu mà các chức năng có (đó là một chức năng được gọi trên các chức năng khác).
.callsẽ lấy đối số đầu tiên của nó và thay thế thisbên trong hàm thông thường bằng bất cứ điều gì bạn đã vượt qua call, như là đối số đầu tiên ( undefinedhoặc nullsẽ sử dụng windowtrong JS hàng ngày hoặc sẽ là bất cứ điều gì bạn đã vượt qua, nếu ở "chế độ nghiêm ngặt"). Phần còn lại của các đối số sẽ được chuyển đến hàm ban đầu.

[1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
    console.log(i + ": " + item);
});
// 0: "a"
// 1: "b"
// 2: "c"

Do đó, bạn đang tạo một cách nhanh chóng để gọi forEachhàm và bạn đang thay đổi thistừ mảng trống sang danh sách tất cả <a>các thẻ và cho từng thẻ<a> thứ tự, bạn đang gọi hàm được cung cấp.

BIÊN TẬP

Kết luận logic / Dọn dẹp

Bên dưới, có một liên kết đến một bài viết gợi ý rằng chúng tôi loại bỏ các nỗ lực lập trình chức năng và luôn tuân thủ thủ công, lặp nội tuyến, bởi vì giải pháp này là hack-ish và khó coi.

Tôi muốn nói rằng trong khi .forEachít hữu ích hơn các đối tác của nó, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), nó vẫn phục vụ mục đích khi tất cả các bạn thực sự muốn làm là thay đổi thế giới bên ngoài (không phải mảng), n-lần, trong khi có quyền truy cập vào một trong hai arr[i]hoặc i.

Vậy làm thế nào để chúng ta đối phó với sự chênh lệch, vì Motto rõ ràng là một chàng trai tài năng và hiểu biết, và tôi muốn tưởng tượng rằng tôi biết những gì tôi đang làm / nơi tôi đang đi (bây giờ và sau đó ... ... khác lần đầu tiên học tập)?

Câu trả lời thực sự khá đơn giản, và một cái gì đó chú Bob và Sir Crockford sẽ cả hai mặt, do sự giám sát:

làm sạch nó lên .

function toArray (arrLike) { // or asArray(), or array(), or *whatever*
  return [].slice.call(arrLike);
}

var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);

Bây giờ, nếu bạn đang tự hỏi liệu bạn có cần phải làm điều này không, bản thân bạn, câu trả lời có thể là không ...
Điều này chính xác được thực hiện bởi ... ... mọi thư viện (?) Với các tính năng bậc cao hơn hiện nay.
Nếu bạn đang sử dụng lodash hoặc gạch dưới hoặc thậm chí jQuery, tất cả họ sẽ có cách lấy một tập hợp các phần tử và thực hiện một hành động n lần.
Nếu bạn không sử dụng một thứ như vậy, thì bằng mọi cách, hãy tự viết.

lib.array = (arrLike, start, end) => [].slice.call(arrLike, start, end);
lib.extend = function (subject) {
  var others = lib.array(arguments, 1);
  return others.reduce(appendKeys, subject);
};

Cập nhật cho ES6 (ES2015) và xa hơn

Không chỉ là một phương thức trợ giúp slice( )/ array( )/ etc sẽ giúp cuộc sống dễ dàng hơn cho những người muốn sử dụng danh sách giống như họ sử dụng mảng (mà họ nên), mà còn cho những người có khả năng vận hành trong các trình duyệt ES6 + tương đối gần tương lai, hoặc "dịch chuyển" trong Babel ngày nay, bạn có các tính năng ngôn ngữ được tích hợp sẵn, điều này làm cho loại điều này không cần thiết.

function countArgs (...allArgs) {
  return allArgs.length;
}

function logArgs (...allArgs) {
  return allArgs.forEach(arg => console.log(arg));
}

function extend (subject, ...others) { /* return ... */ }


var nodeArray = [ ...nodeList1, ...nodeList2 ];

Siêu sạch, và rất hữu ích.
Tra cứu các toán tử Rest and Spread ; thử chúng tại trang web BabelJS; nếu ngăn xếp công nghệ của bạn theo thứ tự, hãy sử dụng chúng trong sản xuất với Babel và một bước xây dựng.


Không có lý do chính đáng nào để không thể sử dụng phép chuyển đổi từ mảng không thành mảng ... ... đừng tạo ra một mớ hỗn độn mã của bạn không làm gì ngoài việc dán cùng một dòng xấu xí đó, ở mọi nơi.


Bây giờ tôi có một chút nhầm lẫn nguyên nhân nếu tôi đã làm: console.log (này); Tôi sẽ luôn lấy đối tượng cửa sổ Và tôi nghĩ rằng .call thay đổi giá trị của cái này
Muhammad Saleh

.call không thay đổi giá trị của this, đó là lý do tại sao bạn sử dụng callvới forEach. Nếu forEach trông giống như forEach (fn) { var i = 0; var len = this.length; for (; i < length; i += 1) { fn( this[i], i, this ); }sau đó bằng cách thay đổi thisbằng cách nói forEach.call( newArrLike )có nghĩa là nó sẽ sử dụng đối tượng mới đó như this.
Norguard

2
@MuhammadSaleh .callkhông thay đổi giá trị thisbên trong của những gì bạn chuyển đến forEach, .callthay đổi giá trị this bên trong của forEach . Nếu bạn bối rối về lý do tại sao thiskhông được truyền forEachvào hàm bạn muốn gọi, bạn nên tìm kiếm hành vi của thisJavaScript. Là một phụ lục, chỉ áp dụng ở đây (và bản đồ / lọc / giảm), có một số thứ hai đến forEach, mà bộ thisbên trong hàm arr.forEach(fn, thisInFn)hoặc [].forEach.call(arrLike, fn, thisInFn);hoặc chỉ sử dụng .bind; arr.forEach(fn.bind(thisInFn));
Norguard

1
@th temstero đó chính xác là điểm. []chỉ có như một mảng, do đó bạn có thể .callcác .forEachphương pháp, và thay đổi thiscác thiết lập bạn muốn làm việc trên. .calltrông như thế này: function (thisArg, a, b, c) { var method = this; method(a, b, c); }ngoại trừ việc có ma thuật đen thisbên trong để đặt bên trong methodbằng với những gì bạn đã vượt qua thisArg.
Norguard

1
vậy trong ngắn hạn ... Array.from ()?
zakius

53

Các querySelectorAllphương pháp trả về một NodeList, mà là tương tự như một mảng, nhưng nó không hoàn toàn là một mảng. Do đó, nó không có một forEachphương thức (mà các đối tượng mảng kế thừa thông qua Array.prototype).

Vì a NodeListtương tự như một mảng, các phương thức mảng sẽ thực sự hoạt động trên nó, do đó, bằng cách sử dụng, [].forEach.callbạn đang gọi Array.prototype.forEachphương thức trong ngữ cảnh của NodeList, như thể bạn đã có thể làm một cách đơn giản yourNodeList.forEach(/*...*/).

Lưu ý rằng mảng trống chỉ là một lối tắt đến phiên bản mở rộng, mà bạn có thể cũng sẽ thấy khá thường xuyên:

Array.prototype.forEach.call(/*...*/);

7
Vì vậy, để làm rõ, không có lợi thế trong việc sử dụng [].forEach.call(['a','b'], cb)hơn ['a','b'].forEach(cb)trong các ứng dụng hàng ngày với mảng tiêu chuẩn, chỉ khi cố gắng lặp qua mảng giống như cấu trúc mà không có forEachtrên mẫu thử nghiệm của riêng mình? Đúng không?
Matt Fletcher

2
@MattFletcher: Vâng, đúng rồi. Cả hai sẽ làm việc nhưng tại sao quá nhiều thứ? Chỉ cần gọi phương thức trực tiếp trên mảng.
James Allardice 30/1/2015

Tuyệt thật, cảm ơn nhé. Và tôi không biết tại sao, có lẽ chỉ vì tín dụng đường phố, gây ấn tượng với các bà già trong ALDI và như vậy. Tôi sẽ gắn bó [].forEach():)
Matt Fletcher

var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) hoạt động tốt
daslicht

@daslicht không có trong các phiên bản IE cũ hơn! (mà làm , tuy nhiên, hỗ trợ forEachvề mảng, nhưng không phải đối tượng NodeList)
Djave

21

Các câu trả lời khác đã giải thích mã này rất tốt, vì vậy tôi sẽ chỉ thêm một gợi ý.

Đây là một ví dụ tốt về mã cần được cấu trúc lại để đơn giản và rõ ràng. Thay vì sử dụng [].forEach.call()hoặc Array.prototype.forEach.call()mỗi khi bạn làm điều này, hãy tạo một chức năng đơn giản từ nó:

function forEach( list, callback ) {
    Array.prototype.forEach.call( list, callback );
}

Bây giờ bạn có thể gọi hàm này thay vì mã phức tạp và tối nghĩa hơn:

forEach( document.querySelectorAll('a'), function( el ) {
   // whatever with the current node
});

5

Nó có thể được viết tốt hơn bằng cách sử dụng

Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {

});

Những gì đang làm là document.querySelectorAll('a')trả về một đối tượng tương tự như một mảng, nhưng nó không kế thừa từ Arraykiểu. Vì vậy, chúng ta gọi forEachphương thức từ Array.prototypeđối tượng với bối cảnh là giá trị được trả về bởidocument.querySelectorAll('a')


3
[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

Về cơ bản nó giống như:

var arr = document.querySelectorAll('a');
arr.forEach(function(el) {
   // whatever with the current node
});

2

Muốn cập nhật câu hỏi cũ này:

Lý do để sử dụng [].foreach.call()để lặp qua các phần tử trong các trình duyệt hiện đại hầu hết đã kết thúc. Chúng tôi có thể sử dụng document.querySelectorAll("a").foreach()trực tiếp.

Các đối tượng NodeList là tập hợp các nút, thường được trả về bởi các thuộc tính như Node.childNodes và các phương thức như document.querySelectorAll ().

Mặc dù NodeList không phải là một Array, nhưng có thể lặp lại nó với forEach () . Nó cũng có thể được chuyển đổi thành một Array thực bằng Array.from ().

Tuy nhiên, một số trình duyệt cũ hơn đã không triển khai NodeList.forEach () cũng như Array.from (). Điều này có thể được phá vỡ bằng cách sử dụng Array.prototype.forEach () - xem ví dụ của tài liệu này.


1

Một mảng trống có một forEachthuộc tính trong nguyên mẫu của nó là một đối tượng Hàm. (Mảng trống chỉ là một cách dễ dàng để có được một tham chiếu đến forEachhàm mà tất cả Arraycác đối tượng có.) Các đối tượng hàm, lần lượt, có một thuộc calltính cũng là một hàm. Khi bạn gọi hàm của callHàm, nó sẽ chạy hàm với các đối số đã cho. Đối số đầu tiên trở thành thishàm được gọi.

Bạn có thể tìm tài liệu cho các callchức năng ở đây . Tài liệu cho forEachở đây .


1

Chỉ cần thêm một dòng:

NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;

Và Voila!

document.querySelectorAll('a').forEach(function(el) {
  // whatever with the current node
});

Thưởng thức :-)

Cảnh báo: NodeList là một lớp toàn cầu. Đừng sử dụng giới thiệu này nếu bạn viết thư viện công cộng. Tuy nhiên, đó là cách rất thuận tiện để tăng hiệu quả cho bản thân khi bạn làm việc trên trang web hoặc ứng dụng node.js.


1

Chỉ là một giải pháp nhanh chóng và bẩn thỉu tôi luôn luôn sử dụng. Tôi sẽ không chạm vào các nguyên mẫu, giống như thực hành tốt. Tất nhiên, có rất nhiều cách để làm cho điều này tốt hơn, nhưng bạn có ý tưởng.

const forEach = (array, callback) => {
  if (!array || !array.length || !callback) return
  for (var i = 0; i < array.length; i++) {
    callback(array[i], i);
  }
}

forEach(document.querySelectorAll('.a-class'), (item, index) => {
  console.log(`Item: ${item}, index: ${index}`);
});

0

[]luôn trả về một mảng mới, nó tương đương new Array()nhưng được đảm bảo trả về một mảng vì Arraycó thể bị ghi đè bởi người dùng trong khi []không thể. Vì vậy, đây là một cách an toàn để lấy nguyên mẫu Array, sau đó như được mô tả, callđược sử dụng để thực thi chức năng trên nút bấm giống như mảng (cái này).

Gọi một hàm với giá trị đã cho này và các đối số được cung cấp riêng lẻ. md


Trong []thực tế sẽ luôn trả về một mảng, trong khi window.Arraycó thể được ghi đè bằng bất cứ thứ gì (trong tất cả các trình duyệt cũ), do đó cũng có thể window.Array.prototype.forEachđược ghi đè bằng bất cứ thứ gì. Không có gì đảm bảo an toàn trong cả hai trường hợp, trong các trình duyệt không hỗ trợ getters / setters hoặc niêm phong / đóng băng đối tượng (đóng băng gây ra các vấn đề mở rộng của chính nó).
Norguard

Vui lòng chỉnh sửa câu trả lời để thêm thông tin bổ sung về khả năng ghi đè các prototypephương thức hoặc đó là : forEach.
Xotic750

0

Norguard đã giải thích CÁI GÌ [].forEach.call()James Allardice TẠI SAO chúng tôi làm điều đó: bởi vì querySelector ALL trả về mộtNodeList cái không có phương thức forEach ...

Trừ khi bạn có trình duyệt hiện đại như Chrome 51+, Firefox 50+, Opera 38, Safari 10.

Nếu không, bạn có thể thêm một Polyfill :

if (window.NodeList && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = function (callback, thisArg) {
        thisArg = thisArg || window;
        for (var i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}

0

Rất nhiều thông tin tốt trên trang này (xem câu trả lời + câu trả lời + bình luận ), nhưng gần đây tôi đã có câu hỏi tương tự như OP, và phải mất một vài lần đào để có được toàn bộ bức tranh. Vì vậy, đây là một phiên bản ngắn:

Mục tiêu là sử dụng Arraycác phương thức trên một mảng giống nhưNodeList không có các phương thức đó.

Một mẫu cũ hơn đã chọn các phương thức của Array thông qua Function.call()và sử dụng một mảng bằng chữ ( []) thay Array.prototypevì vì nó ngắn hơn để gõ:

[].forEach.call(document.querySelectorAll('a'), a => {})

Một mẫu mới hơn (bài ECMAScript 2015) sẽ được sử dụng Array.from():

Array.from(document.querySelectorAll('a')).forEach(a => {})
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.