Hướng dẫn javascript maker - nhà sản xuất javascript

Trong bài viết này, tôi sẽ giải thích về Generator JavaScript và cách chúng sử dụng bộ nhớ so với các hàm JavaScript thông thường.

Hầu như mọi lập trình viên đều gặp phải vấn đề lặp đi lặp lại trên một số lượng lớn các item (chẳng hạn như 1 tập collection các articles, images, database record, v.v.). Và mọi thứ đều ổn cho đến khi server hoặc browser của chúng ta nói: "Hey, bạn đang làm gì vậy? Làm ơn, tôi đang cố xử lý quá nhiều, làm ơn giúp tôi". =)

Trước hết, tôi muốn nói rằng đây không phải là một hướng dẫn đầy đủ cho JavaScript Generator. Đó chỉ là kinh nghiệm của tôi được giải thích ở đây.

Js Generator là gì? Nó là một hàm có thể trả về một giá trị và sau đó tiếp tục thực hiện hàm sau đó. Trong khi một hàm JS thông thường sử dụng một return toán tử, thì hàm generator sử dụng một yield toán tử. Đây là một ví dụ (chú ý đến dấu hoa thị trước tên hàm):

Hàm generator này trả về tất cả số interger từ 0 đến N

Hướng dẫn javascript maker - nhà sản xuất javascript

Nếu chúng ta gọi hàm với bất kỳ đối số nào, nó sẽ trả về một iterator, không phải là một giá trị như chúng ta mong đợi.

Hướng dẫn javascript maker - nhà sản xuất javascript

Để có được giá trị, chúng ta sẽ gọi next() phương thức của đối tượng iterator.

Hướng dẫn javascript maker - nhà sản xuất javascript

Như bạn có thể thấy, kết quả hiện tại được giữ trong thuộc tính value của đối tượng được trả về. Ngoài ra, có một thuộc tính

function* generatorFunc(index) {
  while (index < 2) {
    yield index++;
  }
}

const iterator = generatorFunc(0);

console.log(iterator.next());
// log output: {value : 0, done : false}

console.log(iterator.next());
// log output: {value : 1, done : false}

console.log(iterator.next());
// log output: {value : underfined, done : true}
0 cho biết chức năng của generator function đã hoàn thành công việc của nó hay chưa.

Trong hàm ví dụ ở trên, chúng tôi đã chỉ định

function* generatorFunc(index) {
  while (index < 2) {
    yield index++;
  }
}

const iterator = generatorFunc(0);

console.log(iterator.next());
// log output: {value : 0, done : false}

console.log(iterator.next());
// log output: {value : 1, done : false}

console.log(iterator.next());
// log output: {value : underfined, done : true}
1 làm đối số cho hàm generator của chúng tôi và đó là lý do tại sao hàm sẽ tiếp tục trả về các giá trị từ 0 đến 4 cho mỗi lệnh gọi của phương thức next().

Hướng dẫn javascript maker - nhà sản xuất javascript

Bây giờ tôi muốn cho bạn thấy so sánh của một hàm generator và một hàm thông thường trong một vòng lặp

function* generatorFunc(index) {
  while (index < 2) {
    yield index++;
  }
}

const iterator = generatorFunc(0);

console.log(iterator.next());
// log output: {value : 0, done : false}

console.log(iterator.next());
// log output: {value : 1, done : false}

console.log(iterator.next());
// log output: {value : underfined, done : true}
3 trên một số lượng lớn các item. Giả sử chúng ta cần lặp lại một số lượng lớn các số ngẫu nhiên và làm một cái gì đó với mỗi số. Trong một hàm JavaScript bình thường, nó sẽ có dạng như dưới đây:

Hướng dẫn javascript maker - nhà sản xuất javascript

Để làm điều tương tự với hàm generator, chúng tôi sẽ sử dụng mã sau:

Hướng dẫn javascript maker - nhà sản xuất javascript

Để kiểm tra cả hai chức năng, tôi sẽ tạo một phương thức

function* generatorFunc(index) {
  while (index < 2) {
    yield index++;
  }
}

const iterator = generatorFunc(0);

console.log(iterator.next());
// log output: {value : 0, done : false}

console.log(iterator.next());
// log output: {value : 1, done : false}

console.log(iterator.next());
// log output: {value : underfined, done : true}
4 sẽ kiểm tra mức độ sử dụng bộ nhớ đã thay đổi sau mỗi lần lặp qua các item.
Hướng dẫn javascript maker - nhà sản xuất javascript

