Biên dịch nâng cao

Tổng quan

Việc sử dụng Trình biên dịch khoá với compilation_level ADVANCED_OPTIMIZATIONS mang lại tỷ lệ nén tốt hơn so với việc biên dịch bằng SIMPLE_OPTIMIZATIONS hoặc WHITESPACE_ONLY. Việc biên dịch bằng ADVANCED_OPTIMIZATIONS đạt được độ nén cao hơn bằng cách linh hoạt hơn theo cách chuyển đổi mã và đổi tên các ký hiệu. Tuy nhiên, phương pháp mạnh mẽ hơn này có nghĩa là bạn phải cẩn thận hơn khi sử dụng ADVANCED_OPTIMIZATIONS để đảm bảo mã đầu ra hoạt động giống như mã đầu vào.

Hướng dẫn này minh hoạ chức năng của cấp độ biên dịch ADVANCED_OPTIMIZATIONS và những việc bạn có thể làm để đảm bảo mã hoạt động sau khi biên dịch bằng ADVANCED_OPTIMIZATIONS. Mã này cũng giới thiệu khái niệm bên ngoài: một biểu tượng được xác định trong mã bên ngoài mã do trình biên dịch xử lý.

Trước khi đọc hướng dẫn này, bạn cần làm quen với quy trình biên dịch JavaScript bằng một trong các công cụ Closure Compiler (giao diện người dùng của dịch vụ biên dịch, API dịch vụ biên dịch hoặc ứng dụng biên dịch).

Lưu ý về thuật ngữ: cờ dòng lệnh --compilation_level hỗ trợ chữ viết tắt ADVANCEDSIMPLE thường dùng hơn, cũng như ADVANCED_OPTIMIZATIONSSIMPLE_OPTIMIZATIONS chính xác hơn. Tài liệu này sử dụng dạng dài hơn, nhưng các tên có thể được sử dụng thay thế cho nhau trên dòng lệnh.

  1. Nén tốt hơn
  2. Cách bật n để người dùng có thể nâng cao tài khoản của bạn theo dự án NÂNG CAO
  3. Những điểm cần lưu ý khi sử dụng giải pháp totalvalue_ hoạt động tối ưu hoá
    1. Xoá mã bạn muốn giữ lại
    2. Tên thuộc tính không nhất quán
    3. Biên dịch riêng từng phần mã
    4. Mã tham chiếu bị hỏng trong quá trình biên dịch và mã không biên dịch

Nén tốt hơn

Với cấp độ biên dịch mặc định là SIMPLE_OPTIMIZATIONS, compile Compiler giúp giảm kích thước JavaScript bằng cách đổi tên các biến cục bộ. Tuy nhiên, có các biểu tượng khác với các biến cục bộ có thể được rút ngắn và có nhiều cách để thu nhỏ mã ngoài việc đổi tên các biểu tượng. Quá trình tổng hợp bằng ADVANCED_OPTIMIZATIONS sẽ khai thác toàn bộ khả năng rút gọn mã.

So sánh kết quả của SIMPLE_OPTIMIZATIONSADVANCED_OPTIMIZATIONS cho mã sau:

function unusedFunction(note) {
  alert(note['text']);
}

function displayNoteTitle(note) {
  alert(note['title']);
}

var flowerNote = {};
flowerNote['title'] = "Flowers";
displayNoteTitle(flowerNote);

Việc biên dịch bằng SIMPLE_OPTIMIZATIONS sẽ rút ngắn mã thành phần sau:

function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);

Việc biên dịch bằng ADVANCED_OPTIMIZATIONS sẽ rút ngắn hoàn toàn mã này:

alert("Flowers");

Cả hai tập lệnh này đều tạo cảnh báo có nội dung "Flowers", nhưng tập lệnh thứ hai sẽ nhỏ hơn nhiều.

