Chuyển đổi so với hiệu suất if-else C++
Cấu trúc tổng thể của mã của bạn là một trong những yếu tố chính quyết định tốc độ thực thi của mã đó. Có một lượng mã rất nhỏ không nhất thiết có nghĩa là nó sẽ chạy nhanh và có một lượng mã lớn không nhất thiết có nghĩa là nó sẽ chạy chậm. Rất nhiều tác động đến hiệu suất có liên quan trực tiếp đến cách tổ chức mã và cách bạn đang cố gắng giải quyết một vấn đề nhất định Show Các kỹ thuật trong chương này không nhất thiết chỉ dành riêng cho JavaScript và thường được dạy để tối ưu hóa hiệu suất cho các ngôn ngữ khác. Tuy nhiên, có một số sai lệch so với lời khuyên dành cho các ngôn ngữ khác, vì có nhiều công cụ JavaScript hơn để xử lý và những điều kỳ quặc của chúng cần được xem xét, nhưng tất cả các kỹ thuật đều dựa trên kiến thức khoa học máy tính phổ biến Trong hầu hết các ngôn ngữ lập trình, phần lớn thời gian thực thi mã được sử dụng trong các vòng lặp. Vòng lặp qua một loạt các giá trị là một trong những mẫu được sử dụng thường xuyên nhất trong lập trình và do đó cũng là một trong những lĩnh vực cần tập trung nỗ lực cải thiện hiệu suất. Hiểu tác động hiệu suất của các vòng lặp trong JavaScript đặc biệt quan trọng, vì các vòng lặp vô hạn hoặc dài hạn ảnh hưởng nghiêm trọng đến trải nghiệm người dùng tổng thể ECMA-262, Phiên bản thứ 3, đặc tả xác định hành vi và cú pháp cơ bản của JavaScript, xác định bốn loại vòng lặp. Đầu tiên là vòng lặp for (var prop in object){ //loop body }0 tiêu chuẩn, chia sẻ cú pháp của nó với các ngôn ngữ giống C khác for (var i=0; i < 10; i++){ //loop body } Vòng lặp for (var prop in object){ //loop body }0 có xu hướng là cấu trúc vòng lặp JavaScript được sử dụng phổ biến nhất. Có bốn phần của vòng lặp for (var prop in object){ //loop body }0. khởi tạo, điều kiện kiểm tra trước, thực thi sau và phần thân vòng lặp. Khi gặp vòng lặp for (var prop in object){ //loop body }0, mã khởi tạo được thực thi trước, tiếp theo là điều kiện thử nghiệm. Nếu điều kiện thử nghiệm ước tính là for (var prop in object){ //loop body }4, thì phần thân của vòng lặp được thực thi. Sau khi phần thân được thực thi, mã sau khi thực thi được chạy. Sự đóng gói được nhận thức của vòng lặp for (var prop in object){ //loop body }0 khiến nó trở thành một yêu thích của các nhà phát triển Ghi chúLưu ý rằng việc đặt câu lệnh for (var prop in object){ //loop body }6 trong phần khởi tạo của vòng lặp for (var prop in object){ //loop body }0 sẽ tạo biến cấp hàm, không phải biến cấp vòng lặp. JavaScript chỉ có phạm vi cấp chức năng và do đó, việc xác định một biến mới bên trong vòng lặp for (var prop in object){ //loop body }0 cũng giống như xác định một biến mới bên ngoài vòng lặp Loại vòng lặp thứ hai là vòng lặp for (var prop in object){ //loop body }9. Vòng lặp for (var prop in object){ //loop body }9 là vòng lặp thử nghiệm đơn giản bao gồm điều kiện thử nghiệm trước và thân vòng lặp for (var i=0; i < 10; i++){ //loop body }2 Trước khi thân vòng lặp được thực thi, điều kiện thử nghiệm trước được đánh giá. Nếu điều kiện đánh giá là for (var prop in object){ //loop body }4, thì thân vòng lặp được thực thi; . Bất kỳ vòng lặp for (var prop in object){ //loop body }0 nào cũng có thể được viết thành vòng lặp for (var prop in object){ //loop body }9 và ngược lại Loại vòng lặp thứ ba là vòng lặp for (var i=0; i < 10; i++){ //loop body }64. Vòng lặp for (var i=0; i < 10; i++){ //loop body }64 là vòng lặp hậu kiểm duy nhất có sẵn trong JavaScript và được tạo thành từ hai phần, phần thân vòng lặp và điều kiện hậu kiểm for (var i=0; i < 10; i++){ //loop body }8 Trong vòng lặp for (var i=0; i < 10; i++){ //loop body }64, thân vòng lặp luôn được thực hiện ít nhất một lần và điều kiện sau kiểm tra xác định xem vòng lặp có nên được thực hiện lại hay không Vòng lặp thứ tư và vòng lặp cuối cùng là vòng lặp for (var i=0; i < 10; i++){ //loop body }67. Vòng lặp này có một mục đích rất đặc biệt. nó liệt kê các thuộc tính được đặt tên của bất kỳ đối tượng nào. Định dạng cơ bản như sau for (var prop in object){ //loop body } Mỗi khi vòng lặp được thực thi, biến for (var i=0; i < 10; i++){ //loop body }68 được điền tên của một thuộc tính khác (một chuỗi) tồn tại trên for (var i=0; i < 10; i++){ //loop body }69 cho đến khi tất cả các thuộc tính được trả về. Các thuộc tính được trả về đều là những thuộc tính tồn tại trên đối tượng và những thuộc tính được kế thừa thông qua chuỗi nguyên mẫu của nó Một nguồn tranh luận liên tục về hiệu suất của vòng lặp là sử dụng vòng lặp nào. Trong số bốn loại vòng lặp do JavaScript cung cấp, chỉ một trong số chúng chậm hơn đáng kể so với các loại khác. vòng lặp for (var i=0; i < 10; i++){ //loop body }67 Vì mỗi lần lặp qua vòng lặp dẫn đến tra cứu thuộc tính trên phiên bản hoặc trên nguyên mẫu, nên vòng lặp for (var i=0; i < 10; i++){ //loop body }67 có nhiều chi phí hơn đáng kể cho mỗi lần lặp và do đó chậm hơn các vòng lặp khác. Với cùng số lần lặp lại, một vòng lặp for (var i=0; i < 10; i++){ //loop body }67 có thể kết thúc chậm hơn bảy lần so với các loại vòng lặp khác. Vì lý do này, bạn nên tránh vòng lặp for (var i=0; i < 10; i++){ //loop body }67 trừ khi ý định của bạn là lặp qua một số thuộc tính đối tượng không xác định. Nếu bạn có một danh sách các thuộc tính hữu hạn, đã biết để lặp lại, thì việc sử dụng một trong các loại vòng lặp khác và sử dụng một mẫu như thế này sẽ nhanh hơn for (var i=0; i < 10; i++){ //loop body }6 Mã này tạo một mảng có các thành viên là tên thuộc tính. Vòng lặp for (var prop in object){ //loop body }9 được sử dụng để lặp lại số lượng nhỏ thuộc tính này và xử lý thành viên thích hợp trên for (var i=0; i < 10; i++){ //loop body }69. Thay vì tìm kiếm từng và mọi thuộc tính trên for (var i=0; i < 10; i++){ //loop body }69, mã chỉ tập trung vào các thuộc tính quan tâm, tiết kiệm thời gian và chi phí vòng lặp Ghi chúBạn không bao giờ nên sử dụng for (var i=0; i < 10; i++){ //loop body }67 để lặp lại các phần tử của một mảng Ngoài vòng lặp for (var i=0; i < 10; i++){ //loop body }67, tất cả các loại vòng lặp khác đều có các đặc tính hiệu suất tương đương, do đó không hữu ích khi cố gắng xác định vòng lặp nào nhanh nhất. Việc lựa chọn loại vòng lặp phải dựa trên yêu cầu của bạn hơn là mối quan tâm về hiệu suất Nếu loại vòng lặp không đóng góp vào hiệu suất của vòng lặp, thì điều gì sẽ xảy ra?
Bằng cách giảm một trong hai hoặc cả hai điều này, bạn có thể tác động tích cực đến hiệu suất tổng thể của vòng lặp Giảm công việc trên mỗi lần lặpLý do là nếu một lần đi qua một vòng lặp mất nhiều thời gian để thực hiện, thì nhiều lần đi qua vòng lặp sẽ còn lâu hơn nữa. Hạn chế số lượng thao tác đắt tiền được thực hiện trong thân vòng lặp là một cách tốt để tăng tốc toàn bộ vòng lặp Một vòng lặp xử lý mảng điển hình có thể được tạo bằng bất kỳ loại nào trong ba loại vòng lặp nhanh hơn. Mã được viết thường xuyên nhất như sau for (var prop in object){ //loop body }4 Trong mỗi vòng lặp này, có một số thao tác xảy ra mỗi khi thân vòng lặp được thực thi
Có rất nhiều thứ đang diễn ra trên mỗi vòng lặp đơn giản này, mặc dù không có nhiều mã. Tốc độ mà mã sẽ thực thi phần lớn được xác định bởi những gì for (var prop in object){ //loop body }66 thực hiện với từng mục, nhưng ngay cả như vậy, việc giảm tổng số thao tác trên mỗi lần lặp có thể cải thiện đáng kể hiệu suất vòng lặp tổng thể Bước đầu tiên trong việc tối ưu hóa số lượng công việc trong một vòng lặp là giảm thiểu số lần tra cứu thành viên đối tượng và mục mảng. Như đã thảo luận trong Chương 2, các biến này mất nhiều thời gian hơn để truy cập trong hầu hết các trình duyệt so với các biến cục bộ hoặc giá trị bằng chữ. Các ví dụ trước thực hiện tra cứu thuộc tính cho for (var prop in object){ //loop body }49 mỗi lần qua vòng lặp. Làm như vậy là lãng phí, vì giá trị này sẽ không thay đổi trong quá trình thực hiện vòng lặp và do đó là một cú đánh hiệu suất không cần thiết. Bạn có thể cải thiện hiệu suất vòng lặp một cách dễ dàng bằng cách thực hiện tra cứu thuộc tính một lần, lưu trữ giá trị trong một biến cục bộ, sau đó sử dụng biến đó trong điều kiện kiểm soát for (var prop in object){ //loop body }6 Mỗi vòng lặp được viết lại này thực hiện tra cứu một thuộc tính duy nhất cho độ dài mảng trước khi thực hiện vòng lặp. Điều này cho phép điều kiện kiểm soát chỉ bao gồm các biến cục bộ và do đó chạy nhanh hơn nhiều. Tùy thuộc vào độ dài của mảng, bạn có thể tiết kiệm khoảng 25% tổng thời gian thực hiện vòng lặp trong hầu hết các trình duyệt (và tới 50% trong Internet Explorer) Bạn cũng có thể tăng hiệu suất của các vòng lặp bằng cách đảo ngược thứ tự của chúng. Thông thường, thứ tự xử lý các mục mảng không liên quan đến tác vụ và do đó, bắt đầu từ mục cuối cùng và xử lý mục đầu tiên là một giải pháp thay thế có thể chấp nhận được. Đảo ngược thứ tự vòng lặp là một cách tối ưu hóa hiệu suất phổ biến trong các ngôn ngữ lập trình nhưng nhìn chung vẫn chưa được hiểu rõ lắm. Trong JavaScript, việc đảo ngược vòng lặp sẽ dẫn đến cải thiện hiệu suất nhỏ cho các vòng lặp, với điều kiện là bạn loại bỏ các thao tác bổ sung do đó for (var prop in object){ //loop body }5 Các vòng lặp trong ví dụ này được đảo ngược và kết hợp điều kiện điều khiển với thao tác giảm dần. Mỗi điều kiện kiểm soát bây giờ chỉ đơn giản là so sánh với số không. Các điều kiện kiểm soát được so sánh với giá trị for (var prop in object){ //loop body }4 và bất kỳ số khác không nào sẽ tự động bị ép buộc thành for (var prop in object){ //loop body }4, làm cho số 0 tương đương với for (var prop in object){ //loop body }50. Trên thực tế, điều kiện kiểm soát đã được thay đổi từ hai phép so sánh (bộ lặp có nhỏ hơn tổng và giá trị đó bằng với for (var prop in object){ //loop body }4 không?) thành một phép so sánh duy nhất (giá trị có phải là for (var prop in object){ //loop body }4 không?). Cắt giảm từ hai lần so sánh trên mỗi lần lặp xuống còn một lần tăng tốc các vòng lặp hơn nữa. Bằng cách đảo ngược các vòng lặp và giảm thiểu tra cứu thuộc tính, bạn có thể thấy thời gian thực hiện nhanh hơn tới 50%â60% so với ban đầu Để so sánh với bản gốc, đây là các hoạt động được thực hiện trên mỗi lần lặp cho các vòng lặp này
Mã vòng lặp mới có ít hơn hai thao tác trên mỗi lần lặp, điều này có thể dẫn đến tăng hiệu suất khi số lần lặp tăng lên Ghi chúGiảm công việc được thực hiện trên mỗi lần lặp là hiệu quả nhất khi vòng lặp có độ phức tạp là O(n). Khi vòng lặp phức tạp hơn O(n), bạn nên tập trung chú ý vào việc giảm số lần lặp lại Giảm số lần lặpNgay cả mã nhanh nhất trong thân vòng lặp cũng sẽ cộng lại khi lặp lại hàng nghìn lần. Ngoài ra, có một lượng nhỏ chi phí hoạt động liên quan đến việc thực thi phần thân vòng lặp, điều này chỉ làm tăng thêm thời gian thực hiện tổng thể. Do đó, việc giảm số lần lặp trong suốt vòng lặp có thể dẫn đến hiệu suất tăng cao hơn. Cách tiếp cận nổi tiếng nhất để hạn chế lặp lại vòng lặp là một mẫu có tên là Thiết bị Duffâs Duffâs Device là một kỹ thuật hủy kiểm soát các thân vòng lặp để mỗi lần lặp thực sự thực hiện công việc của nhiều lần lặp. Jeff Greenberg được ghi nhận là người đầu tiên xuất bản cổng Duffâs Device sang JavaScript từ triển khai ban đầu của nó trong C. Một triển khai điển hình trông như thế này for (var prop in object){ //loop body }5 Ý tưởng cơ bản đằng sau việc triển khai Thiết bị Duffâs này là mỗi chuyến đi qua vòng lặp được phép thực hiện tối đa tám cuộc gọi tới for (var prop in object){ //loop body }66. Số lần lặp qua vòng lặp được xác định bằng cách chia tổng số mục cho tám. Bởi vì không phải tất cả các số đều chia hết cho 8, biến for (var prop in object){ //loop body }58 giữ phần còn lại và cho biết có bao nhiêu cuộc gọi đến for (var prop in object){ //loop body }66 sẽ xảy ra trong chuyến đi đầu tiên qua vòng lặp. Nếu có 12 mục, thì chuyến đi đầu tiên qua vòng lặp sẽ gọi for (var prop in object){ //loop body }66 4 lần, sau đó chuyến thứ hai sẽ gọi for (var prop in object){ //loop body }66 8 lần, tổng cộng có hai chuyến đi qua vòng lặp thay vì 12 Phiên bản nhanh hơn một chút của thuật toán này loại bỏ câu lệnh for (var prop in object){ //loop body }52 và tách phần xử lý còn lại khỏi phần xử lý chính for (var prop in object){ //loop body }2 Mặc dù việc triển khai này bây giờ là hai vòng lặp thay vì một, nhưng nó chạy nhanh hơn so với ban đầu bằng cách loại bỏ câu lệnh for (var prop in object){ //loop body }52 khỏi thân vòng lặp Việc sử dụng Thiết bị của Duff, phiên bản gốc hay phiên bản sửa đổi, có đáng hay không, phụ thuộc phần lớn vào số lần lặp lại mà bạn đang thực hiện. Trong trường hợp số lần lặp vòng lặp ít hơn 1.000, bạn có thể chỉ thấy mức độ cải thiện hiệu suất không đáng kể so với việc sử dụng cấu trúc vòng lặp thông thường. Tuy nhiên, khi số lần lặp lại tăng lên 1.000 lần, hiệu quả của Thiết bị Duffâs tăng lên đáng kể. Chẳng hạn, ở 500.000 lần lặp, thời gian thực hiện ít hơn tới 70% so với một vòng lặp thông thường Phiên bản thứ năm của ECMA-262 đã giới thiệu một phương thức mới trên đối tượng gốc for (var prop in object){ //loop body }54 có tên là for (var prop in object){ //loop body }55. Phương thức này lặp qua các thành viên của một mảng và chạy một hàm trên mỗi. Hàm được chạy trên mỗi mục được truyền vào for (var prop in object){ //loop body }55 dưới dạng đối số và sẽ nhận ba đối số khi được gọi, đó là giá trị mục mảng, chỉ mục của mục mảng và chính mảng đó. Sau đây là một ví dụ sử dụng for (var i=0; i < 10; i++){ //loop body }20 Phương pháp for (var prop in object){ //loop body }55 được triển khai nguyên bản trong Firefox, Chrome và Safari. Ngoài ra, hầu hết các thư viện JavaScript đều có logic tương đương for (var i=0; i < 10; i++){ //loop body }21 Mặc dù phép lặp dựa trên chức năng đại diện cho một phương pháp lặp thuận tiện hơn, nhưng nó cũng chậm hơn một chút so với phép lặp dựa trên vòng lặp. Sự chậm lại có thể được giải thích bằng chi phí liên quan đến một phương thức bổ sung được gọi trên mỗi mục mảng. Trong mọi trường hợp, phép lặp dựa trên chức năng mất tới tám lần so với phép lặp dựa trên vòng lặp và do đó không phải là cách tiếp cận phù hợp khi thời gian thực hiện là một mối quan tâm đáng kể Về bản chất tương tự như các vòng lặp, các điều kiện xác định cách thức thực thi chảy qua JavaScript. Đối số truyền thống về việc sử dụng câu lệnh for (var prop in object){ //loop body }58 hay câu lệnh for (var prop in object){ //loop body }52 áp dụng cho JavaScript giống như đối với các ngôn ngữ khác. Vì các trình duyệt khác nhau đã triển khai các tối ưu hóa kiểm soát luồng khác nhau nên không phải lúc nào cũng rõ nên sử dụng kỹ thuật nào Lý thuyết phổ biến về việc sử dụng for (var prop in object){ //loop body }58 so với for (var prop in object){ //loop body }52 dựa trên số lượng điều kiện được thử nghiệm. số lượng điều kiện càng lớn, bạn càng có xu hướng sử dụng for (var prop in object){ //loop body }52 thay vì for (var prop in object){ //loop body }58. Điều này thường dẫn đến việc mã nào dễ đọc hơn. Lập luận là for (var prop in object){ //loop body }58 dễ đọc hơn khi có ít điều kiện hơn và for (var prop in object){ //loop body }52 dễ đọc hơn khi số lượng điều kiện lớn. Hãy xem xét những điều sau đây for (var i=0; i < 10; i++){ //loop body }22 Mặc dù cả hai đoạn mã đều thực hiện cùng một nhiệm vụ, nhưng nhiều người sẽ cho rằng câu lệnh for (var prop in object){ //loop body }58 dễ đọc hơn nhiều so với câu lệnh for (var prop in object){ //loop body }52. Tuy nhiên, việc tăng số lượng điều kiện thường đảo ngược quan điểm đó for (var i=0; i < 10; i++){ //loop body }23 Hầu hết sẽ coi câu lệnh for (var prop in object){ //loop body }52 trong mã này dễ đọc hơn câu lệnh for (var prop in object){ //loop body }58 Hóa ra, câu lệnh for (var prop in object){ //loop body }52 nhanh hơn trong hầu hết các trường hợp khi so sánh với for (var prop in object){ //loop body }58, nhưng chỉ nhanh hơn đáng kể khi số lượng điều kiện lớn. Sự khác biệt chính về hiệu suất giữa hai loại này là chi phí gia tăng của một điều kiện bổ sung đối với for (var prop in object){ //loop body }58 lớn hơn so với đối với for (var prop in object){ //loop body }52. Do đó, xu hướng tự nhiên của chúng tôi là sử dụng for (var prop in object){ //loop body }58 cho một số ít điều kiện và tuyên bố for (var prop in object){ //loop body }52 cho số lượng điều kiện lớn hơn chính xác là lời khuyên đúng đắn khi xem xét hiệu suất Nói chung, for (var prop in object){ //loop body }58 được sử dụng tốt nhất khi có hai giá trị riêng biệt hoặc một vài phạm vi giá trị khác nhau để kiểm tra. Khi có nhiều hơn hai giá trị rời rạc để kiểm tra, câu lệnh for (var prop in object){ //loop body }52 là lựa chọn tối ưu nhất Khi tối ưu hóa for (var prop in object){ //loop body }58, mục tiêu luôn là giảm thiểu số lượng điều kiện cần đánh giá trước khi thực hiện đúng đường dẫn. Do đó, tối ưu hóa đơn giản nhất là đảm bảo rằng các điều kiện phổ biến nhất được ưu tiên. Hãy xem xét những điều sau đây for (var i=0; i < 10; i++){ //loop body }24 Mã này chỉ tối ưu nếu for (var i=0; i < 10; i++){ //loop body }209 thường xuyên nhất nhỏ hơn 5. Nếu for (var i=0; i < 10; i++){ //loop body }209 thường lớn hơn hoặc bằng 10, thì hai điều kiện phải được đánh giá mỗi lần trước khi thực hiện đúng đường dẫn, cuối cùng sẽ tăng lượng thời gian trung bình dành cho câu lệnh này. Các điều kiện trong một for (var prop in object){ //loop body }58 phải luôn được sắp xếp theo thứ tự từ nhiều khả năng nhất đến ít khả năng nhất để đảm bảo thời gian thực hiện nhanh nhất có thể Một cách tiếp cận khác để giảm thiểu việc đánh giá điều kiện là tổ chức ________ 558 thành một chuỗi các câu lệnh ________ 558 lồng nhau. Việc sử dụng một for (var prop in object){ //loop body }58 lớn, duy nhất thường dẫn đến thời gian thực hiện tổng thể chậm hơn khi mỗi điều kiện bổ sung được đánh giá. Ví dụ for (var i=0; i < 10; i++){ //loop body }25 Với câu lệnh for (var prop in object){ //loop body }58 này, số lượng điều kiện tối đa để đánh giá là 10. Điều này làm chậm thời gian thực hiện trung bình nếu bạn giả sử rằng các giá trị có thể có cho for (var i=0; i < 10; i++){ //loop body }209 được phân bổ đều trong khoảng từ 0 đến 10. Để giảm thiểu số lượng điều kiện cần đánh giá, mã có thể được viết lại thành một loạt các câu lệnh for (var prop in object){ //loop body }58 lồng nhau, chẳng hạn như for (var i=0; i < 10; i++){ //loop body }26 Câu lệnh for (var prop in object){ //loop body }58 được viết lại có số lượng tối đa bốn lần đánh giá điều kiện mỗi lần thông qua. Điều này đạt được bằng cách áp dụng cách tiếp cận giống như tìm kiếm nhị phân, chia các giá trị có thể thành một loạt các phạm vi để kiểm tra và sau đó đi sâu hơn vào phần đó. Lượng thời gian trung bình cần thiết để thực thi mã này bằng khoảng một nửa thời gian cần thiết để thực thi câu lệnh for (var prop in object){ //loop body }58 trước đó khi các giá trị được phân bổ đều trong khoảng từ 0 đến 10. Cách tiếp cận này là tốt nhất khi có các phạm vi giá trị cần kiểm tra (trái ngược với các giá trị rời rạc, trong trường hợp đó, câu lệnh for (var prop in object){ //loop body }52 thường phù hợp hơn) Đôi khi, cách tiếp cận tốt nhất đối với câu điều kiện là tránh sử dụng hoàn toàn for (var prop in object){ //loop body }58 và for (var prop in object){ //loop body }52. Khi có một số lượng lớn các giá trị rời rạc để kiểm tra, cả for (var prop in object){ //loop body }58 và for (var prop in object){ //loop body }52 đều chậm hơn đáng kể so với sử dụng bảng tra cứu. Có thể tạo bảng tra cứu bằng cách sử dụng mảng hoặc đối tượng thông thường trong JavaScript và việc truy cập dữ liệu từ bảng tra cứu nhanh hơn nhiều so với sử dụng for (var prop in object){ //loop body }58 hoặc for (var prop in object){ //loop body }52, đặc biệt khi số lượng điều kiện lớn (xem Hình 4-1) Hình 4-1. Â Tra cứu mục mảng so với sử dụng if-else hoặc switch trong Internet Explorer 7 Các bảng tra cứu không chỉ rất nhanh so với for (var prop in object){ //loop body }58 và for (var prop in object){ //loop body }52, mà chúng còn giúp làm cho mã dễ đọc hơn khi có một số lượng lớn các giá trị rời rạc để kiểm tra. Ví dụ: các câu lệnh for (var prop in object){ //loop body }52 bắt đầu trở nên khó sử dụng khi lớn, chẳng hạn như for (var i=0; i < 10; i++){ //loop body }27 Lượng không gian mà câu lệnh for (var prop in object){ //loop body }52 này chiếm trong mã có thể không tỷ lệ thuận với tầm quan trọng của nó. Toàn bộ cấu trúc có thể được thay thế bằng cách sử dụng một mảng làm bảng tra cứu for (var i=0; i < 10; i++){ //loop body }28 Khi sử dụng bảng tra cứu, bạn đã loại bỏ hoàn toàn mọi đánh giá điều kiện. Hoạt động trở thành tra cứu mục mảng hoặc tra cứu thành viên đối tượng. Đây là một lợi thế lớn cho các bảng tra cứu. vì không có điều kiện để đánh giá nên có rất ít hoặc không có chi phí bổ sung khi số lượng giá trị có thể tăng lên Bảng tra cứu hữu ích nhất khi có ánh xạ logic giữa một khóa và một giá trị (như trong ví dụ trước). Câu lệnh for (var prop in object){ //loop body }52 phù hợp hơn khi mỗi phím yêu cầu một hành động duy nhất hoặc tập hợp các hành động diễn ra Các thuật toán phức tạp thường được thực hiện dễ dàng hơn bằng cách sử dụng đệ quy. Trên thực tế, có một số thuật toán truyền thống coi việc triển khai là đệ quy, chẳng hạn như hàm trả về giai thừa for (var i=0; i < 10; i++){ //loop body }29 Vấn đề với các hàm đệ quy là điều kiện thiết bị đầu cuối bị thiếu hoặc không được xác định rõ ràng có thể dẫn đến thời gian thực hiện lâu làm đóng băng giao diện người dùng. Hơn nữa, các hàm đệ quy có nhiều khả năng chạy vào giới hạn kích thước ngăn xếp cuộc gọi của trình duyệt Lượng đệ quy được hỗ trợ bởi các công cụ JavaScript khác nhau và có liên quan trực tiếp đến kích thước của ngăn xếp lệnh gọi JavaScript. Ngoại trừ Internet Explorer, ngăn xếp cuộc gọi có liên quan đến bộ nhớ hệ thống khả dụng, tất cả các trình duyệt khác đều có giới hạn ngăn xếp cuộc gọi tĩnh. Kích thước ngăn xếp cuộc gọi cho các phiên bản trình duyệt gần đây nhất tương đối cao so với các trình duyệt cũ hơn (ví dụ: Safari 2 có kích thước ngăn xếp cuộc gọi là 100). Hình 4-2 hiển thị kích thước ngăn xếp cuộc gọi trên các trình duyệt chính Hình 4-2. Â Kích thước ngăn xếp cuộc gọi JavaScript trong trình duyệt Khi bạn vượt quá kích thước ngăn xếp cuộc gọi tối đa bằng cách giới thiệu quá nhiều đệ quy, trình duyệt sẽ báo lỗi với một trong các thông báo sau
Chrome là trình duyệt duy nhất không hiển thị thông báo cho người dùng khi vượt quá kích thước ngăn xếp cuộc gọi Có lẽ phần thú vị nhất của lỗi tràn ngăn xếp là chúng là lỗi JavaScript thực tế trong một số trình duyệt và do đó có thể bị bẫy bằng cách sử dụng câu lệnh for (var i=0; i < 10; i++){ //loop body }232. Loại ngoại lệ khác nhau dựa trên trình duyệt đang được sử dụng. Trong Firefox, đó là một for (var i=0; i < 10; i++){ //loop body }233; . (Opera không gây ra lỗi; nó chỉ dừng công cụ JavaScript. ) Điều này cho phép xử lý các lỗi như vậy ngay từ JavaScript for (var i=0; i < 10; i++){ //loop body }80 Nếu không được xử lý, các lỗi này sẽ nổi lên giống như bất kỳ lỗi nào khác (trong Firefox, nó xuất hiện trong Firebug và bảng điều khiển lỗi; trong Safari/Chrome, lỗi này xuất hiện trong bảng điều khiển JavaScript), ngoại trừ trong Internet Explorer. IE sẽ không chỉ hiển thị lỗi JavaScript mà còn hiển thị hộp thoại trông giống như một cảnh báo với thông báo tràn ngăn xếp Ghi chúMặc dù có thể bẫy các lỗi này trong JavaScript nhưng không nên. Không nên triển khai tập lệnh nào có khả năng vượt quá kích thước ngăn xếp cuộc gọi tối đa Khi bạn gặp phải giới hạn kích thước ngăn xếp cuộc gọi, bước đầu tiên của bạn là xác định bất kỳ trường hợp đệ quy nào trong mã. Cuối cùng, có hai mẫu đệ quy cần lưu ý. Đầu tiên là mẫu đệ quy đơn giản được trình bày trong hàm for (var i=0; i < 10; i++){ //loop body }236 được hiển thị trước đó, khi một hàm gọi chính nó. Mô hình chung là như sau for (var i=0; i < 10; i++){ //loop body }81 Mẫu này thường dễ xác định khi xảy ra lỗi. Mẫu thứ hai, tinh tế hơn bao gồm hai chức năng for (var i=0; i < 10; i++){ //loop body }82 Trong mẫu đệ quy này, hai hàm mỗi hàm gọi hàm kia, sao cho một vòng lặp vô hạn được hình thành. Đây là mẫu rắc rối hơn và khó xác định hơn nhiều trong các cơ sở mã lớn Hầu hết các lỗi ngăn xếp cuộc gọi đều liên quan đến một trong hai mẫu đệ quy này. Nguyên nhân thường gặp của lỗi tràn ngăn xếp là do điều kiện đầu cuối không chính xác, vì vậy, bước đầu tiên sau khi xác định mẫu là xác thực điều kiện đầu cuối. Nếu điều kiện đầu cuối là chính xác, thì thuật toán chứa quá nhiều đệ quy để có thể chạy an toàn trong trình duyệt và nên thay đổi để sử dụng phép lặp, ghi nhớ hoặc cả hai Bất kỳ thuật toán nào có thể được thực hiện bằng cách sử dụng đệ quy cũng có thể được thực hiện bằng cách sử dụng phép lặp. Các thuật toán lặp thường bao gồm một số vòng lặp khác nhau thực hiện các khía cạnh khác nhau của quy trình và do đó đưa ra các vấn đề về hiệu suất của chính chúng. Tuy nhiên, việc sử dụng các vòng lặp được tối ưu hóa thay cho các hàm đệ quy chạy dài có thể giúp cải thiện hiệu suất do chi phí vòng lặp thấp hơn so với khi thực thi một hàm Ví dụ, thuật toán sắp xếp hợp nhất được triển khai thường xuyên nhất bằng cách sử dụng đệ quy. Một triển khai JavaScript đơn giản của sắp xếp hợp nhất như sau for (var i=0; i < 10; i++){ //loop body }83 Mã cho cách sắp xếp hợp nhất này khá đơn giản và dễ hiểu, nhưng bản thân hàm for (var i=0; i < 10; i++){ //loop body }237 lại được gọi rất thường xuyên. Hàm đệ quy là nguyên nhân rất phổ biến gây ra lỗi tràn ngăn xếp trên trình duyệt Gặp phải lỗi tràn ngăn xếp không nhất thiết có nghĩa là toàn bộ thuật toán phải thay đổi; . Thuật toán sắp xếp hợp nhất cũng có thể được thực hiện bằng cách sử dụng phép lặp, chẳng hạn như for (var i=0; i < 10; i++){ //loop body }84 Việc triển khai for (var i=0; i < 10; i++){ //loop body }237 này thực hiện công việc giống như lần trước mà không sử dụng đệ quy. Mặc dù phiên bản lặp lại của sắp xếp hợp nhất có thể chậm hơn một chút so với tùy chọn đệ quy, nhưng nó không có cùng tác động ngăn xếp lệnh gọi như phiên bản đệ quy. Chuyển thuật toán đệ quy sang thuật toán lặp chỉ là một trong các tùy chọn để tránh lỗi tràn ngăn xếp Tránh làm việc là kỹ thuật tối ưu hóa hiệu suất tốt nhất. Mã của bạn phải làm càng ít công việc thì nó thực thi càng nhanh. Dọc theo những dòng đó, nó cũng có ý nghĩa để tránh lặp lại công việc. Thực hiện cùng một nhiệm vụ nhiều lần là một sự lãng phí thời gian thực hiện. Ghi nhớ là một cách tiếp cận để tránh lặp lại công việc bằng cách lưu vào bộ đệm các phép tính trước đó để sử dụng lại sau này, điều này làm cho việc ghi nhớ trở thành một kỹ thuật hữu ích cho các thuật toán đệ quy Khi các hàm đệ quy được gọi nhiều lần trong quá trình thực thi mã, sẽ có nhiều công việc trùng lặp. Hàm for (var i=0; i < 10; i++){ //loop body }236, được giới thiệu trước đó trong Đệ quy, là một ví dụ tuyệt vời về cách có thể lặp lại công việc nhiều lần bằng các hàm đệ quy. Hãy xem xét đoạn mã sau for (var i=0; i < 10; i++){ //loop body }85 Mã này tạo ra ba giai thừa và kết quả là hàm for (var i=0; i < 10; i++){ //loop body }236 được gọi tổng cộng 18 lần. Phần tồi tệ nhất của mã này là tất cả các công việc cần thiết được hoàn thành trên dòng đầu tiên. Vì giai thừa của 6 bằng 6 nhân với giai thừa của 5, nên giai thừa của 5 được tính hai lần. Tệ hơn nữa, giai thừa của 4 được tính ba lần. Sẽ hợp lý hơn nhiều nếu lưu các phép tính đó và sử dụng lại chúng thay vì bắt đầu lại từ đầu với mỗi lệnh gọi hàm Bạn có thể viết lại hàm for (var i=0; i < 10; i++){ //loop body }236 để sử dụng tính năng ghi nhớ theo cách sau for (var i=0; i < 10; i++){ //loop body }86 Chìa khóa cho phiên bản ghi nhớ này của hàm giai thừa là việc tạo một đối tượng bộ đệm. Đối tượng này được lưu trữ trên chính hàm đó và được điền sẵn hai giai thừa đơn giản nhất. 0 và 1. Trước khi tính giai thừa, bộ đệm này được kiểm tra để xem phép tính đã được thực hiện chưa. Không có giá trị bộ đệm có nghĩa là phép tính phải được thực hiện lần đầu tiên và kết quả được lưu trong bộ đệm để sử dụng sau. Hàm này được sử dụng theo cách tương tự như hàm for (var i=0; i < 10; i++){ //loop body }236 ban đầu for (var i=0; i < 10; i++){ //loop body }87 Mã này trả về ba giai thừa khác nhau nhưng thực hiện tổng cộng tám lệnh gọi tới for (var i=0; i < 10; i++){ //loop body }243. Vì tất cả các tính toán cần thiết được hoàn thành trên dòng đầu tiên, nên hai dòng tiếp theo không cần thực hiện bất kỳ phép đệ quy nào vì các giá trị được lưu trong bộ nhớ cache được trả về Quá trình ghi nhớ có thể hơi khác nhau đối với từng chức năng đệ quy, nhưng nhìn chung áp dụng cùng một mẫu. Để ghi nhớ một chức năng dễ dàng hơn, bạn có thể xác định một hàm for (var i=0; i < 10; i++){ //loop body }244 gói gọn chức năng cơ bản. Ví dụ for (var i=0; i < 10; i++){ //loop body }88 Hàm for (var i=0; i < 10; i++){ //loop body }244 này chấp nhận hai đối số. một chức năng để ghi nhớ và một đối tượng bộ đệm tùy chọn. Đối tượng bộ đệm có thể được chuyển vào nếu bạn muốn điền trước một số giá trị; . Sau đó, một hàm trình bao được tạo để bao bọc kết quả gốc ( for (var i=0; i < 10; i++){ //loop body }246) và đảm bảo rằng một kết quả mới chỉ được tính nếu nó chưa từng được tính trước đó. Hàm shell này được trả về để bạn có thể gọi nó trực tiếp, chẳng hạn như for (var i=0; i < 10; i++){ //loop body }89 Ghi nhớ chung của loại này kém tối ưu hơn so với việc cập nhật thủ công thuật toán cho một hàm nhất định vì hàm for (var i=0; i < 10; i++){ //loop body }244 lưu trữ kết quả của lệnh gọi hàm với các đối số cụ thể. Do đó, các cuộc gọi đệ quy chỉ được lưu khi hàm shell được gọi nhiều lần với cùng các đối số. Vì lý do này, tốt hơn hết là bạn nên triển khai ghi nhớ theo cách thủ công trong những chức năng có vấn đề nghiêm trọng về hiệu suất thay vì áp dụng một giải pháp ghi nhớ chung chung. Cũng giống như các ngôn ngữ lập trình khác, cách bạn tính mã và thuật toán bạn chọn ảnh hưởng đến thời gian thực thi của JavaScript. Không giống như các ngôn ngữ lập trình khác, JavaScript có một bộ tài nguyên hạn chế để rút ra, vì vậy các kỹ thuật tối ưu hóa thậm chí còn quan trọng hơn
Số lượng mã được thực thi càng lớn thì hiệu suất thu được từ việc sử dụng các chiến lược này càng lớn Chuyển đổi nhanh hơn nếuCác hằng số trường hợp trong câu lệnh switch tạo một bảng nhảy tại thời điểm biên dịch. Bảng nhảy này chọn đường dẫn thực hiện dựa trên giá trị của biểu thức. Nếu chúng ta có nhiều lựa chọn thì việc thực thi câu lệnh switch sẽ nhanh hơn nhiều so với logic tương đương của câu lệnh 'if-else' .
Chuyển đổi tốt hơn nếuHóa ra, câu lệnh switch nhanh hơn trong hầu hết các trường hợp so với câu lệnh if-else , nhưng chỉ nhanh hơn đáng kể khi số . Sự khác biệt chính về hiệu suất giữa hai loại này là chi phí gia tăng của một điều kiện bổ sung đối với if-else lớn hơn so với đối với switch.
Công tắc có hiệu quả hơn câu lệnh if không?Câu lệnh chuyển đổi hoạt động nhanh hơn nhiều so với bậc thang if-else tương đương . Đó là do trình biên dịch tạo một bảng nhảy cho một công tắc trong quá trình biên dịch. Kết quả là trong quá trình thực hiện, thay vì kiểm tra trường hợp nào thỏa mãn, nó chỉ quyết định trường hợp nào phải thực hiện.
Chuyển đổi tốt hơn nếuLưu ý, chuyển chậm hơn . Thật hấp dẫn khi nghĩ rằng một công tắc luôn nhanh hơn một câu lệnh if tương đương. Tuy nhiên, điều này là không đúng sự thật. Và một tình huống mà công tắc chậm hơn là khi thời gian chạy thực tế của chương trình có sự phân bổ đầu vào rất sai lệch. |