Tôi đã tính toán mức sử dụng bộ nhớ với một hàm đơn giản sử dụng thuộc tính

function* generatorFunc(index) {
  while (index < 2) {
    yield index++;
  }
}

const iterator = generatorFunc(0);

console.log(iterator.next());
// log output: {value : 0, done : false}

console.log(iterator.next());
// log output: {value : 1, done : false}

console.log(iterator.next());
// log output: {value : underfined, done : true}
5 của đối tượng
function* generatorFunc(index) {
  while (index < 2) {
    yield index++;
  }
}

const iterator = generatorFunc(0);

console.log(iterator.next());
// log output: {value : 0, done : false}

console.log(iterator.next());
// log output: {value : 1, done : false}

console.log(iterator.next());
// log output: {value : underfined, done : true}
6:
Hướng dẫn javascript maker - nhà sản xuất javascript

Bây giờ hãy gọi phương thức

function* generatorFunc(index) {
  while (index < 2) {
    yield index++;
  }
}

const iterator = generatorFunc(0);

console.log(iterator.next());
// log output: {value : 0, done : false}

console.log(iterator.next());
// log output: {value : 1, done : false}

console.log(iterator.next());
// log output: {value : underfined, done : true}
4 của chúng ta với hàm thông thường và hàm generator để tính toán mức sử dụng bộ nhớ của từng loại.

Đầu tiên chúng ta chạy với hàm JS thông thường

function* generatorFunc(index) {
  while (index < 2) {
    yield index++;
  }
}

const iterator = generatorFunc(0);

console.log(iterator.next());
// log output: {value : 0, done : false}

console.log(iterator.next());
// log output: {value : 1, done : false}

console.log(iterator.next());
// log output: {value : underfined, done : true}
8.

Hướng dẫn javascript maker - nhà sản xuất javascript

Tiếp theo sẽ thực thi với hàm generator

function* generatorFunc(index) {
  while (index < 2) {
    yield index++;
  }
}

const iterator = generatorFunc(0);

console.log(iterator.next());
// log output: {value : 0, done : false}

console.log(iterator.next());
// log output: {value : 1, done : false}

console.log(iterator.next());
// log output: {value : underfined, done : true}
9.

Hướng dẫn javascript maker - nhà sản xuất javascript

Như bạn có thể thấy, hàm JS thông thường cho thấy mức tăng bộ nhớ ~ 46,5 kilobyte trong khi mức tăng của hàm generator chỉ là ~ 0,125 kilobyte . Điều này là do chúng tôi không cần giữ tất cả 2000000 item trong RAM với chức năng generator. Iterator cho phép chúng ta theo dõi current iterator item và tiếp tục trả lại item tiếp theo cho đến khi kết thúc.

Đó là điểm quan trọng cho phép các nhà phát triển tiết kiệm bộ nhớ và theo dõi các biến cục bộ hoặc các vòng lặp bên trong giữa hàm generator mà không cần outer code để biết bất cứ điều gì về logic bên trong của hàm.

Ngoài ra, các trình duyệt làm việc với

value : kết quả của biểu thức trả về.
done : nhận giá trị false nếu quá trình generator chưa hoàn thành, true nếu ngược lại.
0 thông qua generator và promises. Nhưng đó là một chủ đề cho một bài viết khác Tôi hy vọng bạn có một cái gì đó hữu ích từ câu chuyện này. Cảm ơn vì đã đọc!
Hướng dẫn javascript maker - nhà sản xuất javascript
Tôi hy vọng bạn có một cái gì đó hữu ích từ câu chuyện này. Cảm ơn vì đã đọc!

Bài viết được lược dịch từ: https://levelup.gitconnected.com/how-i-met-your-javascript-generators-reduce-memory-used-on-your-browser-and-server-8ed2c5077d5c

1. Generator function

Generator function là một trong những chức năng không còn mới đối với lập trình viên Javascript. Từ phiên bản ECMAScript 2015 (ES6) nó đã được nhà phát triển đưa vào sử dụng bằng cú pháp khai báo "function* " , và tất nhiên là trả về một Generator object.

Đâu đó trên mozilla.org có định nghĩa như sau : "Generators are functions which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances ". Vì thế bạn có thể hiểu rằng nó là một function có thể được thực thi nhiều lần liên tiếp mà ngữ cảnh (số lượng biến, giá trị biến, trạng thái các thành phần bên trong hàm ...) đều có thể lưu lại sử dụng sau mỗi phiên. Với chức năng mới này, Generator function cũng có thể dừng thực thi bất cứ thời điểm nào, đợi một điều kiện nào nó xảy ra rồi mới tiếp tục thực thi (Ví dụ như khi gọi ajax, các kiến trúc async chẳng hạn).

