Thứ năm, 10/10/2019 | 00:00 GMT+7

Công cụ V8 và Mẹo tối ưu hóa JavaScript


V8 là công cụ của Google để biên dịch JavaScript của ta . Firefox có công cụ riêng gọi là SpiderMonkey, nó khá giống với V8 nhưng có những điểm khác biệt. Ta sẽ thảo luận về động cơ V8 trong bài viết này.

Một vài sự thật về động cơ V8:

Hành trình JavaScript

Vì vậy, chính xác điều gì sẽ xảy ra khi ta gửi JavaScript của bạn để được phân tích cú pháp bởi động cơ V8 (đây là sau khi nó được rút gọn, không được xác minh và bất cứ điều gì điên rồ khác mà bạn làm với mã JavaScript của bạn )?

Tôi đã tạo sơ đồ sau cho thấy tất cả các bước, sau đó ta sẽ thảo luận chi tiết từng bước:

Sơ đồ hành trình JavaScript

Trong bài viết này, ta sẽ thảo luận về cách mã JavaScript được phân tích cú pháp và cách đưa càng nhiều JavaScript của bạn vào Trình biên dịch Tối ưu hóa càng tốt. Trình biên dịch Tối ưu hóa (hay còn gọi là Turbofan ) lấy mã JavaScript của ta và chuyển đổi nó thành Mã máy hiệu suất cao, do đó, ta cung cấp càng nhiều mã thì ứng dụng của ta càng nhanh. Lưu ý thêm, trình thông dịch trong Chrome được gọi là Ignition.

Phân tích cú pháp JavaScript

Vì vậy, cách xử lý đầu tiên đối với mã JavaScript của ta là phân tích cú pháp nó. Hãy thảo luận chính xác phân tích cú pháp là gì.

Có hai giai đoạn để phân tích cú pháp đó là:

  • Háo hức (phân tích cú pháp đầy đủ) - điều này phân tích cú pháp từng dòng ngay lập tức
  • Lazy (phân tích cú pháp trước) - thực hiện mức tối thiểu, phân tích cú pháp những gì ta cần và để phần còn lại cho đến sau

Cái nào tốt hơn? Tất cả phụ thuộc vào.

Hãy xem xét một số mã.

// eager parse declarations right away const a = 1; const b = 2;  // lazily parse this as we don't need it right away function add(a, b) {   return a + b; }  // oh looks like we do need add so lets go back and parse it add(a, b); 

Vì vậy, ở đây các khai báo biến của ta sẽ được eager parsed nhưng sau đó hàm của ta được lazily parsed . Điều này thật tuyệt vời cho đến khi ta add(a, b) vì ta cần hàm add mình ngay lập tức, vì vậy sẽ nhanh hơn nếu eager parse add ngay lập tức.

Để eager parse hàm add ngay lập tức, ta có thể làm:

// eager parse declarations right away const a = 1; const b = 2;  // eager parse this too var add = (function(a, b) {   return a + b; })();  // we can use this right away as we have eager parsed // already add(a, b); 

Đây là cách tạo hầu hết các module bạn sử dụng.

Nội tuyến hàm

Chrome đôi khi về cơ bản sẽ viết lại JavaScript của bạn, một ví dụ về điều này là nội dòng một hàm đang được sử dụng.

Hãy lấy đoạn mã sau làm ví dụ:

