Giai thừa javascript

Nó không phải là một kỹ thuật dành riêng cho Javascript, tuy nhiên tiêu đề bài viết là “trong Javascript” vì tôi sẽ lấy một vài ví dụ bằng ngôn ngữ JS

Memoization cơ bản

Ghi nhớ là một kỹ thuật thiết lập chương trình, mục đích là làm tăng hiệu suất của một hàm bằng cách lưu trữ (bộ nhớ đệm) lại những kết quả đã được tính toán trước đó

Đối tượng trong Javascript hoạt động giống như một mảng liên kết (key – value), đây là một ứng cử viên sáng giá cho việc ứng dụng một Object JS như một bộ nhớ đệm – cache

Một chức năng được áp dụng ghi nhớ, khi nó được gọi thì các tham số của nó sẽ được áp dụng để thiết lập các mục chỉ cho bộ đệm. Nếu dữ liệu đã tồn tại trong bộ đệm, thì kết quả sẽ được trả về ngay lập tức thay vì thực hiện toàn bộ chức năng. Tất nhiên, nếu kết quả chưa có trong bộ đệm, thì hàm sẽ được thực thi để lấy kết quả và kết quả đó sẽ được lưu vào bộ đệm

Điều này có nghĩa là rất lớn, hãy tưởng tượng bạn có một chức năng, mỗi lần được gọi nó sẽ mất 1 giây để trả về kết quả, nhưng nếu được gọi lại một lần nữa với cùng tham số, nó sẽ chỉ mất 2 ms . 😀

Chúng ta sẽ làm một ví dụ, hàm tính giá trị thừa của một số tự nhiên (giai thừa) có sử dụng kỹ thuật ghi nhớ. Trong ví dụ, chúng ta sẽ sử dụng chức năng ngay lập tức, chức năng này trả lại một chức năng khác (f), chức năng f sẽ được sử dụng để tính toán thừa của một số. Khi hàm f() được trả về, tính chất đóng trong JS vẫn cho phép hàm này truy cập vào đối tượng ghi nhớ, đối tượng này chứa tất cả các kết quả của hàm f() đã thực hiện trước đó. Mỗi lần f() được gọi, lần đầu tiên f() sẽ kiểm tra xem kết quả trả về có tồn tại cho giá trị (tham số tuyến vào). Nếu kết quả tồn tại, thì kết quả được lưu trữ trong bản ghi nhớ sẽ được trả lại, nếu không có đoạn mã tính giai cấp thừa của một số sẽ được thực thi

Chú thích. Ghi nhớ đối tượng được định nghĩa bên ngoài hàm f() để nó có thể lưu giá trị lưu trữ qua nhiều lần gọi hàm

Xử lý trường hợp hàm có nhiều tham số

Ở phần trước, chức năng của chúng ta chỉ có một tham số – n. Điều này giúp cho việc phát triển việc khai thác kỹ thuật ghi nhớ khá đơn giản. Nhưng cuộc sống không dễ dàng như vậy, hầu hết các chức năng đều có chiều cao hơn 1 tham số, điều này làm cho việc đánh chỉ số của bộ nhớ đêm cache trong kỹ thuật ghi nhớ trở nên khó khăn đôi chút

Để thực hiện kỹ thuật ghi nhớ với hàm có nhiều tham số, bộ đệm cache của chúng ta có thể sẽ là một mảng liên kết nhiều chiều, hoặc tìm cách kết hợp các tham số của hàm để tạo thành một mục duy nhất.

Với cách sử dụng mảng liên kết nhiều chiều, bộ nhớ cache sẽ trở thành một hệ thống phân cấp của đối tượng thay vì một đối tượng đơn lẻ. Mỗi chiều sẽ được đánh chỉ mục bởi một số tham số. Chúng ta sẽ nói tới ví dụ cache là một mảng nhiều chiều, áp dụng với hàm giai thừa(). Trong ví dụ này, giai thừa() sẽ nhận thêm một tham số – “x”, tham số này không “không để làm gì cả”. Mỗi lần hàm được gọi, trong code chúng ta sẽ kiểm tra xem “x” có tồn tại trong cache hay chưa, nếu chưa thì khởi động nó là một đối tượng “rỗng”. Từ đó, "x" của bộ đệm được sử dụng để lưu trữ kết quả của hàm với tham số "n". Kết quả là khi gọi giai thừa(“xbox”, 3) và giai thừa(“ps4”, 3) không được coi là kết quả tương tự