Cấp độ ADVANCED_OPTIMIZATIONS không chỉ đơn giản là rút ngắn tên biến theo một số cách, bao gồm:

  • Đổi tên linh hoạt hơn:

    Việc biên dịch bằng SIMPLE_OPTIMIZATIONS chỉ đổi tên các thông số note của các hàm displayNoteTitle()unusedFunction(), vì đây là các biến duy nhất trong tập lệnh được bản địa hoá cho một hàm. ADVANCED_OPTIMIZATIONS cũng đổi tên biến toàn cục flowerNote.

  • xoá mã chết:

    Việc biên dịch bằng ADVANCED_OPTIMIZATIONS sẽ xoá hoàn toàn hàm unusedFunction() vì hàm này không bao giờ được gọi trong mã.

  • hàm cùng dòng:

    Việc biên dịch bằng ADVANCED_OPTIMIZATIONS sẽ thay thế lệnh gọi đến displayNoteTitle() bằng alert() duy nhất để soạn nội dung của hàm. Việc thay thế lệnh gọi hàm này bằng phần nội dung của hàm được gọi là "nội tuyến". Nếu hàm này dài hơn hoặc phức tạp hơn, thì cùng dòng có thể thay đổi hành vi của mã, nhưng Trình biên dịch đóng xác định rằng trong trường hợp này, cùng dòng là an toàn và tiết kiệm không gian. Việc biên dịch bằng ADVANCED_OPTIMIZATIONS cũng điều chỉnh các hằng số cùng dòng và một số biến khi xác định rằng nó có thể thực hiện một cách an toàn.

Danh sách này chỉ là ví dụ về các phép biến đổi giảm kích thước mà quá trình biên dịch ADVANCED_OPTIMIZATIONS có thể thực hiện.

Cách bật tính năng Để NÂNG CAO

Giao diện người dùng của dịch vụ Closure Compiler, API dịch vụ và ứng dụng đều có các phương thức khác nhau để đặt compilation_level thành ADVANCED_OPTIMIZATIONS.

Cách bật CHẾ ĐỘ NÂNG CAO trong giao diện người dùng Dịch vụ Trình biên dịch Đóng

Để bật ADVANCED_OPTIMIZATIONS cho giao diện người dùng của dịch vụ Đóng trình biên dịch, hãy nhấp vào nút chọn "Nâng cao".

Cách bật ADVANCED_OPTIMIZATIONS trong API dịch vụ Trình biên dịch đóng

Để bật ADVANCED_OPTIMIZATIONS cho API dịch vụ biên dịch Closure, hãy thêm một tham số yêu cầu có tên compilation_level với giá trị ADVANCED_OPTIMIZATIONS, như trong chương trình python sau:

#!/usr/bin/python2.4

import httplib, urllib, sys

params = urllib.urlencode([
    ('code_url', sys.argv[1]),
    ('compilation_level', 'ADVANCED_OPTIMIZATIONS'),
    ('output_format', 'text'),
    ('output_info', 'compiled_code'),
  ])

headers = { "Content-type": "application/x-www-form-urlencoded" }
conn = httplib.HTTPSConnection('closure-compiler.appspot.com')
conn.request('POST', '/compile', params, headers)
response = conn.getresponse()
data = response.read()
print data
conn.close()

Cách bật ADVANCED_OPTIMIZATIONS trong ứng dụng Closure Compiler

Để bật ADVANCED_OPTIMIZATIONS cho ứng dụng Closure Compiler, hãy đưa cờ hiệu dòng lệnh --compilation_level ADVANCED_OPTIMIZATIONS vào lệnh sau:

java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js

Những điểm cần chú ý khi sử dụng Elevate_MaxIMIZATIONS

Dưới đây là một số tác động không mong muốn phổ biến của NÂNG CAO và các bước bạn có thể thực hiện để tránh những ảnh hưởng đó.

Xoá mã mà bạn muốn giữ lại

Nếu bạn chỉ biên dịch hàm dưới đây bằng ADVANCED_OPTIMIZATIONS, Trình biên dịch đóng sẽ tạo ra kết quả trống:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}

Vì hàm này không bao giờ được gọi trong JavaScript mà bạn truyền cho trình biên dịch, nên compile Compiler giả định rằng mã này không cần thiết!

Trong nhiều trường hợp, hành vi này là chính xác những gì bạn muốn. Ví dụ: nếu bạn biên dịch mã cùng với một thư viện lớn, Trình biên dịch đóng có thể xác định những hàm trong thư viện mà bạn thực sự sử dụng và loại bỏ các hàm mà bạn không sử dụng.

