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:
- Được viết bằng C ++ và được sử dụng trong Chrome và Node.js (và phiên bản mới nhất của Microsoft Edge )
- Triển khai ECMAScript như được chỉ định trong ECMA-262
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:
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.
Các tin liên quan
Hiểu phạm vi biến trong JavaScript2019-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