Để thay thế cho công việc phải thiết kế bộ nhớ đệm nhiều chiều, chúng ta có thể sử dụng một bộ đệm đối tượng với chỉ số là sự kết hợp của tất cả các tham số của chức năng. Ví dụ được trình bày dưới đây mô tả việc chuyển đổi các đối số của đối tượng thành một mảng và sử dụng mảng đó làm chỉ số cho bộ nhớ cache. Mỗi hàm trong JS luôn có một đối tượng tích hợp là “đối số”, đối tượng này chứa tất cả các tham số truyền vào khi gọi hàm. Một “đối số” có kiểu giống như một mảng đối tượng. Đối tượng này giống như một Mảng, nhưng không thể sử dụng lại để làm chỉ số cho bộ nhớ đệm. Chính vì thế, việc đầu tiên chúng ta phải làm là chuyển các đối tượng này thành một mảng thực sự. Chúng ta có thể thực hiện công việc này bằng phương thức slice() của mảng (có nhiều cách khác nhau để thực hiện công việc này)

Chú thích. Chúng ta có thêm một biến – “slice”, biến được định nghĩa để ánh xạ tới phương thức slice của Array, bằng cách tạo ra biến này, chúng ta tránh được hàm liên tục gọi tới phương thức Array. nguyên mẫu. lát cắt. Phương thức call() được sử dụng để phương thức slice() áp dụng lên các đối tượng “đối số”

Tham số là một đối tượng

Tất cả các cách trình bày đều không thể áp dụng khi các tham số truyền vào là một hoặc nhiều đối tượng. Khi một đối tượng được sử dụng chỉ làm số cho bộ nhớ đệm, đầu tiên hệ thống sẽ chuyển đổi đối tượng đó thành chuỗi bằng cách sử dụng phương thức toString(), chuỗi nhận được của mọi đối tượng sẽ là “[đối tượng đối tượng]”. Đó là lý do làm nhiều đối tượng khác nhau đều trỏ tới một giá trị tương tự trên bộ nhớ đệm. Vấn đề này có thể được chỉnh sửa bằng cách “chuỗi hóa” các đối tượng tham số để chỉ làm số cho bộ nhớ đệm. Nhưng thực hiện công việc này có thể làm giảm hiệu năng chức năng của bạn đi một chút. Ví dụ dưới đây mô tả chung cho việc tạo ra một hàm ghi nhớ với tham số là đối tượng

Ghi nhớ hóa các chức năng

Ở tất cả các ví dụ trên, các chức năng phải sửa lại nội dung để có thể áp dụng ghi nhớ. Chúng ta có cách để phát triển memoization mà không phải thay đổi mã nội dung của các chức năng. Điều này cực kỳ hữu ích, nó cho phép phần logic của chức năng độc lập với phần mã ghi nhớ. Việc này được thực hiện bằng cách tạo ra một chức năng, chức năng này sẽ nhận một chức năng làm đầu vào và áp dụng ghi nhớ cho chức năng đó

Hàm memoize() sẽ nhận vào một hàm – “func”, hàm này trả về một hàm, hàm mới có cấu trúc bộ nhớ đệm bao quanh hàm “func”

Chú thích. Hàm này không xử lý các hàm có tham số là đối tượng. Để xử lý các loại này, bạn có thể cần một vòng lặp để xử lý từng tham số và chuyển chúng thành một chuỗi đại diện duy nhất cho các tham số

Giới hạn

Với những công việc có tính chất đặc biệt, như truy vấn dữ liệu trong cơ sở dữ liệu, truy vấn các dịch vụ khác bằng yêu cầu http, đọc tệp hoặc những hàm “không thuần thúy” không thể được ưu tiên tối ưu bằng kỹ năng . Vì thế với những loại công việc đặc trưng, ​​bạn cần tìm cách khác để tối ưu hóa chúng. Hoặc phải chấp nhận sự “kém hiệu quả” của các kiểu hàm này, đôi khi điều này không thể tránh được