Tuy nhiên, nếu bạn thấy Trình biên dịch đóng đang xoá các hàm bạn muốn giữ lại, có 2 cách để ngăn chặn điều này:

  • Di chuyển các lệnh gọi hàm vào mã do Trình biên dịch đóng.
  • Thêm tệp bên ngoài cho các hàm bạn muốn hiển thị.

Phần tiếp theo sẽ thảo luận chi tiết hơn về từng phương án.

Giải pháp: Di chuyển các lệnh gọi hàm vào mã do Trình biên dịch đóng xử lý

Bạn có thể gặp phải trường hợp xoá mã không mong muốn nếu chỉ biên dịch một phần mã bằng Trình biên dịch đóng. Ví dụ: bạn có thể có một tệp thư viện chỉ chứa các định nghĩa hàm và một tệp HTML bao gồm thư viện và chứa mã gọi các hàm đó. Trong trường hợp này, nếu bạn biên dịch tệp thư viện bằng ADVANCED_OPTIMIZATIONS, Closure Compiler sẽ xoá tất cả hàm thư viện của bạn.

Giải pháp đơn giản nhất cho vấn đề này là biên dịch các hàm cùng với phần của chương trình gọi các hàm đó. Ví dụ: Trình biên dịch đóng sẽ không xoá displayNoteTitle() khi biên dịch chương trình sau:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
displayNoteTitle({'myTitle': 'Flowers'});

Hàm displayNoteTitle() không bị xoá trong trường hợp này vì Closure Compiler nhận thấy hàm này được gọi.

Nói cách khác, bạn có thể ngăn việc xoá mã không mong muốn bằng cách đưa điểm nhập của chương trình vào mã mà bạn chuyển đến Trình biên dịch đóng. Điểm truy cập của chương trình là vị trí trong mã nơi chương trình bắt đầu thực thi. Ví dụ: trong chương trình ghi chú hoa từ phần trước, ba dòng cuối cùng được thực thi ngay khi JavaScript được tải trong trình duyệt. Đây là điểm truy cập cho chương trình này. Để xác định mã bạn cần giữ lại, Closure Compiler bắt đầu tại điểm truy cập này và theo dõi luồng điều khiển của chương trình từ đó về sau.

Giải pháp: Bao gồm Exter cho các hàm bạn muốn hiển thị

Bạn có thể xem thêm thông tin về giải pháp này bên dưới và trên trang về bên ngoài và xuất.

Tên thuộc tính không nhất quán

Quá trình biên dịch Trình biên dịch đóng không bao giờ thay đổi các giá trị cố định kiểu chuỗi trong mã của bạn, bất kể cấp độ biên dịch bạn sử dụng là gì. Điều này có nghĩa là quá trình biên dịch bằng ADVANCED_OPTIMIZATIONS sẽ xử lý các thuộc tính theo cách khác nhau tuỳ thuộc vào việc mã của bạn có truy cập vào các thuộc tính đó bằng một chuỗi hay không. Nếu bạn kết hợp các tệp tham chiếu chuỗi với một thuộc tính có các tham chiếu cú pháp dấu chấm, Trình biên dịch đóng sẽ đổi tên một số tệp tham chiếu đến thuộc tính đó nhưng không đổi tên các tệp tham chiếu khác. Do đó, có thể mã của bạn sẽ không chạy đúng cách.

Ví dụ: hãy lấy mã sau:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
var flowerNote = {};
flowerNote.myTitle = 'Flowers';

alert(flowerNote.myTitle);
displayNoteTitle(flowerNote);

Hai câu lệnh cuối cùng trong mã nguồn này thực hiện cùng một việc. Tuy nhiên, khi nén mã bằng ADVANCED_OPTIMIZATIONS, bạn sẽ nhận được thông báo sau:

var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);

Câu lệnh cuối cùng trong mã nén tạo ra một lỗi. Tệp tham chiếu trực tiếp đến thuộc tính myTitle đã được đổi tên thành a, nhưng tệp tham chiếu được trích dẫn đến myTitle trong hàm displayNoteTitle chưa được đổi tên. Do đó, câu lệnh cuối cùng đề cập đến một thuộc tính myTitle không còn tồn tại.

Giải pháp: Đảm bảo sự nhất quán trong tên tài sản