Cú pháp

function* name([param[, param[, ... param]]]) {
   statements
}

Trong đó thì name: tên hàm. param: tham số đầu vào của hàm, tối đa 255 tham số. statements: phần thân chứa nội dung của hàm.

Giá trị trả về

Khi chúng ta gọi Generator function : " nameYourFuntion() " , nó không trả về các kiểu dữ liệu cơ bản mà đẩy về một iterator object . Hàm next() của iterator object được sử dụng để truy xuất các node dữ liệu sau mỗi bước resume lại generator function. Khi đó generator function sẽ thực thi hàm cho đến khi gặp từ khóa yield , hoặc return kế tiếp chưa được duyệt ở bước trước. P/s : iterator định nghĩa một chuẩn để tạo ra list các giá trị hữu hạn hoặc thậm chí vô hạn. Nó giúp bạn lấy ra giá trị mong muốn khi list kết quả đã được khởi tạo xong toàn bộ.

2. Yield

  • Yield được sử dụng ở một vài nơi và có vẻ khái niệm cũng hơi khác nhau. Trong ruby, javascript hay làm chỉ định trong laravel framework ... mỗi nơi có một định nghĩa và cách sử dụng khác nhau. Ngay cả anh google dich cũng sẽ làm bạn lúng túng về nó, "năng suất" , "bày ra", "sản sinh" . Tôi nghe thì thấy có vẻ chả liên quan gì lập trình và chả thấy có gì tò mò để tìm hiểu. Tuy nhiên trong javascript thì yeild lại khá dễ hiểu và dễ hình dung nhé.
  • Về cơ bản, yeild là từ khóa dùng để tạm dừng và cũng để tiếp tục việc thực thi bên trong generator function.

Sử dụng

function* generatorFunc(index) {
  while (index < 2) {
    yield index++;
  }
}

const iterator = generatorFunc(0);

console.log(iterator.next());
// log output: {value : 0, done : false}

console.log(iterator.next());
// log output: {value : 1, done : false}

console.log(iterator.next());
// log output: {value : underfined, done : true}

Như đã đề cập ở trên thì iterator được khởi tạo bằng generatorFunc với index bắt đầu bằng 0. Bạn có thể thấy yeild ở đây, trong ví dụ này chính là một phiên bản khác giống như return vậy. Nó trả về một đối tượng IteratorResult với hai thuộc tính là "value" và "done".

value : kết quả của biểu thức trả về.
done : nhận giá trị false nếu quá trình generator chưa hoàn thành, true nếu ngược lại.

Giá trị của index được giữ lại sau mỗi lần chúng ta gọi next() và tất nhiên là cả ngữ cảnh của hàm generator cũng thế cho đến khi toàn bộ yield, return đã được duyệt qua..

3.Yield*

Yield* là một dạng ủy quyền thực thi. Ở đây, yield* có thể nhúng mã của một generator function ngay sau nó hoặc là ủy quyền trực tiếp cho một iterator object.

Cú pháp :

yield* [[expression]]

expression ở đây trả về iterator object như đã nói, có thể là generator function, hoặc mảng, hoặng string, hoặc list, ....

Ví dụ :

function* g2() {
yield 3;
yield 4;
}

function* g1() {
  yield* [1, 2];
  yield* g2();
  yield* '56';
  yield* Array.from(arguments);
}

var iterator = g(9, 10);

console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 4, done: false}
console.log(iterator.next()); // {value: "5", done: false}
console.log(iterator.next()); // {value: "6", done: false}
console.log(iterator.next()); // {value: 9, done: false}
console.log(iterator.next()); // {value: 10, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

Bạn có thể sử dụng yield* theo nhiều cách như trên, miễn rằng đằng sau đó trả về một object có kiểu thuộc iterator.

Chú ý : yield* là một biểu thức chứ không phải là một câu lệnh, do đó nó sẽ phản ánh lại giá trị được return về. Ví dụ :

function* g2() {
  yield* [1, 2];
  return 't sẽ trở thành vua hải tặc, vd như vậy xem chạy ko';
}

var rs;

function* g() {
  rs = yield* g2();
}

var iterator = g();

console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: undefined, done: true}, 
                              // g2() đã trả về {value: 't sẽ trở thành vua hải tặc, vd như vậy xem chạy ko', don