Hướng dẫn dùng iterationer JavaScript
Bài viết được sự cho phép của tác giả Huy Trần Một phút dành cho việc tra từ điển liên quan đến Iterator:
Kể từ sau đây thì mình sẽ dùng những chữ này mà không dịch ra tiếng Việt nữa, lý do là dịch ra thì viết dài dòng, bài đã dài rồi, bớt được gì thì bớt lại chút. Trong JavaScript, vòng lặp const text = "hello"; // String là một iterable object for (const c of text) { console.log(c); } // Output: "h" "e" "l" "l" "o" Một đối tượng được coi là iterable nếu như nó thõa mãn iterable protocol,
theo đó, nó phải được implement phương thức Iterator a["@@iterator"] = ... // hoặc a[Symbol.iterator] = ... Hồi xưa các cụ phải xài
Việc implement phương thức const obj = ...; for (const o of obj) { } // không chạy được vì obj không iterable obj[Symbol.iterator] = () => { ... }; for (const o ob obj) { } // giờ thì đã iterable rồi Một object thỏa mãn giao thức next() { return { value: ..., done: false }; } Giá trị của Chúng ta có thể dễ dàng tạo ra một object
thỏa mãn cả hai giao thức let obj = { next: function() { // ... }, [Symbol.iterator]: function() { return this; } }; Cú pháp như này thì rất ngắn gọn, trông rất ngầu, nhưng mà đọc vô sẽ khó hiểu hơn, nên ở các phần tiếp theo chúng ta sẽ không viết như này nữa, mà viết theo cách khác dễ hiểu hơn (cũng dễ reuse hơn). Thêm tí, nếu không lặp bằng let iter; while (iter = iterator.next(), !iter.done) { // ... } Có thể thấy, iterator là một công cụ giải quyết vấn đề khá là lợi hại, để hiểu rõ hơn, hãy cùng xem qua một vài ví dụ với nó. Ví dụ: Tách chuỗi Giả sử chúng ta muốn implement một hàm const text = "The quick brown fox jump over a lazy dog"; Đầu
tiên, chúng ta cần phải tạo ra một iterator, đây sẽ là một class tên class WordIterator { constructor(data) { this.data = data.split(/\s/); this.index = -1; } next() { this.index++; if (this.index < this.data.length) { return { value: this.data[this.index], done: false } } return { done: true }; } } Tiếp theo, chúng ta sẽ implement hàm String.prototype.words = function() { const iterable = this; iterable[Symbol.iterator] = () => new WordIterator(this); return iterable; }; Ở trên, chúng ta tạo một biến trung gian Thế là xong, chạy thử: for (let word of text.words()) { console.log(word); } // Output: // "The" "quick" "brown" "fox" "jump" "over" "a" "lazy" "dog" Quá dễ, phải không nào? Oh, và các bạn biết sao không? Trong cách implement trên, chúng ta đã bỏ qua một thực tế là, kiểu String.prototype.words = function() { return this.split(/\s/); } Ví dụ: Python’s range function OK, giờ lấy ví dụ khác nhé. Hẳn các bạn ai cũng đã từng dùng qua Python, hay cũng từng gặp hàm Chúng ta sẽ mô phỏng hàm này trong JavaScript bằng iterator. Hàm const range = (from, to) => { return { [Symbol.iterator]: function() { return this; }, next: function() { if (from < to) { from++; return { value: from - 1, done: false }; } return { done: true }; } } }; Ở đây chúng ta cũng sử dụng cú pháp implement kiểu rút gọn như đã nói ở phần trước chứ không cần tạo class, vì ở ví dụ này cũng không phức tạp gì mấy, và mục đích của chúng ta là sử dụng hàm Và đây là cách sử dụng: for (const i of range(0, 100)) { console.log(i); } Nếu để ý kĩ cách
implement trên, có thể các bạn sẽ nhận ra là, hàm Ví dụ: Lazy Evaluation Call-by-need là một đặc điểm của Lazy Evaluation (đối lập với call-by-name, đặc tính chỉ các thanh niên nhanh nhẩu, chỉ cần gọi tên là xuất kích) – một khái niệm từ functional programming, giải thích một cách đơn giản không đầy đủ, thì đây là phương pháp xử lý mà khi ta có các biểu thức nối tiếp nhau, chỉ khi nào biểu thức phía sau cần sử dụng kết quả tính toán từ biểu thức phía trước, thì biểu thức trước mới được thực thi. Giả sử, ta có một hàm
const evenNumbers = () => { let index = 0; return { [Symbol.iterator]: function() { return this; }, next: function() { index+=2; return { value: index, done: false }; }, take: function(n) { let values = []; for (let i = 0; i < n; i++) { values.push(this.next().value); } return values; } } }; console.log(evenNumbers().take(5)); Có thể thấy, ta có thể viết theo cách mà người bình thường không ai điên gì mà làm, đó là code logic của hàm Để nói kĩ hơn về Lazy Evaluation, sẽ có một bài viết khác vào một ngày nào đó. Mặc dù khá là hữu ích, nhưng iterator hiện nay vẫn còn một nhược điểm đó là không thể thực hiện các thao tác bất đồng bộ (asynchronous) được, ví dụ, bạn muốn thực hiện HTTP request trong mỗi iteration, hoặc bạn muốn implement một iterator làm nhiệm vụ đọc một file và trả về từng byte hoặc từng line của file đó. Giải pháp thay thế thì hiện đã có proposal, đó là Async Iteration, và nó đã ở stage 4. Bài viết gốc được đăng tải tại thefullsnack.com Có thể bạn quan tâm:
Xem thêm các việc làm JavaScript hấp dẫn tại TopDev |