Giải pháp này khá đơn giản. Đối với bất kỳ loại hoặc đối tượng nào cụ thể, hãy sử dụng dấu chấm câu hoặc chỉ đọc chuỗi trích dẫn. Đừng kết hợp cú pháp, đặc biệt là khi tham chiếu đến cùng một thuộc tính.

Ngoài ra, khi có thể, hãy ưu tiên sử dụng cú pháp chấm vì nó hỗ trợ việc kiểm tra và tối ưu hoá hiệu quả hơn. Chỉ sử dụng quyền truy cập vào thuộc tính chuỗi được trích dẫn khi bạn không muốn Trình biên dịch đóng, thực hiện việc đổi tên, chẳng hạn như khi tên từ một nguồn bên ngoài, chẳng hạn như JSON được giải mã.

Biên dịch hai phần mã riêng biệt

Nếu chia ứng dụng thành các phần mã khác nhau, bạn có thể biên dịch riêng từng phần. Tuy nhiên, nếu hai phần mã tương tác, việc này có thể gây khó khăn. Ngay cả khi bạn thành công, kết quả của hai lần chạy Trình biên dịch đóng sẽ không tương thích.

Ví dụ: giả sử một ứng dụng được chia thành 2 phần: một phần truy xuất dữ liệu và một phần hiển thị dữ liệu.

Dưới đây là mã để truy xuất dữ liệu:

function getData() {
  // In an actual project, this data would be retrieved from the server.
  return {title: 'Flower Care', text: 'Flowers need water.'};
}

Dưới đây là mã để hiển thị dữ liệu:

var displayElement = document.getElementById('display');
function displayData(parent, data) {
  var textElement = document.createTextNode(data.text);
  parent.appendChild(textElement);
}
displayData(displayElement, getData());

Nếu cố gắng biên dịch hai đoạn mã này riêng biệt, bạn sẽ gặp phải một số vấn đề. Trước tiên, Trình biên dịch đóng đã xoá hàm getData() vì những lý do được mô tả trong phần Xoá mã mà bạn muốn giữ lại. Thứ hai, Trình biên dịch đóng cửa gây ra một lỗi nghiêm trọng khi xử lý mã hiển thị dữ liệu đó.

input:6: ERROR - variable getData is undefined
displayData(displayElement, getData());

Vì trình biên dịch không có quyền truy cập vào hàm getData() khi biên dịch mã hiển thị dữ liệu, nên trình biên dịch này sẽ xem getData là không xác định.

Giải pháp: Biên dịch tất cả mã cho một trang

Để đảm bảo việc biên dịch phù hợp, hãy biên dịch tất cả mã cho một trang trong cùng một lần chạy biên dịch. Trình biên dịch đóng có thể chấp nhận nhiều tệp JavaScript và chuỗi JavaScript làm dữ liệu đầu vào, vì vậy, bạn có thể chuyển mã thư viện và mã khác cùng nhau trong một yêu cầu biên dịch.

Lưu ý: Phương pháp này sẽ không hiệu quả nếu bạn cần kết hợp mã đã biên dịch và chưa biên dịch. Vui lòng xem Mã tham chiếu bị hỏng giữa mã đã biên dịch và chưa biên dịch để biết các mẹo xử lý tình huống này.

Tham chiếu bị hỏng giữa mã đã biên dịch và chưa biên dịch

Việc đổi tên biểu tượng trong ADVANCED_OPTIMIZATIONS sẽ làm hỏng việc giao tiếp giữa mã do Trình biên dịch đóng và mọi mã khác xử lý. Quá trình biên dịch sẽ đổi tên các hàm được xác định trong mã nguồn. Mọi mã bên ngoài gọi các hàm của bạn đều sẽ bị hỏng sau khi bạn biên dịch, vì mã này vẫn tham chiếu đến tên hàm cũ. Tương tự, mã tham chiếu trong mã đã biên dịch cho các ký hiệu được xác định bên ngoài có thể bị Trình biên dịch đóng thay đổi.

Hãy lưu ý rằng "mã chưa biên dịch" bao gồm bất kỳ mã nào được chuyển vào hàm eval() dưới dạng một chuỗi. Closure Compiler không bao giờ thay đổi các giá trị cố định kiểu chuỗi trong mã, vì vậy, Trình biên dịch đóng không thay đổi các chuỗi được truyền sang câu lệnh eval().