const square = (x) => { return x * x }  const callFunction100Times = (func) => {   for(let i = 0; i < 100; i++) {     // the func param will be called 100 times     func(2)   } }  callFunction100Times(square) 

Đoạn mã trên sẽ được tối ưu hóa bởi động cơ V8 như sau:

const square = (x) => { return x * x }  const callFunction100Times = (func) => {   for(let i = 100; i < 100; i++) {     // the function is inlined so we don't have      // to keep calling func     return x * x   } }  callFunction100Times(square) 

Như bạn thấy ở trên, về cơ bản V8 đang loại bỏ bước mà ta gọi là func và thay vào đó là phần nội dung của square . Điều này rất hữu ích vì nó sẽ cải thiện hiệu suất của mã của ta .

Hàm gotcha nội tuyến

Có một chút khó hiểu với cách tiếp cận này, hãy lấy ví dụ mã sau:

const square = (x) => { return x * x } const cube = (x) => { return x * x * x }  const callFunction100Times = (func) => {   for(let i = 100; i < 100; i++) {     // the function is inlined so we don't have      // to keep calling func     func(2)   } }  callFunction100Times(square) callFunction100Times(cube) 

Vì vậy, lần này sau khi ta đã được gọi là square chức năng 100 lần, sau đó ta sẽ gọi cho cube chức năng 100 lần. Trước khi cube có thể được gọi, trước tiên ta phải khử tối ưu hóa callFunction100Times vì ta đã nội tuyến nội dung hàm square . Trong những trường hợp như thế này, hàm square sẽ có vẻ nhanh hơn hàm cube nhưng những gì đang xảy ra là bước khử tối ưu hóa làm cho quá trình thực thi lâu hơn.

Các đối tượng

Khi nói đến các đối tượng, V8 dưới mui xe có một hệ thống loại để phân biệt các đối tượng của bạn:

Monomorphism

Các đối tượng có cùng khóa không có sự khác biệt.

// mono example const person = { name: 'John' } const person2 = { name: 'Paul' } 

Tính đa hình

Các đối tượng có chung cấu trúc với một số khác biệt nhỏ.

// poly example const person = { name: 'John' } const person2 = { name: 'Paul', age: 27 } 

Megamorphism

Các đối tượng hoàn toàn khác nhau và không thể so sánh được.

// mega example const person = { name: 'John' } const building = { rooms: ['cafe', 'meeting room A', 'meeting room B'], doors: 27 } 

Vì vậy, bây giờ ta đã biết các đối tượng khác nhau trong V8, hãy xem cách V8 tối ưu hóa các đối tượng của ta .

Các lớp ẩn

Các lớp ẩn là cách V8 xác định các đối tượng của ta .

Hãy chia nhỏ điều này thành các bước.

Ta khai báo một đối tượng:

const obj = { name: 'John'} 

Sau đó V8 sẽ khai báo một classId cho đối tượng này.

const objClassId = ['name', 1] 

Sau đó, đối tượng của ta được tạo như sau:

const obj = {...objClassId, 'John'} 

Sau đó, khi ta truy cập thuộc tính name trên đối tượng của ta như sau:

obj.name 

V8 thực hiện tra cứu sau:

obj[getProp(obj[0], name)] 

Đây là quá trình V8 trải qua khi tạo các đối tượng của ta , bây giờ hãy xem cách ta có thể tối ưu hóa các đối tượng của bạn và sử dụng lại các classIds .

Mẹo tạo đối tượng

Nếu có thể, bạn nên khai báo các thuộc tính của bạn trong hàm tạo . Điều này sẽ đảm bảo cấu trúc đối tượng được giữ nguyên để sau đó V8 có thể tối ưu hóa các đối tượng của bạn.

class Point {   constructor(x,y) {     this.x = x     this.y = y   } }  const p1 = new Point(11, 22) // hidden classId created const p2 = new Point(33, 44) 

Bạn nên giữ thứ tự thuộc tính không đổi , lấy ví dụ sau:

const obj = { a: 1 } // hidden class created obj.b = 3  const obj2 = { b: 3 } // another hidden class created obj2.a = 1  // this would be better const obj = { a: 1 } // hidden class created obj.b = 3  const obj2 = { a: 1 } // hidden class is reused obj2.b = 3 

Mẹo tối ưu hóa chung

Vì vậy, bây giờ ta hãy đi vào một số mẹo chung sẽ giúp mã JavaScript của bạn được tối ưu hóa tốt hơn.

Sửa các loại đối số của hàm

Khi các đối số được chuyển đến một hàm, điều quan trọng là chúng phải cùng kiểu. Turbofan sẽ từ bỏ việc cố gắng tối ưu hóa JavaScript của bạn sau 4 lần thử nếu các loại đối số khác nhau.

Lấy ví dụ sau:

function add(x,y) {   return x + y }  add(1,2) // monomorphic add('a', 'b') // polymorphic add(true, false) add({},{}) add([],[]) // megamorphic - at this stage, 4+ tries, no optimization will happen 

Một mẹo khác là đảm bảo khai báo các lớp trong phạm vi toàn cục :

// don't do this function createPoint(x, y) {   class Point {     constructor(x,y) {       this.x = x       this.y = y     }   }    // new point object created every time   return new Point(x,y) }  function length(point) {   //... } 

Kết luận

Vì vậy, tôi hy vọng bạn đã học được một vài điều về cách V8 hoạt động dưới mui xe và cách viết mã JavaScript được tối ưu hóa tốt hơn.


Tags:

Các tin liên quan

Hiểu phạm vi biến trong JavaScript
2019-10-01
Hiểu điều này, ràng buộc, gọi và áp dụng trong JavaScript
2019-09-30
Sử dụng phương pháp cắt chuỗi trong JavaScript
2019-09-16
Những lý do tại sao bạn không bao giờ nên sử dụng eval () trong JavaScript
2019-08-26
Toàn cầu mới Thuộc tính JavaScript này
2019-08-08
Vẽ hình với API Canvas JavaScript
2019-08-05
clientWidth và clientHeight trong JavaScript
2019-07-24
Các phương pháp hay nhất để gỡ lỗi mã JavaScript trong trình duyệt
2019-07-05
Tối ưu hóa Tuyên bố chuyển đổi trong JavaScript
2019-06-18
Làm việc với Singletons trong JavaScript
2019-04-19