Hãy lưu ý rằng đây là những vấn đề có liên quan nhưng khác biệt: duy trì hoạt động giao tiếp được biên dịch với bên ngoài và việc duy trì hoạt động giao tiếp bên ngoài với quá trình biên dịch. Những vấn đề riêng biệt này có giải pháp chung, nhưng luôn có những sắc thái riêng. Để khai thác tối đa Trình biên dịch đóng, bạn cần phải hiểu trường hợp của mình.

Trước khi tiếp tục, bạn có thể làm quen với các tệp bên ngoài và xuất.

Giải pháp để gọi vào mã bên ngoài từ mã đã biên dịch: Biên dịch với mã cũ

Nếu sử dụng mã được cung cấp cho trang của bạn bằng một tập lệnh khác, bạn cần đảm bảo rằng Closure Compiler không đổi tên các tham chiếu thành các biểu tượng được xác định trong thư viện bên ngoài đó. Để thực hiện việc này, hãy đưa một tệp chứa các phần tử bên ngoài cho thư viện bên ngoài vào nội dung bạn biên dịch. Thao tác này sẽ cho Trình biên dịch đóng tên biết những tên bạn không kiểm soát và do đó không thể thay đổi. Mã của bạn phải sử dụng cùng tên của tệp bên ngoài.

Các ví dụ phổ biến về việc này là các API như OpenOpen APIGoogle Maps API. Ví dụ: nếu mã của bạn gọi hàm OpenSocial opensocial.newDataRequest() mà không có các tiện ích thích hợp, Trình biên dịch đóng sẽ chuyển đổi lệnh gọi này thành a.b().

Giải pháp để gọi vào mã đã biên dịch từ mã bên ngoài: Triển khai phần mở rộng

Nếu có mã JavaScript sử dụng lại làm thư viện, bạn có thể sử dụng Closure Compiler để thu gọn thư viện trong khi vẫn cho phép mã chưa biên dịch gọi các hàm trong thư viện.

Giải pháp trong trường hợp này là triển khai một bộ bên ngoài xác định API công khai của thư viện. Mã của bạn sẽ cung cấp định nghĩa cho các ký hiệu được khai báo trong những tiện ích này. Điều này có nghĩa là mọi lớp hoặc hàm mà bên ngoài mà bạn đề cập. Điều này cũng có thể có nghĩa là các lớp của bạn sẽ triển khai giao diện được khai báo ở bên ngoài.

Những người bên ngoài này cũng hữu ích cho những người khác, không chỉ cho bạn. Người dùng thư viện của bạn sẽ cần đưa vào thư viện này nếu họ đang biên dịch mã, vì thư viện của bạn đại diện cho một tập lệnh bên ngoài theo góc nhìn của họ. Hãy nghĩ rằng bên ngoài là hợp đồng giữa bạn và người tiêu dùng, thì cả hai bạn đều cần một bản sao.

Để đạt được mục tiêu này, hãy đảm bảo rằng khi biên dịch mã, bạn cũng phải đưa các phần mở rộng vào trình biên dịch. Điều này có vẻ bất thường, vì chúng tôi thường nghĩ rằng người bên ngoài là "từ một nơi khác", nhưng bạn cần phải cho Trình biên dịch đóng biết bạn đang để lộ những biểu tượng nào, vì vậy họ không được đổi tên.

Một lưu ý quan trọng là bạn có thể nhận được thông tin chẩn đoán "định nghĩa trùng lặp" về mã xác định ký hiệu bên ngoài. Closure Compiler giả định rằng mọi biểu tượng trong trình đơn bên ngoài đang được một thư viện bên ngoài cung cấp và hiện không thể hiểu được rằng bạn đang cung cấp định nghĩa một cách có chủ ý. Các thông tin chẩn đoán này an toàn cho việc giải quyết và bạn có thể coi việc chặn là một xác nhận rằng bạn thực sự đang thực hiện API.

Ngoài ra, Trình biên dịch đóng có thể kiểm tra loại rằng các định nghĩa của bạn khớp với các loại khai báo bên ngoài. Điều này cung cấp thêm xác nhận rằng định nghĩa của bạn là chính xác.